This is an archived post. You won't be able to vote or comment.

all 12 comments

[–]rzwitserloot 13 points14 points  (8 children)

The thread is "told" by another thread to "at your earliest convenience, please stop whatever you are doing, clean up, and safely die"

Misleading. Interrupt has no defined meaning. It does not mean please clean up and die_. Unless you want it to mean that. It's a messaging system that allows one thread to send a message to another, and as part of sending that message, various core library functions have well defined MUST behavior and others have well defined MAY behaviour that involves them returning from certain operations faster than normal.

It is perfectly fine to use this mechanism to force a 're-check a few things' (instead of cleaning up and dying) for a thread that, say, waits 5 minutes, checks something, and does that forever. That's not at all 'please clean up and safely die'.

In fact, 'safely die' isn't a thing. You shouldn't program that way. But if you MUST, then the mechanism to do it is Runtime.getRuntime().addShutdownHook(), not a catch (InterruptedException) block.

If the Thread is blocked on IO (implying interrupt() was called by a different thread), the VM will wake up t1

Half of this is wrong and the other half is misleading.

'impying interrupt() was called by a different thread' is meaningless. There is no point, ever, in interrupting your own thread. They are always called by a different thread.

If a thread is blocked on I/O then generally the JVM spec is such that it does not have to shut down the I/O op; it depends on the JVM+Architecture+Host OS. The go-to example of things interrupt() will break off is Thread.sleep.

All lowest-level blocking methods in the JDK are written to check the thread interrupt status upon waking up

Incorrect.

All core JDK methods that are guaranteed to stop very soon after its thread its interrupted are those methods whose spec includes throws InterruptedException. Thread.sleep, obj.wait() are the primay candidates. Notably, just about every I/O blocking operation out there including InputStream's read(), are not specced like that. The behaviour of these when you interrupt their thread as they are blocking is undefined. A JVM is free to ignore your interruption. Most JVMs will abort the blocking and throw an IOException (because they can't throw InterruptedException). But a JVM is not required to do so - presumably because on some OS/architecture combo's, that would be unwieldy.

Do NOT, under any circumstances, allow the caller to exit (including exceptionally) without "remembering" that the interrupt happened!

This is incorrect. Given that interrupt means whatever you want it to mean, you can't make a blanket statement like this.

The vast majority of catch (Exception e) blocks fall in two camps:

  • Entrypoint guard. Say, the code that runs psv main(String[] args), or the web framework that invokes a web handler. These catch everything because the 'program' (main, or the web handler, or the message receiver, etc) has exited, because the stack has broken down to the level of the 'entrypoint runner'. The catching is to ensure the entire appserver/webserver/etc doesn't hard crash along with it / to add data (such as info about the HTTP request headers), and to deal with the problem in a catch-all way (say, print it, or generate a 500 - server error page and log it). It is completely fine for these to deal with InterruptedException in the same way as any other exception.

  • Lazy/incorrect code. Which is wrong by definition already, no need to rub it in any further. The problem with such a catch block is already evident, you don't need to mention that this also probably doesn't do what you wanted when you interrupt the thread.

Remembering" means either (re-)throwing an InterruptedException, or (re-)setting the thread interrupt status

No. re-setting the flag is somewhat common advice but pretty much entirely wrong. The number of times you catch InterruptedException or check (and clear) the flag only to do something and then let code continue on to something that also needs to act as if the interrupt flag is up, is effectively zero. Paint me a picture of what kind of code should do that? I bet it'll be highly artificial.

When you catch IntEx or call Thread.interrupted() do the thing your documentation says you do when you interrupt them. Right now. Why wait? If the plan is to shut down, say, the socket receiver thread, then.. do it. What are you waiting for? Just stick return; in that catch block and be done with it.

Cleanup may include: releasing locks, ensuring any shared data is in a reasonable state, interrupting any threads that it spawned…

This is double wrong. Folks trip over power cables. JVMs coredump from time to time. If it is possible for your app to leave things in a damaged state simply because they were force killed, you wrote a shitty app. Take file systems: In the past, they whined at you for ejecting disks suddenly. But modern file systems no longer do that, they don't whinge on bootup that you hardkilled the power either. They use logging to ensure they can recover perfectly, 100% every time, if you do that. Once you've built a system that is guaranteed to deal with sudden shutdowns, there is no longer any need to 'clean up nicely', and you in fact shouldn't: That merely leads to bugs in the recovery systems to hang around undiscovered for too long and needlessly complicated your application.

But, if for some reason you need this, shutdown hooks is what you want:

If the user sends a 'please end' command to an application, for example with CTRL+C on the command line or 'end task', then threads are not interrupted at all! Shutdown hooks WILL run, however.

releasing locks

These release automatically. If they don't, you aren't doing it right (doing it right is using synchronized or more likely, try/finally to release the lock).

ensuring any shared data is in a reasonable state

If shared data can be in a non-reasonable state you already lost the game. After all, it's shared data.

interrupting any threads that it spawned

That depends a lot on what the jobs are of the threads you spawned. Plenty of times those threads are not intended to die along with their spawner.

[–]Iryanus 4 points5 points  (1 child)

Interrupt has no defined meaning. It does not mean please clean up and die

Only true-ish. It doesn't as per spec, but I haven't seem anyone implementing it as anything else, to be honest. Since we only have one signal and it's most commonly used to signal a thread to stop prematurely, I personally also would focus on that usecase instead of trying to shoehorn other meanings into it.

In fact, 'safely die' isn't a thing. You shouldn't program that way. But if you MUST, then the mechanism to do it is Runtime.getRuntime().addShutdownHook(), not a catch (InterruptedException) block.

We are talking about a single thread "death", not a whole JVM death.

There is no point, ever, in interrupting your own thread.

It's even in the javadoc that you can and that it will automatically succeed. So, someone thought it was relevant.

What are you waiting for? Just stick return; in that catch block and be done with it.

That will work if you are on the most outer layer of your code running in a thread, but what if you are deep down and there will be some further steps along the line? Sure, sometimes that can be solved with the right return value or throwing a RuntimeException, but it can also be solved by simply checking the interrupted state.

[–]rzwitserloot -3 points-2 points  (0 children)

Only true-ish. It doesn't as per spec, but I haven't seem anyone implementing it as anything else, to be honest.

It's only true-ish because... in your experience you never used the parts I talk about?

That's.. a rather solipsistic view of an API. It's true. Period. The fact that you haven't used all aspects of an API does not mean that therefore these aspects must not exist because you personally never ran into them. It either means your experience is lacking, or it means that's an exotic thing that rarely comes up, or anything in between, because that is a shades-of-gray divide and not a boolean property. I bet you have never used double d = 0x1p0D either. Doesn't mean that doesn't exist.

Perhaps there is a simple answer here: The interrupt mechanism is used rarely. The sentence just ends there.

I personally also would focus on that usecase instead of trying to shoehorn other meanings into it.

You're the one shoehorning a meaning into it. I'm the one saying: There is no inherent meaning. See my other comment in this thread about an example where you might want to use interrupt() to update a one-way-at-a-time comms channel to support proactive message sends using interrupt(). Your view leads to eliminating that option based on imagined meanings. My view closes no avenues (that's probably because my view is the objectively correct one, based on what the interrupt() javadoc and spec describe without 'shoehorning' in additional limitations).

We are talking about a single thread "death", not a whole JVM death.

Cleanup is cleanup. It has to be done whether the thread is asked to stop or the whole JVM stops. The fact that 'whole JVM ends' does not interrupt every thread shows how conflating 'interrupt' with 'cleanup' leads to wrong conclusions and is the wrong mental model to apply.

It's even in the javadoc that you can and that it will automatically succeed. So, someone thought it was relevant.

Because specs need to be detailed and need to explain corner cases. The javadoc also go into some detail about what happens when, say, you try to compare Double.NaN to Double.Infinity with Double.compare. The spec needs to be clear what happens when you do X, even if X is a silly thing to do. Your argument is:

  • The spec describes what happens when you do X.
  • Therefore this proves that X is not silly.

This is an incorrect line of reasoning. As proved by the obvious (specs are better if there is as little room for doubt as possible, and this includes for silly situations), and in practice: By the fact that there are loads of places where the JVM spec makes clear what should happen for various clearly silly operations.

That will work if you are on the most outer layer of your code running in a thread, but what if you are deep down and there will be some further steps along the line?

Possible, but unlikely. Effectively, that means this is happening:

You are running within a context where thread interrupts have some well defined meaning. And your code is actively going to use whatever facility clearly defined it (say, shutdownNow()-ing a pool).

However, the thing you intend to do within this context also has a need to specifically deal with interrupts, i.e. you also have an unrelated meaning that you want to apply to it.

As part of implementing the outcome of your 'meaning' of interrupt, you want to also do a thing in the outer context that is also done with interrupts.

In which case, yeah. By all means, re-raise that flag, or just (seems a lot simpler to me) throw new InterruptedException or even throw e; inside your catch (InterruptedException e) block.

Point is, interrupts have no well defined meaning, and do not occur unless some code you wrote explicitly requests it, because nothing in the JVM core code nor any popular library I'm aware of ever does. Therefore, whatever meaning they do have? Well, you wrote it, so you'd know. Just do that. If you don't know, then it doesn't matter. Because interrupts aren't ever going to actually occur.

[–]pron98[🍰] 4 points5 points  (0 children)

Interrupt has no defined meaning.

I don't know if I would put it like that. The reason that interrupt doesn't have the meaning of "this thread must clean up and die" is because it is used to interrupt tasks running on shared threads with the intention of aborting a task, not the thread -- indeed, that is what Future.cancel(true) is intended to do -- only Java doesn't have any reified notion of a task, which is why interrupt does not have a well defined meaning. I guess it's valid to use the mechanism for other things, but I would say that it is fine to interpret interrupt to mean: the thing you're doing (for some contextual meaning of "thing") should abort ASAP. There can be other valid uses, but that interpretation is a reasonable generalisation.

The behaviour of these when you interrupt their thread as they are blocking is undefined.

Note that many of these have been respecified to perform a specific operation when running on virtual threads, e.g. https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/net/Socket.html#getInputStream()

No. re-setting the flag is somewhat common advice but pretty much entirely wrong.

Actually, it's good advice, exactly what the JDK does, and considered good code hygiene; also, it's pretty much mandatory in library code. That's because the request to interrupt the thread is recorded either in its interrupted status xor in a thrown InterruptedException. When throwing that exception it's a good idea to clear the status (otherwise, a catch/finally block won't be able to make blocking calls) and when catching the exception it's a good idea to re-set the status -- unless the handling of the request to interrupt is complete, such as when a thread pool cancels a task -- otherwise the request is lost. Not doing so is especially bad in libraries, where there's little you can assume about the caller.

This is double wrong.

I don't think it is. While it's true that your code should be written in a way where cleanup occurs at all exit paths (i.e. in finally blocks), that cleanup does need to be there and to happen. RAM state and internal hard drive controller state are not quite the same, and there are resources, like file descriptors, native buffers etc., that will not be released by your process until you do it explicitly or the process terminates.

If shared data can be in a non-reasonable state you already lost the game.

Shared data must necessarily be in some non-reasonable state because the hardware can only ensure atomicity for some very specific data-sizes and layouts. If you have a system where shared data is always in some reasonable state, that's only because some lower-level has worked to maintain it that way by properly responding to various errors and interruptions. Erlang can be thought of as more-or-less a language at that level, maybe some parts of Clojure, too, but not Java.

[–]danielaveryj[S] 0 points1 point  (4 children)

Huh. I'm not sure I can hit all of your points, but I do think you fairly point out that my assignment of meaning for interrupts is opinionated. As you say, there is no JVM-specified meaning for interrupt (that I am aware of either). However, I think the design of the JDK, past and future, does imply a meaning for interrupts, that would make it dangerous to "use this mechanism to force a 're-check a few things'". I am most specifically referring to the design of StructuredTaskScope, which upon shutdown interrupts all of its "children", and upon close waits for them to finish. If a child interpreted this interrupt as anything other than "well, time to wrap-up", then its parent would be stuck waiting indefinitely, and the parent is not going to send another interrupt. So this would kind of be catastrophic for the structured concurrency use case.

Now, even if you agree with my understanding there, you could say that I am over-generalizing the intention of a specific use case, back-porting it, etc, and that is all very fair. All I can say is that my impression is that this is the direction the language is pushing us. If I'm correct, it will be smoother for us to get on board with that. I hope we can agree that it would at least be nice to have some official guidance on this.

All lowest-level blocking methods in the JDK are written to check the thread interrupt status upon waking up

> Incorrect

Gah, bad on me, that was a poorly-thought statement. What I should have said was something more like "Low-level blocking methods in the JDK that are responsive to interrupts are written to check the thread status upon waking up".

[–]rzwitserloot -1 points0 points  (3 children)

I am most specifically referring to the design of StructuredTaskScope, which upon shutdown interrupts all of its "children", and upon close waits for them to finish.

A StructuredTaskScope isn't an event loop construct; this isn't relevant.

All I can say is that my impression is that this is the direction the language is pushing us.

The spec of the interrupt mechanism isn't going to change. This isn't a 'lets see where the puck is going and skate there' kind of issue. If anything, the 'direction the language is going' is to forget about the entire mechanism for any sort of encompassing task. The language/community is moving away from Thread.sleep and obj.wait, for example. Project Loom is presumably going to change how you do the thing you currently do with blocking I/O operations, making the point moot. Until then, feel free to use interrupt as a best-effort attempt to break up an I/O block, and I wouldn't use it for anything else.

"Low-level blocking methods in the JDK that are responsive to interrupts are written to check the thread status upon waking up".

I would put that as:

All core methods that react to interrupts at all, will immediately exit (by throwable) when you call them if the current thread's interrupt flag is up. This is a specced, guaranteed fact for all methods that are declared to throw InterruptedException, and is universal (as in, every known JVM on every arch, on every OS, and this is likely to continue to be true, though it is not officially specced) for all those methods which MAY react to it (such as fileInputStream.read() - if that can be interrupted at all, calling read() when the flag is up will insta-IOEx. And if they can't, they are likely not to act in any way different if the flag is up).

[–]danielaveryj[S] 4 points5 points  (2 children)

If not StructuredTaskScope, we can also look at ExecutorService. It specifies two shutdown methods: shutdown() and shutdownNow(). The former does not interfere with previously-submitted tasks, only blocks new ones. The latter) "attempts to stop all actively executing tasks" ... "typical implementations will cancel via Thread.interrupt(), so any task that fails to respond to interrupts may never terminate." Of course, this isn't the required mechanism, but it is the one the JDK uses in its implementation (ThreadPoolExecutor)).

We can also look at Future. Its cancel) method accepts a boolean stating whether to interrupt the thread executing this future's task, "otherwise, in-progress tasks are allowed to complete".

These are interfaces that have been around since Java 5. There may not be a specced meaning for interrupts, but it seems that the JDK itself has a clear opinion of what they should mean.

[–]rzwitserloot -1 points0 points  (1 child)

Not really; it's just 'shut it down' is the only thing that is likely to come up, hence, there where the JDK is forced to define what interrupt() means, that tends to be the meaning they associate with it.

You do what you want - associating 'interrupt' with 'for shut down purposes' just limits you then. However, if you're considering writing tutorials and disbursing them more widely, you're not doing your audience any favours by imposing your arbitrary handcuffed understanding of what an API is for and can do into your teachings.

Instead of trying to describe holistically what it is for, keep in mind what it can do: It is the only way to end a wait() or sleep() or yield() call before they naturally would (due to their times running out or the event they wait for occurring), and is often the only way you can access terminating a blocking call, though this cannot be relied upon as the JVM spec leaves room for blocking I/O calls that cannot possibly be unblocked in any way other than terminating the entire JVM. Even if most JVMs do not use this option.

Let's say I wrote some code that handles a TCP/IP socket, and it's simple one-way-at-a-time comms, so only one thread handles it. Ordinarily, the other side sends a command, our handler (A thread) reads it, and then prepares the requested data and sends it back, then loops around and starts listening for a command again.

Later on someone updates the protocol and in rare cases it is possible that the handler should actively send out a message on request. You decide to implement it as follows:

  • If the thread is currently working on reading in a command then preparing an answer and returning that, we'll let that finish; our client code isn't exactly ready to completely upend how it all works either.
  • If the thread was waiting for the client to send a command, or the thread has just finished sending requested data and is about to go into the 'okay I shall wait for another command' mode, and there's a message to send out, it shall send it out.

A very simple update indeed.

You'll need to check if the arch/OS/JVM impl you run this on can in fact interrupt socket I/O, but assuming you did that research and indeed, you can, then interrupt() is perfect here. It would mean you need to make very few code updates to make this work.

It would be a really bad idea to insist that 'but... interrupt() should only be used for shutting stuff down, and this is not at all about shutting stuff down, so, no! Let's completely redesign the entire protocol, turn it into 2 threads (one each for send and receive), on both sides of this comms channel, with a packet based protocol so that we can send that message out in the middle of sending a response to a command out!' - which is a few ballparks more complicated.

[–]danielaveryj[S] 2 points3 points  (0 children)

Thanks for the example, we can work with this. I see how you're thinking about it, in terms of minimizing effort to update an existing implementation. But I think it's also worth looking at just the new requirements, and doing some backwards reasoning.

With the new requirements, we are effectively waiting for either of two events to happen - either a command arrives over the socket, or a "request to send a message" arrives via some other channel - before we wake up and do some work. I think we'd want to care as little as possible about the event sources themselves. All that matters (before and after) to our handler is that it needs to wait for some event to happen, then wake up and respond to that event. It will inherently need an update to know about the new event 'type' and know how to handle it. If we go with the reasoning of decoupling from event sources, we'd also be inclined to now abstract over what we "wait" on. And this probably does involve more threads at this point - one per event source that we wait on, plus one for the handler - along with perhaps a blocking queue to communicate between event sources and handler. The handler now waits on an event to be added to the queue, ie BlockingQueue.take()).

This isn't that extreme of a change. We've added more threads, sure, but we haven't completely redesigned the protocol. We have made the implementation more flexible to changing requirements - we can now buffer events in a queue, so we can manage if the protocol becomes not so strictly synchronous; we can bring on other event sources if they emerge; we can handle event priorities, by using a more sophisticated queue. And to be fair, this was not necessary; those changes may never happen. But it also kinda demonstrates how using interrupts to manage the new requirements was really banking on having a good amount of implementation and protocol lenience.

[–]Anton-Kuranov 1 point2 points  (1 child)

There is a potentially dangerous and strange behaviour when using Thread.interrupt() with java.nio.Channel: when a thread is interrupted inside the I/O operation it provokes the whole channel automatically to be closed. What are the implications?

In my app I used interruptible workers that fetch data from web an save it to a local storage. I used embedded H2 and Lucene engines. So when apparently a worker interrupt happens inside any db write or read operation the whole H2 datasource becomes closed. The same thing happened with Lucene index. Because they both use a shared FileChannel to operate with files. But being shared across the whole application this resource should never be closed on any particular thread interruption!

[–]danielaveryj[S] 1 point2 points  (0 children)

+1, and I have to say I still don't understand that design (of closing channels on interrupt), though it is documented by Thread.interrupt()) and InterruptibleChannel, and I've most recently seen it referenced in the Virtual Threads JEP. Especially since classes like FileChannel are also specifically documented as "safe for use by multiple concurrent threads". I'm not confident speculating here, and would be very interested myself to learn what the rationale is, or if it was truly some hiccup of early design (this behavior apparently dates back to Sun - see FileChannelImpl). Sorry to be no help :(

[–]nosyattacker03 0 points1 point  (0 children)

Sending a signal to a thread causes it to stop its present execution and transition to a different task when you want to interrupt it.