Name: rmT116609 Date: 07/21/2004
FULL PRODUCT VERSION :
java version "1.4.2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2-b28)
Java HotSpot(TM) Client VM (build 1.4.2-b28, mixed mode)
FULL OS VERSION :
Microsoft Windows 2000 [Version 5.00.2195]
A DESCRIPTION OF THE PROBLEM :
Select.select(timeout) throws NullPointerException occasionally.
This would appear to be the same bug as Bug ID 4726620.
This bug still happens under j2sdk1.4.2
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
import java.io.*;
import java.net.*;
import java.nio.channels.*;
import java.util.*;
public class Test {
public static void main(String[] args) throws IOException {
final Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false).register(selector, SelectionKey.OP_ACCEPT);
serverChannel.socket().bind(new InetSocketAddress(InetAddress.getLocalHost(), 3333));
startClientThread();
while (true) {
selector.select(1000); // NullPointerException in code called from this line
synchronized (Test.class) {
Iterator keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = (SelectionKey)keyIterator.next();
keyIterator.remove();
if (key.isAcceptable()) {
final SelectionKey clientKey = serverChannel.accept().configureBlocking(false).register(selector, 0);
new Thread() {
public void run() {
try {
Thread.sleep(10000);
}
catch (InterruptedException ex) {
}
synchronized (Test.class) {
clientKey.interestOps(SelectionKey.OP_WRITE);
}
}
}.start();
}
if (key.isWritable()) {
key.interestOps(0);
}
}
}
}
}
private static void startClientThread() throws IOException {
new Thread() {
public void run() {
try {
Selector selector = Selector.open();
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
if (channel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 3333))) {
channel.register(selector, SelectionKey.OP_READ);
}
else {
channel.register(selector, SelectionKey.OP_CONNECT);
}
while (true) {
selector.select();
synchronized (selector) {
Iterator keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = (SelectionKey)keyIterator.next();
keyIterator.remove();
if (key.isConnectable()) {
if (channel.finishConnect()) {
key.interestOps(SelectionKey.OP_READ);
}
}
if (key.isReadable()) {
key.interestOps(0);
}
}
}
}
}
catch (IOException ex) {
System.exit(1);
}
}
}.start();
}
}
As it only happens occasionaly this code might not reproduce the problem.
Bugs 4726620, 4684585 appear to have the same problem, and LotsOfChannels.java from bug 4685526 appears to reproduce this problem as well.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
no exception
ACTUAL -
NullPointerException
ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.lang.NullPointerException
at sun.nio.ch.WindowsSelectorImpl$SubSelector.processFDSet(WindowsSelectorImpl.java:309)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.processSelectedKeys(WindowsSelectorImpl.java:282)
at sun.nio.ch.WindowsSelectorImpl$SubSelector.access$2600(WindowsSelectorImpl.java:245)
at sun.nio.ch.WindowsSelectorImpl.updateSelectedKeys(WindowsSelectorImpl.java:427)
at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:142)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:59)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:70)
REPRODUCIBILITY :
This bug can be reproduced occasionally.
(Incident Review ID: 198556)
======================================================================
Suggested fix provided by java.net member mernst
A DESCRIPTION OF THE FIX :
This is a patch to fix two bugs in the NIO selector implementation that occur with concurrent selection and closing of channels. This patch is against Mustang b66.
5076772: NullPointerException
at sun.nio.ch.WindowsSelectorImpl$FdMap.remove(WindowsSelectorImpl.java:76)
at sun.nio.ch.WindowsSelectorImpl$FdMap.access$3100(WindowsSelectorImpl.java:66)
at sun.nio.ch.WindowsSelectorImpl.implDereg(WindowsSelectorImpl.java:524)
at sun.nio.ch.SelectorImpl.processDeregisterQueue(SelectorImpl.java:131)
at sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:149)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:69)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:80)
is due to a missing null-check in FdMap.
Patch
diff -uwr /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/windows/classes/sun/nio/ch/WindowsSelectorImpl.java src/windows/classes/sun/nio/ch/WindowsSelectorImpl.java
--- /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/windows/classes/sun/nio/ch/WindowsSelectorImpl.java 2006-01-10 22:41:23.599350400 +0100
+++ src/windows/classes/sun/nio/ch/WindowsSelectorImpl.java 2006-01-14 13:18:18.638014400 +0100
@@ -73,7 +73,7 @@
private MapEntry remove(SelectionKeyImpl ski) {
Integer fd = new Integer(ski.channel.getFDVal());
MapEntry x = get(fd);
- if (x.ski.channel == ski.channel)
+ if (x != null && x.ski.channel == ski.channel)
return remove(fd);
return null;
}
4729342 Selector.select() throws CancelledKeyException seems to not have been completely fixed. While the nio* operations in SelectionKeyImpl provide access to the interest and ready ops without validity checking, they aren't used consistently in the various implementations of SelChImpl#translateAndUpdateReadyOps. The patch replaces all calls to #readyOps by #nioReadyOps.
diff -uwr /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/windows/classes/sun/nio/ch/SinkChannelImpl.java src/windows/classes/sun/nio/ch/SinkChannelImpl.java
--- /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/windows/classes/sun/nio/ch/SinkChannelImpl.java 2006-01-10 22:41:23.589336000 +0100
+++ src/windows/classes/sun/nio/ch/SinkChannelImpl.java 2006-01-14 13:27:10.422683200 +0100
@@ -78,7 +78,7 @@
}
public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
- return translateReadyOps(ops, sk.readyOps(), sk);
+ return translateReadyOps(ops, sk.nioReadyOps(), sk);
}
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
diff -uwr /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/windows/classes/sun/nio/ch/SourceChannelImpl.java src/windows/classes/sun/nio/ch/SourceChannelImpl.java
--- /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/windows/classes/sun/nio/ch/SourceChannelImpl.java 2006-01-10 22:41:23.599350400 +0100
+++ src/windows/classes/sun/nio/ch/SourceChannelImpl.java 2006-01-14 13:27:10.462740800 +0100
@@ -77,7 +77,7 @@
}
public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
- return translateReadyOps(ops, sk.readyOps(), sk);
+ return translateReadyOps(ops, sk.nioReadyOps(), sk);
}
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
diff -uwr /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/share/classes/sun/nio/ch/DatagramChannelImpl.java src/share/classes/sun/nio/ch/DatagramChannelImpl.java
--- /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/share/classes/sun/nio/ch/DatagramChannelImpl.java 2006-01-10 22:39:38.468179200 +0100
+++ src/share/classes/sun/nio/ch/DatagramChannelImpl.java 2006-01-14 13:27:10.432697600 +0100
@@ -633,7 +633,7 @@
}
public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
- return translateReadyOps(ops, sk.readyOps(), sk);
+ return translateReadyOps(ops, sk.nioReadyOps(), sk);
}
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
diff -uwr /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/share/classes/sun/nio/ch/ServerSocketChannelImpl.java src/share/classes/sun/nio/ch/ServerSocketChannelImpl.java
--- /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/share/classes/sun/nio/ch/ServerSocketChannelImpl.java 2006-01-10 22:39:38.558308800 +0100
+++ src/share/classes/sun/nio/ch/ServerSocketChannelImpl.java 2006-01-14 13:27:10.452726400 +0100
@@ -253,7 +253,7 @@
}
public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
- return translateReadyOps(ops, sk.readyOps(), sk);
+ return translateReadyOps(ops, sk.nioReadyOps(), sk);
}
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
diff -uwr /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/share/classes/sun/nio/ch/SocketChannelImpl.java src/share/classes/sun/nio/ch/SocketChannelImpl.java
--- /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/share/classes/sun/nio/ch/SocketChannelImpl.java 2006-01-10 22:39:38.568323200 +0100
+++ src/share/classes/sun/nio/ch/SocketChannelImpl.java 2006-01-14 13:27:10.462740800 +0100
@@ -716,7 +716,7 @@
}
public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
- return translateReadyOps(ops, sk.readyOps(), sk);
+ return translateReadyOps(ops, sk.nioReadyOps(), sk);
}
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
diff -uwr /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/solaris/classes/sun/nio/ch/SinkChannelImpl.java src/solaris/classes/sun/nio/ch/SinkChannelImpl.java
--- /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/solaris/classes/sun/nio/ch/SinkChannelImpl.java 2006-01-10 22:41:13.905411200 +0100
+++ src/solaris/classes/sun/nio/ch/SinkChannelImpl.java 2006-01-14 15:40:22.164243200 +0100
@@ -118,7 +118,7 @@
}
public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
- return translateReadyOps(ops, sk.readyOps(), sk);
+ return translateReadyOps(ops, sk.nioReadyOps(), sk);
}
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
diff -uwr /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/solaris/classes/sun/nio/ch/SourceChannelImpl.java src/solaris/classes/sun/nio/ch/SourceChannelImpl.java
--- /home/Matthias/c/Program Files/Java/jdk1.6.0b66/j2se/src/solaris/classes/sun/nio/ch/SourceChannelImpl.java 2006-01-10 22:41:13.905411200 +0100
+++ src/solaris/classes/sun/nio/ch/SourceChannelImpl.java 2006-01-14 15:40:22.154228800 +0100
@@ -118,7 +118,7 @@
}
public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
- return translateReadyOps(ops, sk.readyOps(), sk);
+ return translateReadyOps(ops, sk.nioReadyOps(), sk);
}
public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
JUnit TESTCASE :
The attached test case is a standalone server that opens a server socket, and repeatedly connects to itself and closes the connection again in five concurrent clients. It just needs to be compiled and run.
package test;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SelectorStressTest {
static Selector selector;
static ExecutorService pool = Executors.newCachedThreadPool();
static SocketAddress address;
static class ChannelToSelect {
static ConcurrentLinkedQueue<ChannelToSelect> queue = new ConcurrentLinkedQueue<ChannelToSelect>();
public static void add(SelectableChannel channel, Protocol protocol) {
ChannelToSelect ct = new ChannelToSelect();
ct.channel = channel;
ct.protocol = protocol;
queue.add(ct);
}
public static ChannelToSelect poll() {
return queue.poll();
}
SelectableChannel channel;
Protocol protocol;
}
interface Protocol {
public abstract int interest();
public abstract Protocol run(Channel channel) throws IOException;
}
static Protocol ACCEPTING_SERVER = new Protocol() {
public int interest() {
return SelectionKey.OP_ACCEPT;
}
public Protocol run(Channel channel) throws IOException {
SocketChannel connection;
while((connection = ((ServerSocketChannel) channel).accept()) != null) {
connection.configureBlocking(false);
ChannelToSelect.add(connection, CONNECTED_SERVER);
}
return ACCEPTING_SERVER;
}
};
static Protocol CONNECTED_SERVER = new Protocol() {
public int interest() {
return SelectionKey.OP_WRITE;
}
public Protocol run(Channel channel) {
return null;
}
};
static Protocol CONNECTING_CLIENT = new Protocol() {
public int interest() {
return SelectionKey.OP_CONNECT;
}
public Protocol run(Channel channel) throws IOException {
return (((SocketChannel)channel).finishConnect()) ? CONNECTED_CLIENT : CONNECTING_CLIENT;
}
};
static Protocol CONNECTED_CLIENT = new Protocol() {
public int interest() {
return SelectionKey.OP_READ;
}
public Protocol run(Channel channel) throws IOException {
int bytes = ((SocketChannel) channel).read(ByteBuffer.allocate(1));
if(bytes == -1) {
startClient();
return null;
}
return CONNECTED_CLIENT;
}
};
private static void startClient() throws IOException {
SocketChannel client = SocketChannel.open();
client.configureBlocking(false);
client.connect(address);
ChannelToSelect.add(client, CONNECTING_CLIENT);
}
private static SocketAddress startServer() throws IOException {
final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(0));
ChannelToSelect.add(serverSocketChannel, ACCEPTING_SERVER);
return serverSocketChannel.socket().getLocalSocketAddress();
}
public static void main(String[] args) throws IOException {
selector = Selector.open();
address = startServer();
startClient();
startClient();
startClient();
startClient();
startClient();
while(true) {
registerAndUpdateChannels();
select();
runSelected();
}
}
private static void runSelected() {
for (final SelectionKey key : selector.selectedKeys()) {
key.interestOps(0);
pool.execute(new Runnable() {
public void run() {
Protocol p = (Protocol) key.attachment();
Protocol next;
try {
next = p.run(key.channel());
} catch(IOException io) {
next = null;
}
if(next == null) {
try {
key.channel().close();
} catch (IOException e) {
}
} else {
ChannelToSelect.add(key.channel(), next);
}
selector.wakeup();
}
});
}
selector.selectedKeys().clear();
}
private static void select() throws IOException {
selector.select();
}
private static void registerAndUpdateChannels() throws ClosedChannelException {
ChannelToSelect channelToSelect;
while((channelToSelect = ChannelToSelect.poll()) != null)
channelToSelect.channel.register(selector, channelToSelect.protocol.interest(), channelToSelect.protocol);
}
}
FIX FOR BUG NUMBER:
4729342 5076772