11

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?

Nick Holt
  • 33,455
  • 4
  • 52
  • 58

2 Answers2

3

The documentation on close says that it causes AsynchronousCloseException or ClosedChannelException on the other side.

To cause completed(-1) the client should call shutdownInput.

However, I would treat AsynchronousCloseException and ClosedChannelException as normal shutdown, along with completed(-1).

Alexei Kaigorodov
  • 13,189
  • 1
  • 21
  • 38
  • 1
    It doesn't say anything about 'the other side'. Both those exceptions should be treated as programming errors, not 'normal shutdown'. – user207421 Dec 23 '13 at 22:22
  • I've added in the call to `AsynchronousSocketChannel.shutdownInput` on the client-side, which does indeed cause a callback to `CompletionHandler.completed` with a read length of -1 on the server. – Nick Holt Dec 24 '13 at 10:08
  • It's a shame the client-side gets a `AsynchronousCloseException` though at least it's not the very general `IOException` and because the client initiated the close, I can add a `closing` flag in order to ignore this error. – Nick Holt Dec 24 '13 at 10:10
  • @EJP - do you know of a way to avoid the `AsynchronousCloseException` altogether? – Nick Holt Dec 24 '13 at 10:11
  • I was stumped by this behavior as well... Why would you throw an exception if a client *closes* the connection using a valid method? I want to know. That's like your car alarm triggering every time you close the car door... – niken Jun 12 '15 at 15:13
  • The thing is, the exception is translated to java.io.IOException... Which is kind of ambiguous. This has "not well thought out" stink all over it... How the hell am I supposed to know how bad this io Exception is? Do i need to wrap it in extra logic because it was sent to failed method in the handler? Makes me feel dirty just thinking about it and not in a good way. – niken Jun 12 '15 at 15:29
  • In my code, I have actually identified several different ways we're told about closed channels. 1) Trying to initiate operation on a closed channel immediately completes with `ClosedChannelException`. 2) The specialized subclass `AsynchronousCloseException` is when a pending operation aborts. 3) Trying to initiate when the associated group is closed will get you a `ShutdownChannelGroupException`. 4) Any IOException which has _a cause_ set to a `ShutdownChannelGroupException` is a rare breed but means the operation initialization was accepted but failed afterwards because the group shutdown. – Martin Andersson Nov 21 '20 at 17:29
  • What's the alternative? I don't see one. How can a close "normally" complete a channel operation? The task of driving a car either gets you there or an exception happened. We could have more result sentinel values than just "-1" but this is a bit smelly and I think will lead down the rabbit hole. It's probably not very coherent with many other APIs in the runtime either. Also, using exceptions we have only one unified way of dealing with failures, and we can even learn about new future scenarios/reasons in the future why a channel was closed without being limited to sentinel values alone. – Martin Andersson Nov 21 '20 at 17:34
  • Anyways, in my case the "other side" was a Java client which only sometimes threw a "java.net.SocketException: Connection reset" on read(). I figured as much the server was terminating the connection badly before the client was done. Anyways, solved the problem by changing the server's close procedure from doing only `close()` to `shutdownInput()` + `shutdownOutput()` + `close()`. Thank you for sharing guys! =) – Martin Andersson Nov 21 '20 at 17:34
  • Adding `shutdownInput()` and `shutdownOutput()` won't have fixed it. It doesn't change anything if the close follows immediately. You must have done something else as well. – user207421 Apr 21 '22 at 02:23
1

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

});
andbi
  • 4,426
  • 5
  • 45
  • 70
  • No need to call `close()`? – Jin Kwon Aug 20 '23 at 16:04
  • 1
    @JinKwon the idea is to break the handler's recursive reads before closing the connection, thus avoiding the `AsynchronousCloseException`. That's of course if your app's logic allows for it. If not, one can close the connection and handle the exception in the `failed` block – andbi Aug 20 '23 at 17:59