Search code examples
javasocketsserversocket

Content of Socket's InputStream inaccessible after write when closing


I'm trying to read the full content of a socket inputstream that just got closed. If I just try reading it normally works however, I'm constantly trying to write in that socket's output stream in the same time. It looks like writing into it "breaks" the socket's inputstream and makes it impossible to read what's remaining.

Take this small example I made to illustrate my problem:

@Test
public void socketTest() throws Throwable
{
    new Thread(() -> {
        try
        {
            ServerSocket serverSocket = new ServerSocket(5555);

            while(true)
            {

                //keep listening
                Socket socket = serverSocket.accept();

                InputStream in = socket.getInputStream();

                System.out.println(in.read());

                Thread.sleep(2000);

                try
                {
                    socket.getOutputStream().write(3);
                }
                catch(Throwable ex)
                {
                    System.out.println("Error writing");
                    ex.printStackTrace();
                }

                System.out.println(in.read());

                in.close();
                socket.close();
                System.exit(0);
            }
        }
        catch(Throwable ex)
        {
            System.out.println("Error reading");
            ex.printStackTrace();
        }
    }).start();

    Thread.sleep(100); //you might need this
    Socket socket = new Socket("localhost", 5555);
    OutputStream out = socket.getOutputStream();
    out.write(1);
    out.write(2);
    out.close();
    socket.close();

    synchronized(this)
    {
        wait();
    }
}

I get the output:

1
java.net.SocketException: Software caused connection abort: recv failed
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
    at java.net.SocketInputStream.read(SocketInputStream.java:170)
    at java.net.SocketInputStream.read(SocketInputStream.java:141)
    at java.net.SocketInputStream.read(SocketInputStream.java:223)
    at net.jumpai.test.SocketTTest.lambda$socketTest$0(SocketTTest.java:55)
    at java.lang.Thread.run(Thread.java:745)
Error reading

However the 2 was never read and writing 3 in the stream never made an error. I don't care if writing in the stream throws but I'd like to read the 2. Is there any way to do that?

I'm on Windows 7 with jdk1.8.0_91. Even after updating to jdk1.8.0_181 (latest version right now) I could reproduce the problem. I could also reproduce on another Windows 7 computer with Java 1.8.0_111.

Could not reproduce the bug on ArchLinux open-jdk1.8.0_172, does this means it's a bug with the java version? It really seems to be linked to Windows or at least Oracle's implementation.


Solution

  • Seeing that the behavior can easily be reproduced on my 2 Windows machines on any version but not on any Linux or Mac system, I suspect it's a bug in Oracle's JVM that doesn't exists with open-jdk.

    This bug is pretty annoying and I would really appreciate if someone could post a work around. Meanwhile, I've submitted a bug report to Oracle: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8208479

    Trying to build a work around, I've come with this that allows to check if your system has the bug.

    public static boolean hasOracleSocketBug()
    {
        try
        {
            int port = 10000;
    
            while(!PortChecker.portAvailable(port))
                port++;
    
            ServerSocket serverSocket = new ServerSocket(port);
    
            int fPort = port;
            new Thread(() -> {
                try
                {
                    Socket socket = new Socket("localhost", fPort);
                    OutputStream out = socket.getOutputStream();
                    out.write(1);
                    out.write(2);
                    out.close();
                    socket.close();
                }
                catch(Exception ex)
                {
                    try
                    {
                        serverSocket.close();
                    }
                    catch(IOException ignored) {}
                }
            }).start();
    
            Socket socket = serverSocket.accept();
    
            InputStream in = socket.getInputStream();
            in.read();
            socket.getOutputStream().write(3);
    
            try
            {
                in.read();
            }
            catch(Exception ex)
            {
                socket.close();
                return true;
            }
    
            in.close();
            socket.close();
            return false;
        }
        catch(Throwable ignored)
        {
            return false;
        }
    }
    
    public static boolean portAvailable(int port)
    {
        ServerSocket ss = null;
        try
        {
            ss = new ServerSocket(port);
            ss.setReuseAddress(true);
            return true;
        }
        catch(IOException ignored)
        {
            return false;
        }
        finally
        {
            if(ss != null)
            {
                try
                {
                    ss.close();
                }
                catch(IOException ignored) {}
            }
        }
    }