In Java, all the standard blocking methods can be interrupted by calling Thread.interrupt()
, but what if we have Java bindings wrapping a native library which does its own I/O? How then should the native code hook into the Thread and respond to calls to Thread.interrupt()
?
Example code
For a full writeup, including runnable sample code, see https://github.com/NWilson/javaInterruptHook.
How does Thread.interrupt()
work?
In Sun's JRE (and OpenJDK), interrupt()
is able to wake up a few low-level operations itself, such as waiting on a monitor (Object.wait()
), and provides an internal hook for higher-level code to be notified of the interruption. This is provided through JavaLangAccess.blockedOn()
, which can actually be called directly by your code through sun.misc.SharedSecrets
, if you're willing to use Sun-specific implementation details.
As far as I can tell, there's only one public, documented way to register for that notification, which is using java.nio.channels.spi.AbstractSelector
. This is a partial implementation of Selector
which does the dirty work for you of hooking up the JavaLangAccess.blockedOn()
notification to the selector's wakeup()
method.
How to implement this all
Implement AbstractSelector
to make your own selector; most of the methods are not relevant so just ignore them and put in stubs to shut up the compiler. When you are about to enter your JNI blocking method, call the AbstractSelector
begin()
method, then call end()
when the JNI call has returned.
The selector should cancel the JNI method in its implementation of wakeup()
.
Rough code
(For a full running example see the github repo linked at the top.)
class NativeTask {
public NativeTask() {}
public void doTask()
throws InterruptedException {
NativeTaskSelector selector = new NativeTaskSelector(this);
try {
selector.registerStart();
doTask0(); // native method
if (Thread.interrupted())
throw new InterruptedException();
} finally {
selector.registerEnd();
try { selector.close(); } catch (IOException impossible) {}
}
}
/* The long-running native operation. */
native private void doTask0();
public void wakeupTask() { wakeupTask0(); }
/* A way to cause the native operation to wake up. */
native private void wakeupTask0();
}
class NativeTaskSelector extends AbstractSelector {
protected NativeTaskSelector(NativeTask task_) {
super(null);
task = task_;
}
public void registerStart() { begin(); }
public void registerEnd() { end(); }
final private NativeTask task;
@Override
public Selector wakeup() {
task.wakeupTask();
return this;
}
@Override
protected void implCloseSelector() throws IOException {
}
@Override
protected SelectionKey register(AbstractSelectableChannel arg0, int arg1,
Object arg2) {
throw new UnsupportedOperationException();
}
@Override
public Set<SelectionKey> keys() {
throw new UnsupportedOperationException();
}
@Override
public int select() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int select(long arg0) throws IOException {
throw new UnsupportedOperationException();
}
@Override
public int selectNow() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public Set<SelectionKey> selectedKeys() {
throw new UnsupportedOperationException();
}
}
© 2022 - 2024 — McMap. All rights reserved.