JDK-4724030 : (so) Async close of socket stream fails when timeout specified
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 1.4.1
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: linux,solaris,windows_nt
  • CPU: x86,sparc
  • Submitted: 2002-07-31
  • Updated: 2005-04-16
  • Resolved: 2005-04-16
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6
6 b33Fixed
Related Reports
Duplicate :  
Relates :  
Description

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) 
======================================================================

Comments
EVALUATION This is an oversight in the socket-adaptor code. Not hard to fix. -- ###@###.### 2002/7/31
07-10-0197