JDK-4726957 : (so) Socket.close fails if timeout set on Socket created from SocketChannel
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: spider,1.3.0,1.4.1,1.4.2_05
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic,other,linux,solaris
  • CPU: generic,other,x86,sparc
  • Submitted: 2002-08-06
  • 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.
Other JDK 6
5.0u7Fixed 6 b33Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Description
Name: rmT116609			Date: 08/06/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 :
Linux 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686 unknown
Red Hat Linux release 7.1 (Seawolf)
glibc-2.2.2-10

ADDITIONAL OPERATING SYSTEMS :
Windows NT 4.0

A DESCRIPTION OF THE PROBLEM :
Socket.close() does not work if the socket was created from
SocketChannel.open() and ever had a read timeout set via
setSoTimeout().  Under these circumstances, close() does not
send a FIN packet.  This happens on both Linux and Windows
NT.  Closer investigation with strace on Linux shows that
neither the close() nor shutdown() system calls are being
called.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached code.  This connects a client C to a server
S and simulates a simple protocol:

-C sends a message to S
-S sends a message to C
-C sends a message to S
-C closes the connection

The important thing is that C is created via
SocketChannel.open(..), though C does not use non-blocking
IO.  Also, C calls setSoTimeout before reading each message
from S, but the read never times out.

The details of the server thread are mostly irrelevant.
This server can run in a different process on another
machine.  However, adding one carefully placed delay in the
server does seem to exacerbate the problem.

EXPECTED VERSUS ACTUAL BEHAVIOR :
Below is the expected output.  Messages from the client are
shown on the left.  Message from the server are shown on the
right.

    Establishing connection
    1. Writing byte 1
				Listening on port 6347
				Accepted client
				Read byte 1

				2. Writing byte 2
    Read byte 2

    3. Writing byte 3
				Read byte 3

    Closing
				Read byte EOF
				Closing

At this point, both threads should terminate and the JVM
should exit.  However, running the code actually results in
the following output:

    Establishing connection
    1. Writing byte 1
				Listening on port 6347
				Accepted client
				Read byte 1

				2. Writing byte 2
    Read byte 2

    3. Writing byte 3
				Read byte 3

    Closing
                                [hangs]

Note that the server never reads the EOF.  Hence the server
thread hangs and the JVM does not terminate.  Investigation
with tcpdump shows that the client never sent a FIN segment.
 I've omitted several fields in the output below for
clarity.  Time is shown in milliseconds.

    13 lo > me.4427 > me.6347: S 2170895982:2170895982(0)
                                         win 32767
    13 lo > me.6347 > me.4427: S 2163215961:2163215961(0)
                                  ack 2170895983 win 32767
    13 lo > me.4427 > me.6347: . 1:1(0) ack 1 win 32767
    15 lo > me.4427 > me.6347: P 1:2(1) ack 1 win 32767
    15 lo > me.6347 > me.4427: . 1:1(0) ack 2 win 32767
    23 lo > me.6347 > me.4427: P 1:2(1) ack 2 win 32767
    23 lo > me.4427 > me.6347: . 2:2(0) ack 2 win 32767
    24 lo > me.4427 > me.6347: P 2:3(1) ack 2 win 32767
    24 lo > me.6347 > me.4427: . 2:2(0) ack 3 win 32767

If the program were working properly (e.g., if setSoTimeout
or SocketChannel.open were not used) the following
additional segments would sent:

    25 lo < me.4430 > me.6347: F 3:3(0) ack 2 win 32767
    25 lo < me.6347 > me.4430: F 2:2(0) ack 4 win 32767
    25 lo < me.4430 > me.6347: . 4:4(0) ack 3 win 32767


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.*;
import java.nio.*;
import java.net.*;
import java.nio.channels.*;

/**
 * Tests whether socket close always result in a FIN packet.
 */
public class SocketCloseTest {
    final static int PORT=6347;

    public static void main(String args[]) {
        //Start listening thread.
        try {
            ServerSocketChannel listener=ServerSocketChannel.open();
            listener.socket().bind(new InetSocketAddress(PORT));
            AcceptorThread thread=new AcceptorThread(listener);
            thread.start();
        } catch (IOException e) {
            System.out.println("Mysterious IO problem");
            e.printStackTrace();
            System.exit(1);
        }
        

        //Establish connection.  Bug only happens if we open with channel.
        try {
            System.out.println("Establishing connection");
            Socket socket=SocketChannel.open(
                new InetSocketAddress("127.0.0.1", PORT)).socket();
            OutputStream out=socket.getOutputStream();
            InputStream in=socket.getInputStream();

            System.out.println("1. Writing byte 1");
            out.write((byte)1);

            int n=read(socket, in);
            System.out.println("Read byte "+n+"\n");

            System.out.println("3. Writing byte 3");
            out.write((byte)3);

            System.out.println("Closing");
            socket.close();
        } catch (IOException e) {
            System.out.println("Mysterious IO problem");
            e.printStackTrace();
            System.exit(1);
        }
    }

    /** Reads one byte from in, which must be s.getInputStream.  */
    private static int read(Socket s, InputStream in) throws IOException {
        try {
            s.setSoTimeout(8000);     //causes a bug!
            return in.read();
        } finally {
            s.setSoTimeout(0);
        }
    }
}

/** Server thread */
class AcceptorThread extends Thread {
    final String INDENT="\t\t\t\t";
    ServerSocketChannel _listener;
    
    /** @param listener MUST be bound to a port */
    AcceptorThread(ServerSocketChannel listener) {
        _listener=listener;
    }

    public void run() {
        try {
            //This sleep isn't strictly necessary but seems to make the bug more
            //repeatable.
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) { }

            System.out.println(INDENT+"Listening on port "
                               +SocketCloseTest.PORT);
            ByteBuffer buf=ByteBuffer.allocate(5);
            Socket client=_listener.accept().socket();;
            System.out.println(INDENT+"Accepted client");

            OutputStream out=client.getOutputStream();
            InputStream in=client.getInputStream();
            
            int n=in.read();
            System.out.println(INDENT+"Read byte "+n+"\n");

            System.out.println(INDENT+"2. Writing byte 2");
            out.write((byte)2);

            n=in.read();
            System.out.println(INDENT+"Read byte "+n+"\n");

            n=in.read();
            System.out.println(INDENT+"Read byte "
                               +(n<0 ? "EOF" : Integer.toString(n)));

            System.out.println(INDENT+"Closing");
            client.close();
        } catch (IOException e) {
            System.out.println(INDENT+"Error accepting!");
        }
    }
}

---------- END SOURCE ----------

CUSTOMER WORKAROUND :
-use Socket.shutdownOutput() and shutdownInput() instead of
 close().  The former results in a FIN segment.
-use "new Socket(..)" instead of SocketChannel.open(..) if
 non-blocking IO is not needed
-don't use setSoTimeout() if not needed
(Review ID: 160421) 
======================================================================

Comments
EVALUATION Essentially the same as 4724030. -- ###@###.### 2002/8/7
08-10-0173