Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you close a AsynchronousSocketChannel cleanly?

Tags:

java

nio

nio2

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?

like image 309
Nick Holt Avatar asked Dec 23 '13 15:12

Nick Holt


1 Answers

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);
        }
    }
    
    // ... 

});
like image 55
andbi Avatar answered Oct 07 '22 00:10

andbi