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)
======================================================================