Name: nt126004 Date: 07/31/2002
FULL PRODUCT VERSION :
java version "1.4.1-beta"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1-beta-b14)
Java HotSpot(TM) Client VM (build 1.4.1-beta-b14, mixed mode)
FULL OPERATING SYSTEM VERSION :
Windows NT Version 4.0
A DESCRIPTION OF THE PROBLEM :
Let S be a socket created from an NIO channel, with a
SO_TIMEOUT value of T. Say that a thread T1 is blocked in a
call to S.getInputStream().read(). If another thread T2
calls S.close(), T1's call to read() should terminate
immediately and throw an IOException.
However, this does not happen with Java 1.4.1-beta or 1.4.0.
Instead, T1's call to read() does not return for a full T
milliseconds, and then it throws CancelledKeyException. So
there are really two problems: read() terminates too slowly
and then throws an unexpected exception.
The bug happens on Windows NT and Linux. Have not tried
other platforms. It does not happen if the Socket is not
created from a channel. Nor does it happen if SO_TIMEOUT is
not set.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached program. You will need an internet
connection to contact the named web server.
EXPECTED VERSUS ACTUAL BEHAVIOR :
The attached program should generate the following output,
modulo a few timing changes:
Thread1: opening connection
Thread1: starting read w/timeout
Thread2: closing socket
Thread2: socket closed
Thread1: connection closed
Thread1: terminating after 74 msecs
Instead it prints the following:
Thread1: opening connection
Thread1: starting read w/timeout
Thread2: closing socket
Thread2: socket closed
Thread1: terminating after 3074 msecs
******UNEXPECTED EXCEPTION!******
java.nio.channels.CancelledKeyException
at
sun.nio.ch.SelectionKeyImpl.ensureValid(SelectionKeyImpl.java:55)
at
sun.nio.ch.SelectionKeyImpl.readyOps(SelectionKeyImpl.java:77)
at
java.nio.channels.SelectionKey.isReadable(SelectionKey.java:271)
at
sun.nio.ch.SocketAdaptor$SocketInputStream.read(SocketAdaptor.java:186)
at
sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:86)
at java.io.InputStream.read(InputStream.java:88)
at
sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:64)
at
CloseInterruptsReadTest.startConnection(CloseInterruptsReadTest.java:39)
at
CloseInterruptsReadTest.main(CloseInterruptsReadTest.java:16)
ERROR MESSAGES/STACK TRACES THAT OCCUR :
To repeat the above exception (hopefully without line wrapping):
java.nio.channels.CancelledKeyException
at sun.nio.ch.SelectionKeyImpl.ensureValid(SelectionKeyImpl.java:55)
at sun.nio.ch.SelectionKeyImpl.readyOps(SelectionKeyImpl.java:77)
at java.nio.channels.SelectionKey.isReadable(SelectionKey.java:271)
at sun.nio.ch.SocketAdaptor$SocketInputStream.read(SocketAdaptor.java:186)
at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:86)
at java.io.InputStream.read(InputStream.java:88)
at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:64)
at CloseInterruptsReadTest.startConnection(CloseInterruptsReadTest.java:39)
at CloseInterruptsReadTest.main(CloseInterruptsReadTest.java:16)
Also, it's worth noting that I once got a NullPointerException in the call to
read. This may be a distinct bug; I am still struggling to repeat it.
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
import java.nio.*;
import java.net.*;
public class CloseInterruptsReadTest {
private static Socket _socket=null;
public static void main(String args[]) {
try {
startKillerThread();
startConnection();
} catch (Exception e) {
System.err.println("******UNEXPECTED EXCEPTION!******");
e.printStackTrace();
}
}
/** Synchronously connects to server and starts a read. Blocking. */
private static void startConnection() throws IOException {
//Create connection to arbitrary server using channels. Here we use an
//HTTP server, because it won't send data until it gets a proper GET
//request. Make connection blocking, with timeouts.
System.out.println("Thread1: opening connection");
SocketAddress addr=new InetSocketAddress("www.limewire.com", 80);
SocketChannel channel=SocketChannel.open(addr);
_socket=channel.socket();
_socket.setSoTimeout(3000);
InputStream in=_socket.getInputStream();
//Now start a read.
System.out.println("Thread1: starting read w/timeout");
long start=System.currentTimeMillis();
try {
int b=in.read();
} catch (IOException e) {
System.out.println("Thread1: connection closed");
return;
} finally {
long elapsed=System.currentTimeMillis()-start;
System.out.println("Thread1: terminating after "+elapsed+" msecs");
}
}
/** Starts a thread to close connection created by startConnection.
* Does not block. */
private static void startKillerThread() {
KillerThread thread=new KillerThread();
thread.start();
}
private static class KillerThread extends Thread {
public void run() {
//Busy wait for read() to start.
while (_socket==null) { }
try { Thread.sleep(100); } catch (InterruptedException e) { }
//Now close connection
System.out.println("\t\t\t\t\tThread2: closing socket");
try {
_socket.close();
System.out.println("\t\t\t\t\tThread2: socket closed");
} catch (IOException e) {
System.err.println("\t\t\t\t\tThread2: couldn't kill!");
}
}
}
}
---------- END SOURCE ----------
CUSTOMER WORKAROUND :
We can catch CancelledKeyException and treat it like
IOException. That doesn't take care of the timing issue,
but that's only a minor performance problem in our application.
(Review ID: 160054)
======================================================================