JDK-6525190 : (se) Selector implementation violates spec by always returning (lnx)
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 6
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: linux
  • CPU: x86
  • Submitted: 2007-02-14
  • Updated: 2010-04-04
  • Resolved: 2007-02-15
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_01"
Java(TM) SE Runtime Environment (build 1.6.0_01-b04)
Java HotSpot(TM) Client VM (build 1.6.0_01-b04, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Linux dhcppc0 2.6.18-3-686 #1 SMP Mon Dec 4 16:41:14 UTC 2006 i686 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
The NIO Selector implementation on Linux contains a bug that makes the Selector.select() method to behave against its specification (the method always returns, despite no channel was selected, wakeup() was not invoked and the current thread was not interrupted). This consumes all available CPU resources and makes running NIO server applications on Linux impossible.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile and run the following test program on Linux. Notice that select() always returns; leading to a very high number of selection rounds.
Repeat the test on Windows or Solaris and notice that select() works as specified.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I expect the Selector.select() method to work as specified on Linux.
ACTUAL -
The Selector.select() method always returns, despite no channel was selected, wakeup() was not invoked and the current thread was not interrupted.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Set;
 
/**
 * Tests a strange behaviour of the Selector class:
 * If a peer shuts down its socket and we remove the read interest and later write
 * again at the channel the selector goes into a wild spin...
 * @author ###@###.###
 */
public class SelectorTest {
    
    /**
     * Creates a new instance of SelectorTest
     * @throws java.lang.Exception
     */
    public SelectorTest() throws Exception {
        
        SelectorThread selectorThread = new SelectorThread();
        selectorThread.start();
        
        final int PORT = 11111;
        TestServer testServer = new TestServer(PORT);
        testServer.start();
        
        SocketAddress serverAddress = new InetSocketAddress("localhost", PORT);
        SocketChannel socketChannel = SocketChannel.open(serverAddress);
        socketChannel.configureBlocking(false);
        selectorThread.registerChannel(socketChannel);
        Thread.sleep(1000);
        
        testServer.close();
        Thread.sleep(1000);
        
        System.out.println("write some data to the channel...");
        ByteBuffer buffer = ByteBuffer.allocate(100);
        socketChannel.write(buffer);
        Thread.sleep(100);
        
        selectorThread.stopSelection();
        Thread.sleep(1000);
        
        int counter = selectorThread.getCounter();
        if (counter < 5) {
            System.out.println("Good. Selector works as advertised.");
        } else {
            System.out.println("Bad. Selector was spinning " + counter + " rounds.");
        }
        System.exit(0);
    }
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        try {
            new SelectorTest();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private class SelectorThread extends Thread {
        
        private final Selector selector;
        private int counter;
        private boolean running;
        
        public SelectorThread() throws IOException {
            selector = Selector.open();
            running = true;
        }
        
        public void run() {
            try {
                while (running) {
                    synchronized(this){}; // guard
                    int updatedKeys = selector.select();
                    Set<SelectionKey> keys = selector.selectedKeys();
                    System.out.println(updatedKeys + " keys updated\n" +
                            keys.size() + " keys in selector's selected key set");
                    for (SelectionKey key : keys) {
                        SocketChannel channel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(100);
                        int bytesRead = channel.read(buffer);
                        if (bytesRead == -1) {
                            System.out.println("removing read interest");
                            key.interestOps(key.interestOps() & ~SelectionKey.OP_READ);
                        }
                    }
                    keys.clear();
                    counter++;
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        
        public synchronized void registerChannel(SocketChannel channel)
        throws ClosedChannelException {
            System.out.println("registering channel");
            selector.wakeup();
            SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
        }
        
        public int getCounter() {
            return counter;
        }
        
        private void stopSelection() {
            running = false;
        }
    }
    
    private class TestServer extends Thread {
        private final int port;
        private ServerSocket serverSocket;
        private Socket socket;
        public TestServer(int port) throws IOException {
            this.port = port;
            serverSocket = new ServerSocket(port);
        }
        public void run() {
            try {
                while (true) {
                    socket = serverSocket.accept();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        public void close() throws IOException {
            System.out.println("closing server socket...");
            socket.close();
        }
    }
}

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

CUSTOMER SUBMITTED WORKAROUND :
See the following link for a discussion about a possible workaround: http://forum.java.sun.com/thread.jspa?threadID=5135128

Comments
EVALUATION Yes, we have a problem on Linux when a file descriptor is registered with the polling mechanism (either poll or epoll) with an event mask of 0 and the connection is closed. The issue is tricky to resolve and is tracked as 6403933.
2007-02-15