How do I write native code which responds to `Thread.interrupt()`?
Asked Answered
R

1

9

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()?

Robbinrobbins answered 29/3, 2015 at 15:24 Comment(0)
R
9

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();
    }

}
Robbinrobbins answered 29/3, 2015 at 15:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.