JDK-6546995 : (so) SocketChannel.close doesn't preempt socket adapter emulation of timed reads
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 6
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: solaris_2.5.1
  • CPU: x86
  • Submitted: 2007-04-17
  • Updated: 2012-09-11
  • Resolved: 2012-09-11
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
tbd_minorResolved
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
Java(TM) SE Runtime Environment (build 1.6.0-b105)
Java HotSpot(TM) 64-Bit Server VM (build 1.6.0-b105, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Linux version 2.6.13-15.8-amd64 (geeko@buildhost) (gcc version 3.3.5 20050117 (prerelease) (SUSE Linux)) #4 SMP Fri Mar 3 20:18:21 CET 2006

A DESCRIPTION OF THE PROBLEM :
I encountered that an asynchronous close on a SocketChannel which is configured to be blocking and has an SO_TIMEOUT will not behave as expected. I think it should exactly imitate a socket's behavior. The used socket adapter emulates timeouts by putting the socket channel into non-blocking mode and using a temporary Selector to poll the socket as far as I found out. The asynchronous close of the socket channel (or socket) doesn't know about the timed operation and so doesn't wakeup the Selector. Instead of this it hangs until the timeout run out at this stacktrace:

at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(Unknown Source)
at sun.nio.ch.EPollSelectorImpl.doSelect(Unknown Source)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(Unknown Source)
at sun.nio.ch.SelectorImpl.select(Unknown Source)
at sun.nio.ch.SocketAdaptor$SocketInputStream.read(Unknown Source)
at sun.nio.ch.ChannelInputStream.read(Unknown Source)

On Windows platform it behaves as expected.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
run the following Unittest

1. accept a socketchannel via an serversocketchannel
2. configure it to blocking
3. set an so timeout on it
4. do an asynchronous close on that socketchannel

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The Reader thread should get an ClosedChannelException in read() after about 1500 ms (the time, close() is called).
ACTUAL -
We get an SocketTimeoutException after SO_TIMEOUT which is set to 10000

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

import junit.framework.Assert;
import junit.framework.TestCase;



/**
 * @author ###@###.###
 */
public class AsyncCloseNIOChannelTest extends TestCase {

    private class Acceptor extends Thread {

        private ServerSocketChannel m_server;

        Acceptor() throws IOException {
            super.setDaemon(true);
            this.m_server =  ServerSocketChannel.open();
            this.m_server.socket().bind(AsyncCloseNIOChannelTest.this.m_serverAddress);
        }
        
        /**
         * @see java.lang.Thread#run()
         */
        @Override
        public void run() {
            try {
                AsyncCloseNIOChannelTest.this.m_acceptedChannel = this.m_server.accept();
                AsyncCloseNIOChannelTest.this.m_acceptedChannel.configureBlocking(true);
            } catch (IOException e) {
                Assert.fail("io error in accept: " + e + ", " + e.getMessage());
            }
        }
        
    }

    
    private class Reader extends Thread {

        /**
         * @see java.lang.Thread#run()
         */
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            try {
                AsyncCloseNIOChannelTest.this.m_acceptedChannel.socket().setSoTimeout(10000);
                InputStream inputStream = AsyncCloseNIOChannelTest.this.m_acceptedChannel.socket().getInputStream();
                inputStream.read();
            } catch (ClosedChannelException e) {
                // this is what we wan't
                e.printStackTrace(System.out);
            } catch (IOException e) {
                Assert.fail("io error in read: " + e + ", " + e.getMessage());
            } finally {
                AsyncCloseNIOChannelTest.this.m_duration = (System.currentTimeMillis() - start);
            }
        }
        
    }
    
    long m_duration;
    InetSocketAddress m_serverAddress;
    SocketChannel m_acceptedChannel;

    /**
     * @see junit.framework.TestCase#setUp()
     */
    protected void setUp() throws Exception {
        super.setUp();
        this.m_acceptedChannel = null;
        this.m_duration = Long.MAX_VALUE;
        this.m_serverAddress = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 17007);
    }

    
    /**
     * @throws Exception
     */
    public void testClose() throws Exception {
            
        new Acceptor().start();
        
        Socket socket = new Socket();
        socket.connect(this.m_serverAddress);
        
        Reader reader = new Reader();
        reader.setName("Reader");
        reader.start();
        
        int sleep = 1500;
        
        Thread.sleep(sleep);
        
        //new StackDump(reader).printStackTrace(System.out);
        this.m_acceptedChannel.close();
        
        reader.join();
        
        assertTrue("close did not interrupt read after about 2500 ms, but: " + this.m_duration , this.m_duration < (sleep * 2));
    }
}
---------- END SOURCE ----------

Comments
EVALUATION The socket adapter emulates legacy Socket read timeouts by putting the socket channel into non-blocking mode and using a temporary Selector to poll the socket. If the socket channel is closed before the timeout expires then it is platform/selector provider specific if the Selector wakeups. We expect this scenario is rare (this is the first bug report and the issue has existed since 1.4). The issue can be resolved by close invoking the Selector's wakeup method and small changes to the read timeout implementation to check the socket state.
17-04-2007