"Future.cancel(true)" cancels the receiver Future and also attempts to
interrupt the executor thread that is running this task. However, not all
threads may be interrupted. An exception are threads that executing one of the
"restart-able blocking system calls" from libnet.
Such threads will ignore the thread interrupt(EINTR) and restart the blocking
system call that was interrupted. See the system calls wrapped in the
BLOCKING_IO_RETURN_INT macro:
https://github.com/openjdk/jdk/blob/master/src/java.base/linux/native/libnet/linux_close.c#L352
The javadoc for "java.util.concurrent.Future.cancel(boolean
mayInterruptIfRunning)" DOES clarify that this method is an "attempt" to cancel
the Future, and that it has no effect if the "task is already completed or
cancelled, or could not be cancelled for *some other reason*". It is also made
clear that "the return value from this method does not necessarily indicate
whether the task is now cancelled". This is good, in the context of this note.
The javadoc also presents "Future.isCancelled()" as a definitive way to test if
a Future was cancelled. However, it does not comment on the thread interruption
attempted by Future.cancel(true). This might lead users to assume that if
"Future.isCancelled()" returns true, the related executor thread was also
successfully interrupted. This assumption would be invalid if the related
executor thread was blocked in one of libnet's restart-able system calls
(connect() could block for a couple of minutes).
I am attaching a test program that reproduces the mentioned behaviour. The
executor thread held a lock and it was assumed that when "Future.isCancelled()"
returned true, the executor had been interrupted and the lock released. In
reality, the lock was held for a longer time and it blocked the main thread
where the invalid assumption was made.
I am curious to know what others think of this matter! Any
help/corrections/opinions will be appreciated. Thank you!
----------
import java.net.*;
import java.util.concurrent.*;
public class ConnectionTest {
private synchronized Socket connect(String host, int port) throws
Exception {
InetSocketAddress address = new InetSocketAddress(host, port);
Socket s = new Socket();
s.connect(address); // HERE: s.connect(address, T), with any T>0, would
resolve the hang!
return s;
}
private Socket connectToMain() throws Exception {
System.out.println("Connecting to main...");
return connect("www.google.com", 81);
}
private Socket connectToAlternate() throws Exception {
System.out.println("Connecting to alternate...");
return connect("www.example.com", 80);
}
public void test() throws Exception {
ExecutorService es = Executors.newFixedThreadPool(1);
Future<Socket> f = es.submit(new Callable<Socket>() {
public Socket call() throws Exception {
return connectToMain();
}
});
try {
f.get(2000, TimeUnit.MILLISECONDS);
System.out.println("Connected to main!");
return;
} catch (TimeoutException e) {
System.out.println("Connection to main timed out, cancelling the
Future with mayInterruptIfRunning = true");
boolean ret = f.cancel(true);
System.out.println("Future.cancel(true) returned " + ret);
}
System.out.println("Is Future canceled? ..." + f.isCancelled());
if (f.isCancelled()) {
connectToAlternate();
System.out.println("Connected to alternate!");
}
}
public static void main(String [] args) throws Exception {
new ConnectionTest().test();
}
}
----------
-Pushkar