JDK-4854354 : SocketChannel.write throws IOexception in 'non-exceptional' situation
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 1.4.1
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2003-04-25
  • Updated: 2003-05-16
  • Resolved: 2003-05-16
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
5.0 tigerFixed
Description

Name: nt126004			Date: 04/25/2003


FULL PRODUCT VERSION :
java version "1.4.1_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_01-b01)
Java HotSpot(TM) Client VM (build 1.4.1_01-b01, mixed mode)


FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]

A DESCRIPTION OF THE PROBLEM :
Under high load SocketChannel.write sometimes throws
an exception:

java.io.IOException: A non-blocking socket operation could
not be completed immediately

I was running sender and receiver on the same machine.
The sender was sending a lot of data and the receiver
couldn't keep up. Quite soon this exception happened.

The JavaDoc for SocketChannel.write says that the return
value is "The number of bytes written, possibly zero."

My educated guess is that SocketDispatcher.writev0 calls
WSASend. WSASend returns SOCKET_ERROR and then no check
is made to see if the error code is WSAEWOULDBLOCK.
Instead, it just throws an IOException.

Yes, I know this is in the SocketChannel.write API, but that 
doesn't mean it's right. I realize that I should have made 
this more clear in my report.

Exceptions should, of course, only be used for exceptional
situations. The fact that writing on a non-blocking socket 
could not be completed immediately is not an error - in fact, 
it's a very important, integral part of the non-blocking design. 
Therefore it is critical that the write() method not throw an 
exception. It shall just return the number of bytes it managed 
to write, even if that number is zero. This allows the application 
to use a Selector to wait until the channel is again ready to 
accept more data. 

This behavior is completely analogous to SocketChannel.read(),
which handles it correctly. It returns zero read bytes instead
of throwing an exception if there's no data to read.

So, the bug is that a non-blocking SocketChannel.write throws 
an exception instead of returning zero written bytes. The
performance hit involved in throwing an exception destroys
any chance of creating a high-performance network application.

This really is very important. Scout's honor. This bug is
preventing us from migrating our products to NIO.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Create a non-blocking channel.
2. Send data faster than the receiver can handle.
3. Wait for exception.

EXPECTED VERSUS ACTUAL BEHAVIOR :
The expected behavior:
SocketChannel.write should return 0 when it can't write
data to the socket.

Actual behavior:
SocketChannel.write throws an IOException.


ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.io.IOException: A non-blocking socket operation could not be completed
immediately
        at sun.nio.ch.SocketDispatcher.writev0(Native Method)
        at sun.nio.ch.SocketDispatcher.writev(SocketDispatcher.java:37)
        at sun.nio.ch.IOUtil.write(IOUtil.java:160)
        at sun.nio.ch.SocketChannelImpl.write0(SocketChannelImpl.java:329)
        at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:349)
        at java.nio.channels.SocketChannel.write(SocketChannel.java:360)



REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
while (true) {
   long bytesWritten = 0;
   try {
      _socketChannel.socket().getOutputStream().write(1);
      bytesWritten = _socketChannel.write(buffers);
   } catch (IOException e) {
      e.printStackTrace();
   }
   bytesToWrite -= bytesWritten;
   if (bytesToWrite == 0) {
      // All done.
      return;
   }
   // Wait and try again
   try {
      Thread.sleep(1);
   } catch (InterruptedException e) {
      e.printStackTrace();
   }
}

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

CUSTOMER WORKAROUND :
Catch the incorrect exception and pretends that write
returned 0. It works but the performance is terrible.
(Review ID: 179144) 
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: tiger FIXED IN: tiger INTEGRATED IN: tiger tiger-b07
14-06-2004

EVALUATION Not for mantis. ###@###.### 2003-04-29 The write code does check for WSAEWOULDBLOCK and returns 0 in that case, but the writev does not. Will fix in tiger. ###@###.### 2003-04-29
29-04-2003