Can continuation support finally solve the "How do I stop this thread" problem?

Ron Pressler ron.pressler at oracle.com
Thu Sep 1 09:04:30 UTC 2022


Hi.

See Alan’s response re the details of interruption.

When it comes to the question of forcibly killing threads, for it to be generally useful, there must be limitations imposed on what the threads can do to data that is accessed by other threads, as an errant thread could otherwise harm other threads. The Java platform (and language) currently does not impose such “isolation” limitation, but some language targeting the Java platform could, and so it could also emit interruption checks when compiling to bytecode. A language that behaves in this way is Erlang (although I don’t know if implementations targeting Java are currently maintained), but even there there are pitfalls. A similar path is available to jshell as well, as it can emit interruption checks in the bytecode.

As to the question of sandboxing in general, I believe the best way is to restrict the APIs available to untrusted code. Modules are one part of the story, but the java.base module, available to all Java programs, has plenty of “dangerous" APIs. Those could be fenced off with (very careful!) bytecode transformation and classloader implementation. However, just as sandboxing of Java code requires some support from the Java runtime, when native code is involved — perhaps even indirectly — sandboxing would require support from the OS, and the OS does provide “isolates” in the form of processes. Forcibly terminating a process is *largely* safe (assuming the process is forbidden from mutating shared files or memory).

But note that while the Java runtime is, at least hypothetically, able to provide sandboxing for a *client* program (such as applets), it lacks other crucial pieces to support sandboxing of *server* programs. The difference between the two is that client programs implicitly rely on OS process isolation: a client program serves one user, while a server program of the kind you describe serves many. Even if all “dangerous” APIs are blocked, any user code could allocate as much memory as it likes, exhausting the memory available for the entire process. If it is also allowed to spawn platform threads at will, it can also exhaust the CPU allocation for the entire process. In the case of a client program, the process can be forcibly terminated without affecting other users, but that is not the case for server programs that are shared among multiple users.

The Java runtime could be changed to support isolated heaps in imitations of the isolation and memory restrictions offered by the OS, but if the goal is to share some internal runtime data structures for efficiency, there are ways to do that for multiple processes. Given that the kernel has more arrows in its quiver to support such isolation, including at the native code level, that is probably the most appropriate level to provide it, I think, so much so that trying to implement proper and secure isolation in multi-user server programs in user mode is a fool’s errand. A multi-user Java runtime running in *kernel* mode is a different matter altogether, but it is currently beyond the scope of the OpenJDK JDK.

— Ron

On 31 Aug 2022, at 22:50, Archie Cobbs <archie.cobbs at gmail.com<mailto:archie.cobbs at gmail.com>> wrote:

This question is somewhat tangential to Loom but I thought I'd ask in case the answer is an easy yes or no.

Lately I've been reviewing various attempts at intra-JVM Java "sandboxing", none of which are fully satisfying. I think this is a missed opportunity for Java in general, but that's a separate discussion.

The term "sandbox" has various meanings but for this question think of it simply as what you would need (for example) to build a web site that hosted a live JShell console for educational purposes but all running within a single JVM (there are some examples of this out there like tryjshell.org<http://tryjshell.org/> but those use separate processes or Docker). Obviously, execution of arbitrary Java code from random people on the Internet would have to be strictly controlled, and therefore JShell's ExecutionControl would have to run the code in some kind of airtight sandbox.

I just want to focus on one small aspect of doing this: A basic requirement of such a sandbox is the ability to stop a running thread. But currently there's no 100% reliable way to do this in Java, even if you can rewrite the bytecode, because it's impossible to unblock a thread blocked in certain system calls.

What about Thread.stop()? It only works sometimes. Plus it's deprecated (side node, the stated reasons for deprecating this method never made sense to me, because the trade-offs should be left to the programmer to evaluate, and moreover ThreadDeath is no more unsafe than StackOverflowError, which programmers seem to throw all the time :)

FYI here's a simple example showing a thread that can't be stopped no matter what we try:

public class ThreadNoStop {
    public static void main(String[] args) throws Exception {

        // Start unstoppable thread
        final Thread thread = new Thread(() -> {
            System.out.println("thread: reading stdin...");
            try {
                System.in.read();
            } catch (Throwable t) {
                System.out.println("thread: caught exception: " + t);
            }
            System.out.println("thread: done reading stdin...");
        });
        thread.start();

        // Try to kill it any way possible
        Thread.sleep(500);
        System.out.println("main: invoking interrupt()...");
        thread.interrupt();
        System.out.println("main: invoking stop()...");
        thread.stop();
        System.out.println("main: closing System.in...");
        System.in.close();   // hangs here until there's input
        System.out.println("main: joining thread...");
        thread.join();
        System.out.println("main: done.");
    }
}

[JEP 425 Virtual Threads] states:

> The implementations of the networking APIs in the java.net<http://java.net/> and java.nio.channels packages now work with virtual threads: An operation on a virtual thread that blocks to, e.g., establish a network connection or read from a socket, releases the underlying platform thread to do other work.

This makes it sounds like basically every blocking method is going to have to be adapted so that its state can be "detached" from the platform thread for continuation purposes...? If so, great!

My question is this: could this work be leveraged to also finally solve the "How do I stop this thread" problem?

Even if this new mechanism only worked for virtual threads (or only for platform threads) it would still be a big improvement.

Again, my opinion here but it seems like there really ought to be some official equivalent of Process.destroyForcibly() for Java threads.... it's a basic control/fail-safe.

On a related note - will [JEP 428 Structured Concurrency] guarantee that exiting a StructuredTaskScope never hangs indefinitely?? I guess not..

But a better stop mechanism would allow it to make that nice guarantee. This would give it an "automatic cleanup" feature analogous to how try-with-resources automatically hides any extra exceptions thrown by close().

We could also then add a new method ExecutorService.forceShutdown()...

This seems like an obvious missing piece to me.

Thoughts?

-Archie

--
Archie L. Cobbs

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20220901/08ecf361/attachment-0001.htm>


More information about the loom-dev mailing list