Task cancellation is challenging in Java (as it is in many languages/runtimes). The most widely supported cancellation primitive in Java is thread interruption, even if the API for thread interruption has some peculiar ergonomics.
Thread interruption has deep support across the Java standard library, but nonetheless the behaviour of the standard libary when dealing with thread interruption can be surprising.
Let’s take a look at the Javadoc for SocketChannel#read(ByteBuffer):
Throws:
ClosedByInterruptException - If another thread interrupts the current thread while the read operation is in progress, thereby closing the channel and setting the current thread’s interrupt status
The name of the exception and the explanatory text, make things very clear: when a thread is interrupted whilst blocking inside the read()
method, the associated channel will be closed!
This is suprising to me, why should interrupting a thread whilst blocked on a socket read, require the socket to be closed?
Let’s look at a real world scenario. Say I have an application that performs some lengthy database operation.
We want to allow the user cancel the operation, if they decide they don’t want to wait for the operation to complete.
In this scenario, the application will block reading from the socket until the database operation completes.
If we were to use thread interruption to attempt to cancel the operation, then the database socket will be closed! All database session state (uncomitted transactions etc.) will be lost!
But why is this the case? Why does thread interruption have to force the socket to be closed? This behaviour would seem to make thread interruption practially unusable for a common set of use cases?
The best explanation I have found is in this Hacker News discussion thread:
Not every OS supports cancel-able file IO, but they all support closing the file outright, which will get this behavior of canceling all IO against the file.
A link in the above discussion thread says something similar:
NIO’s designers chose to shut down a channel when a blocked thread is interrupted because they couldn’t find a way to reliably handle interrupted I/O operations in the same manner across operating systems. The only way to guarantee deterministic behavior was to shut down the channel.
In other words, this behaviour is a least common denominator type decision. Ideally cancellation without closure would have been supported, but due to the variances across operating systems it appears there was no way to achieve this and we ended up with this ’least bad thing we can do’ style behaviour.
If you have a stateful network connection (i.e. the server and/or client associate state with the specific connection instance), then you cannot use thread interruption in Java to cancel operations. If the interruption happens to occur whilst blocking for a read from a socket, then the socket will be closed.
If your connection is stateless, then this concern does not apply. For a stateless connection, you don’t care if a connection is closed, the next operation can restablish the connection, without any state loss.