My server uses a AsynchronousServerSocketChannel
that listens for client connections using a CompletionHandler
. When a client connection is accepted, the AsynchronousSocketChannel
is read, again using a CompletionHandler
to receive the data with no timeout.
So far so good, my client connects, writes data that is read by the server, which is able to respond sending data back to the client via the same socket.
When my client terminates, it calls AsynchronousSocketChannel.close()
, to close the socket. When this call is made the server is waiting to read data from the socket.
I had expected the call to AsynchronousSocketChannel.close()
on the client to translate into a callback to CompletionHandler.completed
with a read length of -1
on the server, indicating the socket had been closed, however the callback is to CompletionHandler.failed
with the following exception:
java.io.IOException: The specified network name is no longer available.
at sun.nio.ch.Iocp.translateErrorToIOException(Iocp.java:309)
at sun.nio.ch.Iocp.access$700(Iocp.java:46)
at sun.nio.ch.Iocp$EventHandlerTask.run(Iocp.java:399)
at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:744)
How should a client close a socket, so that it is not seen as an error on the server?
Looking at stack traces and the implementation sources, you might notice that the exception is thrown by the internal sun.nio.ch.UnixAsynchronousSocketChannelImpl#finish()
method which checks for pending read/write operations. So the only way to avoid this exception is to prevent new asynchronous read()
and write()
calls at some point, and that should be a part of the application's logic.
I've dealt with this exception quite a lot, and in most cases the root problem was in the completion handler's unconditional "continue listening" calls:
conn.read(src, attachment, new CompletionHandler<Integer, T>() {
@Override
public void completed(Integer result, T attachment) {
// Some business logic here
// Below is the problem - unconditional read() call
conn.read(src, attachment, this);
}
@Override
public void failed(Throwable t, T attachment) {
// Exception handling
}
});
To gracefully close the connection, there should be no unconditional async read/write calls. To achieve that, one might need to send an additional message which would mean that no new async data is expected and it's safe to close the connection. And the correct pseudo-code would look something like this:
conn.read(src, attachment, new CompletionHandler<Integer, T>() {
@Override
public void completed(Integer result, T attachment) {
// Some business logic here
if(continueListening()) {
conn.read(src, attachment, this);
}
}
// ...
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With