Socket.close needs to interrupt threads blocked in I/O

RESOLVED FIXED

Status

RESOLVED FIXED
14 years ago
12 years ago

People

(Reporter: glenbeasley, Assigned: glenbeasley)

Tracking

Sun
Solaris

Details

Attachments

(4 attachments, 6 obsolete attachments)

(Assignee)

Description

14 years ago
request to implement JSS SSLSocket.close so that it has the same behavior as the
JDK Socket.Close in the sense that any thread currently blocked in an I/O
operation upon this socket will throw a SocketException. 

http://java.sun.com/j2se/1.5.0/docs/api/java/net/Socket.html#close()
(Assignee)

Updated

14 years ago
No longer depends on: 282496
(Assignee)

Comment 1

14 years ago
Created attachment 178188 [details]
Test Case to demostrate separate IO theads handling close
(Assignee)

Comment 2

14 years ago
Created attachment 178193 [details] [diff] [review]
read/write/close thread protection

The first call to SSLSocket.close or SSLServerSocket.close will close the
socket and release all native resources. Any further calls to  SSLSocket.close
or SSLServerSocket.close will return. The finalize method  will call
SSLSocket.close or SSLServerSocket.close incase the user did not. 

If a separate thread is performing a read or a write on the socket the socket
will throw an I/O exception, and exit gracefully. SocketBase.close is no longer
synchronized because SSLSocket.close and SSLServerSocket.close methods have
their own synchronization. 

The Sun JES Portal server team did week long testing of this fix for both the
Portal server and Netlet on the SUN_SECURITY_3_3_BRANCH. 

We still need a proper fix for NSPR on windows
https://bugzilla.mozilla.org/show_bug.cgi?id=282496 passing in PR_SHUTDOWN_BOTH
ends up sending an FIN/EOF to the other end, which will close the socket and
unblock the read operation.
(Assignee)

Updated

14 years ago
Attachment #178193 - Flags: superreview?(wtchang)
Attachment #178193 - Flags: review?(saul.edwards.bugs)

Updated

14 years ago
Attachment #178193 - Flags: review?(saul.edwards.bugs) → review+

Comment 3

14 years ago
Comment on attachment 178193 [details] [diff] [review]
read/write/close thread protection

I recommend doing the following.

In the JSSL_SocketData structure or the PRFilePrivate
structure its jsockPriv member points to, add two
PRThread * members:

    PRThread *reader;
    PRThread *writer;

Add native methods to set the reader or writer member
to the current thread (the return value of PR_GetCurrentThread())
or to NULL.

The SSLSocket.read() method would set the reader member
to the current thread before calling socketRead(), and
set the reader member back to NULL before returning or
throwing an exception.	Similarly for the SSLSocket.write()
method.

Then, rename Java_org_mozilla_jss_ssl_SSLSocket_shutdownNativeLow
as Java_org_mozilla_jss_ssl_SSLSocket_unblockReaderWriter (because
unblocking reader and writer is what that method is really trying
to do), and instead of or in addition to calling PR_Shutdown, add
these PR_Interrupt calls:

    if (sock->reader) {
	PR_Interrupt(sock->reader);
    }
    if (sock->writer) {
	PR_Interrupt(sock->writer);
    }

This way you don't need any NSPR change related to PR_Shutdown.
(Assignee)

Comment 4

14 years ago
Created attachment 178876 [details] [diff] [review]
read/write/close thread protection

the main update to this patch is in the 
the Java_org_mozilla_jss_ssl_SSLSocket_abortReadWrite method 
I added comments in the method stating 

    * The java layer prevents I/O once closed has been 
    * called but if an I/O op was in progress then 
    * abort the I/O operation.
    * We could use PR_Interrupt or PR_Shutdown to abort the I/O
    * PR_Shutdown is more responsive on unix but PR_Interrupt
    * is required on windows to unblock the I/O. On 
    * WINNT PR_Interrupt does not work see 282496, so 
    * we rely on PR_Shutdown(fd, PR_SHUTDOWN_SEND) to send an
    * EOF to the other end to kill the socket, which as long as 
    * the other end ends kills the socket, the reader will then 
    * unblock.
    */ 
    /* when bug 282496 is fixed this code needs to be updated */
    /* to use PR_Interrupt on WINNT for the reader and writer */
Attachment #178193 - Attachment is obsolete: true
Attachment #178876 - Flags: superreview?(wtchang)
(Assignee)

Comment 5

14 years ago
Created attachment 178877 [details] [diff] [review]
sorry had some debug pks11 that should not of been in last patch
Attachment #178876 - Attachment is obsolete: true
Attachment #178877 - Flags: superreview?(wtchang)
(Assignee)

Comment 6

14 years ago
note this bug also address PR_PENDING_INTERRUPT_ERROR
https://bugzilla.mozilla.org/show_bug.cgi?id=206332
It still does not handle the PR_IO_PENDING_ERROR case
https://bugzilla.mozilla.org/show_bug.cgi?id=206333

Status: NEW → ASSIGNED
(Assignee)

Comment 7

14 years ago
*** Bug 278434 has been marked as a duplicate of this bug. ***

Comment 8

14 years ago
Glen, I am confused by your comment 4 about
WINNT PR_Interrupt and bug 282496.  Bug 282496
doesn't mention PR_Interrupt at all!

Updated

14 years ago
Attachment #178193 - Flags: superreview?(wtchang)

Updated

14 years ago
Attachment #178876 - Flags: superreview?(wtchang)

Comment 9

14 years ago
Comment on attachment 178877 [details] [diff] [review]
sorry had some debug pks11 that should not of been in last patch 

Glen,

Sorry about the long review comments below.

1. In org/mozilla/jss/ssl/SSLServerSocket.java, I
think we also need a native abortAccept method so
that closing an SSL server socket will cause any
pending accept method calls to throw an exception,
right?

>     public Socket accept() throws IOException {
>+        if (isClosed) { /* abort accept if socket closed */
>+             throw new 
>+             IOException("SSLServerSocket has been closed, and cannot be reused."); 
>+        }

I think the testing and setting of the isClosed
class member needs to be synchronized.	Otherwise
it is not thread safe.	Since you already made
the close method (which sets isClosed) sycnchronized,
we just need to put the above code in a synchronized
block:
	  synchronized (this) {
	      if (isClosed) { /* abort accept if socket closed */
		  throw new 
		  IOException("SSLServerSocket has been closed, and cannot be
reused."); 
	      }
	  }

>+    protected void finalize() throws Throwable {
>+        this.close(); /* in Case user never called closed */
>+    }

"Case" should not be capitalized.

>+    public synchronized void close() throws IOException {
>+        isClosed=true;
>+        if( sockProxy != null ) { /* only call base.close once */
>+            sockProxy = null;
>+            base.close();
>+        }
>     }

Why does the whole close method need to be synchronized?

Do we really need to allow the close method to be called
multiple times?

It seems that isClosed is true if and only if sockProxy is
null, so the the body of the close method can be like this:

	  if( !isClosed ) { /* only call base.close once */
	      isClosed = true;
	      sockProxy = null;
	      base.close();
	  }

2. org/mozilla/jss/ssl/SSLSocket.c

>+#include <prthread.h>

This is not necessary because this file already includes
<nspr.h>, which includes all NSPR headers.

>+    /* set the current thread doing the read*/
>+    sock->reader = PR_GetCurrentThread();

Please add
      PR_ASSERT(sock->reader == NULL);
before setting sock->reader to PR_GetCurrentThread().

>@@ -717,28 +720,33 @@
>         } else {
>             /* some error, but is it recoverable? */
>             PRErrorCode err = PR_GetError();
> 
> 
>-            if( err == PR_PENDING_INTERRUPT_ERROR ||
>-                err == PR_IO_PENDING_ERROR )
>+            if( err == PR_IO_PENDING_ERROR )
>             {
>                 /* just try again */
>             } else {
>-#ifdef WINNT
>+		if (err == PR_PENDING_INTERRUPT_ERROR ) {
>+                    JSSL_throwSSLSocketException(env, 
>+				"Read operation interrupted");
>+                    goto finish;
>+		}

You need to call PR_NT_CancelIo for WINNT when you receive
the PR_PENDING_INTERRUPT_ERROR.  This is similar to
PR_IO_TIMEOUT_ERROR.

Perhaps this is why you had strange problems after calling
PR_Interrupt on WINNT?

>                 if (err == PR_IO_TIMEOUT_ERROR ) {
>+#ifdef WINNT
>+
>                 /*
>                  * if timeout was set, and the PR_Accept() timed out,
>                  * then cancel the I/O on the port, otherwise PR_Accept()
>                  * will always return PR_IO_PENDING_ERROR on subsequent
>                  * calls
>                  */
>                     PR_NT_CancelIo(sock->fd);
>+#endif 

In the comment, change "PR_Accept" to "PR_Recv" (two occurrences).

>+    /*Set the current thread doing the write */
>+    sock->writer = PR_GetCurrentThread();

Add
      PR_ASSERT(sock->writer == NULL);

>+            if(err == PR_IO_PENDING_ERROR)
>             {
>                 /* just try again */
>-            } else if( err == PR_IO_TIMEOUT_ERROR ) {
>+            } else  if (err == PR_PENDING_INTERRUPT_ERROR ) {
>+                    JSSL_throwSSLSocketException(env, "Write operation interrupted");
>+                    goto finish;

Need to call PR_NT_CancelIo for WINNT.

>+		} else if( err == PR_IO_TIMEOUT_ERROR ) {
> #ifdef WINNT
>                 /*
>                  * if timeout was set, and the PR_Accept() timed out,
>                  * then cancel the I/O on the port, otherwise PR_Accept()
>                  * will always return PR_IO_PENDING_ERROR on subsequent

Need to change "PR_Accept" to "PR_Send" in the comment.

> JNIEXPORT void JNICALL
>+Java_org_mozilla_jss_ssl_SSLSocket_abortReadWrite(
>+    JNIEnv *env, jobject self)
>+{
...
>+    /*
>+    * The java layer prevents I/O once closed has been 
>+    * called but if an I/O op is in progress then 
>+    * abort the I/O operation.
>+    * We could use PR_Interrupt or PR_Shutdown to abort the I/O
>+    * PR_Shutdown is more responsive on unix but PR_Interrupt
>+    * is required on windows to unblock the I/O. On 
>+    * WINNT PR_Interrupt does not work see 282496, so 
>+    * we rely on PR_Shutdown(fd, PR_SHUTDOWN_SEND) to send an
>+    * EOF to the other end to kill the socket, which as long as 
>+    * the other end ends kills the socket, the reader will then 
>+    * unblock.
>+    */ 
>+    /* when bug 282496 is fixed this code needs to be updated */
>+    /* to use PR_Interrupt on WINNT for the reader and writer */

This comment needs work.  It is self-contradictory.  It first
says "PR_Interrupt is required on windows" and then says
"On WINNT PR_Interrupt does not work ... so we rely on
PR_Shutdown..."

>+    if (sock->reader) {
>+	PR_Shutdown(fd, PR_SHUTDOWN_RCV); 
>+#ifdef WINNT 
>+	PR_Shutdown(fd, PR_SHUTDOWN_SEND);
>+#endif
>+    }
>+
>+    if (sock->writer) {
>+	PR_Shutdown(fd, PR_SHUTDOWN_SEND);
>+    }

My original suggestion was to do the following:

>+    if (sock->reader) {
>+	PR_Interrupt(sock->reader);
>+    }
>+
>+    if (sock->writer) {
>+	PR_Interrupt(sock->writer);
>+    }

We should find out why PR_Interrupt doesn't work.
We should use PR_Shutdown only on platforms where
PR_Interrupt doesn't work.

3. org/mozilla/jss/ssl/SSLSocket.java

There are some thread synchronization problems in
this file.

I suggest the following code for the close,
read, and write methods.

    public void close() throws IOException {
	// this ugly nesting of synchronized blocks can
	// be avoided at the cost of having separate
	// abortRead and abortWrite methods.
	synchronized (readLock) {
	    synchronized (writeLock) {
		/*if a read or write is occuring abort the I/O, which will
cause any
		 *further attempts to read/write to fail since "isClosed==true"
		 */
		if (ioRead || ioWrite) {
		    abortReadWrite();
		}
		while (ioWrite) {
		    writeLock.wait();
		}
	    }
	    while (ioRead) {
		readLock.wait();
	    }
	}
	synchronized (this) {
	    if	(!isClosed) {  /* only call base.close once
		isClosed = true;
		sockProxy = null;
		base.close();
	    }
	}
    }

    int read(byte[] b, int off, int len) throws IOException {
	synchronized (this) {
	    if (isClosed) { /* abort read if socket closed */
		throw new 
		IOException("Socket has been closed, and cannot be reused."); 
	    }
	}
	int iRet = 0;
	synchronized (readLock) {
	    ioRead=true;
	    try {
		iRet = socketRead(b, off, len, base.getTimeout()); 
	    } catch (IOException ioe) {
		ioRead=false;
		throw new 
		IOException("SocketException cannot read on socket");
	    }
	    ioRead=false;
	}
	return iRet;
    }

Similarly for the write method (omitted for brevity).

4. org/mozilla/jss/ssl/common.c

> JNIEXPORT void JNICALL
> Java_org_mozilla_jss_ssl_SocketProxy_releaseNativeResources
>     (JNIEnv *env, jobject this)
>+{  
>+   /* call to socket.close will destroy all Native resources  */
>+   /* if user does call close on the socket the finalize method will*/
> }

Why is this method a no-op?  Can we remove it altogether?  It
is very confusing to have a method that does nothing.

OK, that's it!

Comment 10

14 years ago
Comment on attachment 178877 [details] [diff] [review]
sorry had some debug pks11 that should not of been in last patch 

Sorry, I forgot to mention one problem.  In org/mozilla/jss/ssl/common.c

>+   /* if user does call close on the socket the finalize method will*/

should that be "if the user does not call close..."?
(Assignee)

Comment 11

14 years ago
Created attachment 179008 [details] [diff] [review]
added comment for PR_Interrupt bug 282496

in reply to comment 8

I updated bug 282496 to mention PR_Interrupt,  
created a seperate bug for PR_Interrupt 288232, and 
changed abortReadWrite methods comments. 

Although 288232 or 282496 needs to be solved, I would like to check 
in this JSS bug so that our internal JES 4 products can use the fix
with the understanding that on WINNT we rely on the remote connection
closing the SSL socket first to unblock the read before closing the 
socket.

Java_org_mozilla_jss_ssl_SSLSocket_abortReadWrite(
    JNIEnv *env, jobject self)
{
    JSSL_SocketData *sock = NULL;
    PRStatus status;
    PRFileDesc *fd = NULL;

    if( JSSL_getSockData(env, self, &sock) != PR_SUCCESS) goto finish;

    /*find the lowest fileDescriptor */

    fd = sock->fd;
    if (fd == NULL) {
	JSS_throw(env, NULL_POINTER_EXCEPTION);
	goto finish;
    }
    while (fd->lower) {
	fd = fd->lower;
    }
    /*
    * The java layer prevents I/O once closed has been 
    * called but if an I/O op is in progress then 
    * abort the I/O operation.
    * We could use PR_Interrupt or PR_Shutdown to abort the I/O
    * PR_Shutdown is more responsive on unix but PR_Interrupt
    * is required on windows to unblock the I/O. On 
    * WINNT PR_Interrupt does not work see 288232 and 282496, so 
    * we rely on PR_Shutdown(fd, PR_SHUTDOWN_SEND) to send an
    * EOF to the other end to kill the socket, which as long as 
    * the other end ends kills the socket, the reader will then 
    * unblock.
    */ 
    /* when bug 288232 or 282496 is fixed this code needs to be updated */
    /* to use PR_Interrupt on WINNT for the reader and writer */
    if (sock->reader) {
	PR_Shutdown(fd, PR_SHUTDOWN_RCV); 
#ifdef WINNT 
	PR_Shutdown(fd, PR_SHUTDOWN_SEND);
#endif
    }

    if (sock->writer) {
	PR_Shutdown(fd, PR_SHUTDOWN_SEND);
    }
finish:
    EXCEPTION_CHECK(env, sock)
    return;
}
Attachment #178877 - Attachment is obsolete: true
Attachment #179008 - Flags: superreview?(wtchang)

Comment 12

14 years ago
Please ignore my suggested rewrite of the close,
read, and write methods for now.  It is wrong.
(Assignee)

Comment 13

14 years ago
Created attachment 179012 [details] [diff] [review]
calling PR_NT_CancelIo in socketRead and socketWrtie 

replying to comment 12

Wan-teh your rewrite does work. By calling PR_NT_CancelIo in SocketRead and 
SocketWrite when err == PR_PENDING_INTERRUPT_ERROR the solution works
correctly.
I tested several times, with no issue.
Note socket.close will wait until ioRead and ioWrite are false because the 
readLock and writeLock locks will not become free until the threads exit the
socket.read and socket.write. 
After you review this fix, I will close bug 288232
Attachment #179008 - Attachment is obsolete: true
Attachment #179012 - Flags: superreview?(wtchang)
(Assignee)

Comment 14

14 years ago
Created attachment 179124 [details]
Test program for ublocking SSLServerSocket.accept when closing 

Test program puts a thread blocked on accept, and has the main 
thread call close to unblock the accept thread and the close 
the SSLServerSocket.
(Assignee)

Comment 15

14 years ago
Created attachment 179128 [details] [diff] [review]
SSLServerSocket and SSLSocket protection for close methods

Reply to comment 9

1) I added support for abortAccept, I also attached a test program for 
this case. 

1a) the isClosed flag does not need to be synchronized because the close 
method now has acceptLock and the close method itself is not synchronized.
(orginally I was going to add proper protection for SSLServerSocket after
SSLSocket was completed but now this bug addresses both). 

2) done

3 I tested the thread synchronization, and reviewed the code 
with Nelson several times. I would like to keep the nested blocks in 
SSLSocket.close. Once a thread goes into the close method the private boolean 
isClosed is set to true, (it is never set to false afterwards.) In the close
method when a reader or writer needs to be aborted, after they are aborted the
locks will be free for the close method to acquire. Any attempts by other
threads to read/write will be aborted since the isClosed flag is set to true
and the threads will return from the read/write methods instead of being
allowed to acquire a lock again.

4) done
(Assignee)

Updated

14 years ago
Attachment #179012 - Attachment is obsolete: true
Attachment #179128 - Flags: superreview?(wtchang)

Comment 16

14 years ago
Created attachment 179350 [details] [diff] [review]
wtc's patch

Glen,

I fixed the thread synchronization problems in your
patch.	Please look at my comments "Locking strategy"
in SSLServerSocket.java and SSLSocket.java, and
my comment in jssl.h next to the "PRLock *lock;"
member of struct JSSL_SocketData.

I also some other code cleanup and bug fixes (the
bugs are not all in your patch; some are preexisting).

Now that I understand the code better, I'm not sure
I like how you fixed the resource leaks.  The reason
is that now SocketProxy.releaseNativeResources has an
empty body (I found that this method must be defined
because it is an abstract method in the base class).

So now we allow user to call close more than once.
Should we?  Can a standard Java socket be closed
more than once?

Note that I got rid of the for loops around PR_Accept,
PR_Recv, and PR_Send because they are not necessary.
If you call PR_NT_CancelIo properly, you will never
get PR_IO_PENDING_ERROR.  And even if you get
PR_IO_PENDING_ERROR for some reason, you should not
loop back to retry the I/O call.

I got rid of the necessary code in abortAccept and
abortReadWrite to find the lowest file descriptor.

I have one question about your code, which I marked
with a "XXX" comment.  Please take a look at it.

My version of your patch passed the two tests of yours.
(Assignee)

Comment 17

14 years ago
Created attachment 179749 [details] [diff] [review]
checkin of patch

Wan-teh and I had in-person code review meeting. 
checkins:
/cvsroot/mozilla/security/jss/lib/jss.def,v  <--  jss.def
new revision: 1.28; previous revision: 1.27
done
 cvs commit -m "282732 read/write/accept/close thread protection"
cvs commit: Examining .
Enter passphrase for key '/home/gb134726/.ssh/id_dsa': 
? .nbattrs
Checking in SSLServerSocket.c;
/cvsroot/mozilla/security/jss/org/mozilla/jss/ssl/SSLServerSocket.c,v  <-- 
SSLServerSocket.c
new revision: 1.15; previous revision: 1.14
done
Checking in SSLServerSocket.java;
/cvsroot/mozilla/security/jss/org/mozilla/jss/ssl/SSLServerSocket.java,v  <-- 
SSLServerSocket.java
new revision: 1.18; previous revision: 1.17
done
Checking in SSLSocket.c;
/cvsroot/mozilla/security/jss/org/mozilla/jss/ssl/SSLSocket.c,v  <-- 
SSLSocket.c
new revision: 1.22; previous revision: 1.21
done
Checking in SSLSocket.java;
/cvsroot/mozilla/security/jss/org/mozilla/jss/ssl/SSLSocket.java,v  <-- 
SSLSocket.java
new revision: 1.20; previous revision: 1.19
done
Checking in SocketBase.java;
/cvsroot/mozilla/security/jss/org/mozilla/jss/ssl/SocketBase.java,v  <-- 
SocketBase.java
new revision: 1.12; previous revision: 1.11
done
Checking in common.c;
/cvsroot/mozilla/security/jss/org/mozilla/jss/ssl/common.c,v  <--  common.c
new revision: 1.22; previous revision: 1.21
done
Checking in jssl.h;
/cvsroot/mozilla/security/jss/org/mozilla/jss/ssl/jssl.h,v  <--  jssl.h
new revision: 1.15; previous revision: 1.14
done
(Assignee)

Updated

14 years ago
Attachment #179350 - Attachment is obsolete: true
(Assignee)

Comment 18

14 years ago
This bug also fixed bug 206332 and bug 20633
Status: ASSIGNED → RESOLVED
Last Resolved: 14 years ago
Resolution: --- → FIXED
(Assignee)

Comment 19

14 years ago
meant this bug fixed bug 206333 not 20633

Updated

14 years ago
Attachment #178877 - Flags: superreview?(wtchang)

Updated

14 years ago
Attachment #179008 - Flags: superreview?(wtchang)

Updated

14 years ago
Attachment #179012 - Flags: superreview?(wtchang)

Updated

14 years ago
Attachment #179128 - Flags: superreview?(wtchang)
You need to log in before you can comment on or make changes to this bug.