JDK-6645197 : (so) Timed read with socket adaptor throws ClosedSelectorException if temporary selector GC'ed.
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 5.0u24,6u3,6u20
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: other
  • Submitted: 2007-12-24
  • Updated: 2011-05-18
  • Resolved: 2011-05-18
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 Other Other JDK 6 JDK 7
5.0u25-revFixed 5.0u26-revFixed 5.0u27Fixed 6u21-revFixed 7 b31Fixed
Related Reports
Relates :  
Description
OPERATING SYSTEM(S)
-------------------
First seen on Windows. Failure is also reported on Linux IA32.

FULL JDK VERSION
----------------
java version "1.6.0_03"
Java(TM) SE Runtime Environment (build 1.6.0_03-b05)
Java HotSpot(TM) Client VM (build 1.6.0_03-b05, mixed mode)

Also fails on 5.0.

DESCRIPTION
-----------
When a SocketChannel is used as the base for a socket InputStream and read() is called, it's possible for the read() to throw  a ClosedSelectorException.  The exception is thrown if the read() blocks for a long time and the GC runs (at least once) during the block.       
                                                                         
If the socket has a non-0 timeout then the read() call gets turned into Selector.poll() in the implementation.  If the GC runs and decides that SelectWrapper's phantom reference should be cleaned up then a Cleanup associated with this Seletor Wrapper is going ahead and closing the Temporary Selector associated with the Thread before read could finish.
                                  
The reason we do not see the exception when timeout is not set is because this temporary selector is not obtained in the first place,it just tries to read into the buffer.                                                         
                                                                         
The temporary selector getting lost should not get lost before the testcase is done using it.

RECREATION INSTRUCTIONS
-----------------------

Run the attached testcase with a reduced amount of memory (ie, with -Xmx5m or so) for faster recreate.

Exception stack trace:

Exception in thread "main" java.nio.channels.ClosedSelectorException     
       at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:66)  
       at sun.nio.ch.SelectorImpl.selectNow(SelectorImpl.java:88)        
       at sun.nio.ch.Util.releaseTemporarySelector(Util.java:135)        
       at sun.nio.ch.SocketAdaptor$SocketInputStream.read(SocketAdaptor.java:209)  
       at sun.nio.ch.ChannelInputStream.read(ChannelInputStream.java:86) 
       at java.io.InputStream.read(InputStream.java:85)                  
       at ClosedSelectorTest.main(ClosedSelectorTest.java:63)   

TESTCASE SOURCE
---------------
import java.io.IOException;                                              
import java.net.InetSocketAddress;                                       
import java.net.ServerSocket;                                            
import java.net.Socket;                                                  
import java.nio.channels.ServerSocketChannel;                            
import java.nio.channels.SocketChannel;                                  

public class ClosedSelectorTest {

    public static void main(String[] args) {                          
        try {
            // Create a server socket that will open and  accept                                             
            ServerSocketChannel serverSocketChannel =         
            ServerSocketChannel.open();                                              
            final ServerSocket serverSocket =                 
            serverSocketChannel.socket();                                            
            serverSocket.bind(null);                          
            int localPort = serverSocket.getLocalPort();      
            Thread acceptor = new Thread(new Runnable() {     
                                             public void run() {                       
                                                 try {
                                                     System.out.println("Waiting on serverSocket.accept()");           
                                                     System.out.flush();                                                                             Socket socket =  serverSocket.accept();                                                         System.out.println("Server socket connected");    
                                                     System.out.flush();       
                                                 } catch (IOException e) {
                                                     e.printStackTrace();      
                                                 }
                                             }                                         
                                         });                                               
            acceptor.start();                                 

            // Create a thread that just creates data to cause the                                                                
            // GC to run                                      
            Thread t = new Thread(new Runnable() {            
                                      public void run() {                       
                                          while (true) {
                                              byte[] bytes = new byte[100000];                                                                                         System.gc();              
                                          }                                 
                                      }                                         
                                  });                                               
            t.start();                                        

            // Create a client socket that will connect and read                                                                     
            System.out.println("Connecting to server socket");                                                                
            System.out.flush();                               
            SocketChannel channel = SocketChannel.open(new InetSocketAddress("localhost", localPort));                              
            System.out.println("Connected to server socket"); 

            System.out.flush();                               
            try {
                System.out.println("Reading from socket input stream");                                                          
                System.out.flush();                       
                byte[] buffer = new byte[500];            
                Socket socket = channel.socket();         
                socket.setSoTimeout(1000000);  // The timeout must be set                                                                                                                      
                // to trigger this bug                                 
                socket.getInputStream().read(buffer);     
            } finally {
                try {
                    channel.close();                  
                } catch (IOException e) {
                    e.printStackTrace();              
                }
            }                                                 
        } catch (IOException e) {
            e.printStackTrace();                              
        }
    }           
}

Comments
EVALUATION While the implementation does have a dedicated ThreadLocal variable localSelectorWrapper designed to hold a reference to the selWrapper object to prevent it from being cleaned when the temporary selector is on lease, unfortunately the selWrapper is NOT stored into this place-holder the first time the selector is being created/inited for lease, which caused the problem in scenario described in the description.
27-06-2008

EVALUATION Yes, this is a bug. Should only impact code using the socket adaptor for timed reads. Will fix in jdk7 first.
24-12-2007