United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-4469394 : (so) SocketChannels registered with OP_WRITE only release selector once (win)

Details
Type:
Bug
Submit Date:
2001-06-13
Status:
Resolved
Updated Date:
2002-06-12
Project Name:
JDK
Resolved Date:
2002-04-25
Component:
core-libs
OS:
solaris_8,windows_nt,solaris_7
Sub-Component:
java.nio
CPU:
x86,sparc,generic
Priority:
P2
Resolution:
Fixed
Affected Versions:
1.4.0,1.4.1
Fixed Versions:
1.4.0_02 (02)

Related Reports
Backport:
Duplicate:
Relates:

Sub Tasks

Description

Name: bsC130419			Date: 06/13/2001


java version "1.4.0-beta"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-beta-b65)
Java HotSpot(TM) Client VM (build 1.4.0-beta-b65, mixed mode)

Non-blocking SocketChannels registered with a Selector and including the
SelectionKey.OP_WRITE operation code will not release Selector.select() more
than once. The first time selector.select() is called and the outgoing socket
is available for writing, the select method returns as expected, and the
selector.selectedKeys Set includes the SelectionKey for the SocketChannel with
readyOps including OP_WRITE. However, after removing the SelectionKey from the
selectedKeys Set, any later call to selector.select() will block indefinitely.

The following simple program demonstrates the problem. The program creates a
ServerSocketChannel in non-blocking mode using an InetSocketAddress built
from "localhost", port 1110. The program registers the ServerSocketChannel with
a Selector with operations OP_ACCEPT. selector.select() then blocks until a
client (e.g., a Telnet client) connects to localhost 1110.

Once a client is accepted, a SocketChannel for that client is configured in non-
blocking mode. The new SocketChannel is registered with the same Selector with
operation OP_WRITE. The next selector.select() call returns also most
immedaitely with a selectedKey set including the SelectionKey for the new
SocketChannel -- indicating the socket can be written to. The program writes a
simple, 7 byte buffer to the socket (using SocketChannel.write(ByteBuffer)),
and removes the SelectionKey from the selector's selectedKey Set. The next call
to selector.select() never returns, even though the outgoing client socket
should be again available for writing almost instantaneously.

Note that the erroneous behavior occurs whether or not the test program
actually writes any bytes to the SocketChannel. In the final block of the main
() method's loop below, simply comment out the call to SocketChannel.write()
(actual line is "sc.write(bb);"), and the same problem still occurs.

// Simple test program to exercise writing multiple times
// to a SocketChannel using non-blocking I/O in the java.nio.*
// packages.

import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;

public class WriteTest
{
    public static void main(String[] args) throws Exception
    {
	//...contents of this byte buffer will be written to
	//   all connected clients repeatedly until they disconnect...
	ByteBuffer bb = ByteBuffer.wrap(new byte[] {
	    (byte)'B', (byte)'r', (byte)'i', (byte)'a', (byte)'n',
	    (byte)'\r', (byte)'\n'
	});

	//...ServerSocketChannel to receive new client connections on
	//   "localhost" interface, port 1110
	ServerSocketChannel ssc = ServerSocketChannel.open();
	ssc.configureBlocking(false);
	ServerSocket ss = ssc.socket();
	ss.bind(new InetSocketAddress(InetAddress.getByName("localhost"),
1110));

	//...single Selector used to receive non-blocking I/O events...
	Selector sel = Selector.open();

	//...register the ServerSocketChannel with the Selector...
	SelectionKey keyAccept = ssc.register(sel, ssc.validOps());

	while(true)
	{
	    if(sel.select() == 0)
		continue;

	    Set selectedKeys = sel.selectedKeys();

	    //...if a new client is trying to copnnect, accept the
	    //   connection then loop...
	    if(selectedKeys.contains(keyAccept))
	    {
		Socket sock = ssc.accept();
		SocketChannel sc = sock.getChannel();
		sc.configureBlocking(false);
		SelectionKey k = sc.register(sel, SelectionKey.OP_WRITE);
		k.attach(sc);
		selectedKeys.remove(keyAccept);
		continue;
	    }

	    //...no new client connections, meaning that at least one of
	    //   the currently connected clients can receive more bytes...
	    Iterator iter = selectedKeys.iterator();
	    while(iter.hasNext())
	    {
		SelectionKey k = (SelectionKey)iter.next();
		SocketChannel sc = (SocketChannel)k.attachment();
		sc.write(bb); // Problem persists even after
			      // commenting this line out.
		bb.rewind();
		selectedKeys.remove(k);
	    }
	}
    }
}

The following modified version of the above test program attempts to work
around the problem by creating a new Selector object after each write operation
and registering all client SocketChannels with the new Selector (instead of re-
using the same Selector in each iteration of the outer infinite loop). This
does not fix the problem, however, indicating to me that the problem is in the
native layer (comment elided for brevity). (Note that this version of the test
program does not work with multiple simultaneous clients -- but it still
demonstrates the problem which is the important part).

import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;

public class WriteTest
{
    public static void main(String[] args) throws Exception
    {
	ByteBuffer bb = ByteBuffer.wrap(new byte[] {
	    (byte)'B', (byte)'r', (byte)'i', (byte)'a', (byte)'n',
	    (byte)'\r', (byte)'\n'
	});

	ServerSocketChannel ssc = ServerSocketChannel.open();
	ssc.configureBlocking(false);
	ServerSocket ss = ssc.socket();
	ss.bind(new InetSocketAddress(InetAddress.getByName("localhost"),
1110));

	Selector sel = Selector.open();

	SelectionKey keyAccept = ssc.register(sel, ssc.validOps());

	while(true)
	{
	    if(sel.select() == 0)
		continue;

	    Set selectedKeys = sel.selectedKeys();

	    if(selectedKeys.contains(keyAccept))
	    {
		Socket sock = ssc.accept();
		SocketChannel sc = sock.getChannel();
		sc.configureBlocking(false);
		SelectionKey k = sc.register(sel, SelectionKey.OP_WRITE);
		k.attach(sc);
		selectedKeys.remove(keyAccept);
		continue;
	    }

	    //...this section modified from the previous example to open
	    //   a new Selector and use the new one in the next iteration
	    //   through the output (infinite) while loop...
	    Iterator iter = selectedKeys.iterator();
	    Selector sel2 = Selector.open();
	    while(iter.hasNext())
	    {
		SelectionKey k = (SelectionKey)iter.next();
		SocketChannel sc = (SocketChannel)k.attachment();
		sc.write(bb);
		bb.rewind();
		selectedKeys.remove(k);

		k = sc.register(sel2, SelectionKey.OP_WRITE);
		k.attach(sc);
	    }
	    ssc.register(sel2, SelectionKey.OP_ACCEPT);
	    sel.close();
	    sel = sel2;
	}
    }
}


Brian Maso
(Review ID: 126007) 
======================================================================

                                    

Comments
EVALUATION

This bug is due to the fact that the NIO specification requires level-triggered
readiness notifications (which is, conveniently, how they work in Solaris and
Linux) but Windows only provides edge-triggered notifications.  A fix is in
progress and is slated for the 1.4.1 release.

-- ###@###.### 2002/3/23
                                     
189-10-03 0
PUBLIC COMMENTS

A new version of Selector for Windows solves this problem
                                     
2004-06-10
SUGGESTED FIX

###@###.### 2002-04-02
For 1.4.0_02, the file which has to be changed is in
j2se/src/win32/native/sun/nio/ch/PollArrayWrapper.c
sccs diffs -C PollArrayWrapper.c 

------- PollArrayWrapper.c -------
*** /tmp/d7Rai8Q        Tue Apr  2 20:18:35 2002
--- PollArrayWrapper.c  Wed Mar  6 19:39:20 2002
***************
*** 109,114 ****
--- 109,167 ----
      WSAEVENT *events = (WSAEVENT *) jlong_to_ptr(eventAddress);
      int err;
      WSANETWORKEVENTS ne;
+     int i, lastError;
+     FD_SET readfds, writefds, exceptfds;
+     const static struct timeval zerotime = {0, 0};
+     if (numfds > 1) { 
+         FD_ZERO(&readfds);
+         FD_ZERO(&writefds);
+         FD_ZERO(&exceptfds);
+         for (i = 0; i < numfds -1; i++) {
+             if (fds[i].events & POLLIN) {
+                 FD_SET(fds[i].fd, &readfds);
+             }
+             if (fds[i].events & POLLOUT) {
+                 FD_SET(fds[i].fd, &writefds);         
+             }
+             FD_SET(fds[i].fd, &exceptfds);
+         }
+         while ((result = WSAWaitForMultipleEvents(numfds - 1, events + 1, 
+                             FALSE, 0, FALSE)) != WSA_WAIT_TIMEOUT) {
+             if (result == WSA_WAIT_FAILED) {
+                 lastError = WSAGetLastError();
+                 JNU_ThrowIOExceptionWithLastError(env, 
+                                     "In Poll0 reset events:WSA_WAIT_FAILED");
+                 return lastError;
+             }
+             WSAResetEvent(events[result - WSA_WAIT_EVENT_0 + 1]);  
+         };
+ 
+         if ((result = select(numfds - 1 , &readfds, &writefds, &exceptfds, 
+                                                     &zerotime)) != 0){
+             if (result == SOCKET_ERROR) {
+                 lastError = WSAGetLastError();
+                 JNU_ThrowIOExceptionWithLastError(env, 
+                         "Poll0 before select:WSA_WAIT_FAILED");
+                 return lastError;
+             }
+             timeout = 0;
+ 
+             for (i = 0 ; i < numfds - 1; i++) {
+ 
+               if ((fds[i].events & POLLIN) && 
+                           FD_ISSET(fds[i].fd, &readfds)) {
+                     fds[i].revents |= POLLIN;
+               }
+               if ((fds[i].events & POLLOUT) && FD_ISSET(fds[i].fd, &writefds)) {
+                                 fds[i].revents |= POLLOUT;
+               }
+               if (FD_ISSET(fds[i].fd, &exceptfds)){ // error
+                    fds[i].revents = fds[i].events;
+               }
+             }
+         }
+     }      
+ 
   
      if (timeout < 0) {
          timeout = WSA_INFINITE;
                                     
2004-06-11
WORK AROUND



Name: bsC130419			Date: 06/13/2001


No known workaround that I can find other than not using non-blocking I/O for
socket writes.
======================================================================
                                     
2004-06-11
CONVERTED DATA

BugTraq+ Release Management Values

COMMIT TO FIX:
1.4.0_02
hopper

FIXED IN:
1.4.0_02
hopper

INTEGRATED IN:
1.4.0_02
hopper

VERIFIED IN:
hopper


                                     
2004-06-14



Hardware and Software, Engineered to Work Together