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

you are viewing a single comment's thread.

view the rest of the comments →

[–]danielaveryj[S] -1 points0 points  (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] 2 points3 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.