From: Charles Oliver Nutter Date: 2007-12-31T17:16:49+09:00 Subject: Re: Experimental PATCH to improve thread performance Brent Roman wrote: >> If an exception were raised immediately after the blocking operation >> completed, wouldn't it prevent the ensure from running to completion? > > As I mentioned in a previous post, if one must make a blocking_operation > uninterruptible, one could code it as follows: ... > Note that this whole construct IS ATOMIC because a thread's default > interruptible status is false. Synchronous exceptions do not change > a thread's interruptible status and asynchronous exceptions (i.e. > interrupts, > typically raised by other threads) will be deferred until the target > thread's interruptible status is set to true, either explicitly or by > blocking. > Recall that, just before processing any external exception the thread's > interrupt status is aways reset to false. This is what keeps the construct > safe, > even when there is a whole stack full of ensure clauses to unwind. Ok, the key point here I must have missed is that threads would be uninterruptible by default. So let me recap, and let me know if I've got it now: In your model (which you did not invent, but we'll call it yours for sake of argument :) 1. threads are uninterruptible by default (or do you have to set them uninterruptible early on?) 2. blocking makes a thread uninterruptible 3. wrapping a section of blocking code in an uninterruptible block keeps it safe from interruptions This should be safe. But it does represent a change to the current model, yes? A change I would whole heartedly agree with, since it would mean concurrent-thread impls don't have to constantly poll and synchronize "just in case" another thread wants them to raise. > I did not invent this idea. In fact, this is the way that many > microprocessors deal with hardware interrupts at the machine code level. > I learned it as a teenager when I started programming > real-time industrial control systems in Intel 8086 assembly language. > (in the late 1970's!) > I'm just proposing that Ruby respond to asynchronous exceptions > the much same way that microprocessors process hardware interrupts. Yes, I think we're on the same page now. I agree. > In any case, enclosing a few critical blocking cleanups in > Thread.uninterruptible{} > will not be terribly difficult. Although, finding all of them may be a > bit of a challenge. Modifying Ruby to make threads uninterruptible by default should help flush out a lot of these cases, since they won't raise anymore. > Charles Oliver Nutter-2 wrote: >> >> I would disagree that Thread#raise is intended as a way to pass any sort >> of information. I think it's a way to kick a thread back to >> responsiveness without killing it. But it's still unsafe as it exists >> today. ... > I feel this is a very appropriate use of Exceptions. > However, using them as a replacement for Queues and the like in > normal data transfers between threads is, as you point out, > very inefficient and likely to be non-deterministic (i.e. unsafe). Ok, we still agree. It's certainly usable as a mechanism to pass information, and your use of them to propagate exceptional conditions is totally fine in my book. I'm just not sure I'd advocate them as a general-purpose cross-thread data transfer mechanism when there are obviously better options (and I think we agree here also). > Charles Oliver Nutter-2 wrote: >> >> It's also provably unsafe to be able to >> arbitrarily interrupt a blocking operation. Does it leave a file locked? >> a channel open? In Java, interrupting a thread that's blocking on >> low-level IO forces the IO channel into a dead state, since it's >> impossible to guarantee that it hasn't been irreversibly corrupted. >> >> The way to solve this--and really the only way to make it safe--is to >> use non-blocking operations instead. This is how all major >> multi-threaded workhorse applications are designed...they avoid blocking >> as much as possible and simulate blocking operations by feeding >> operations to a non-blocking worker and waiting for responses. > > This is really worrisome. What you are saying amounts to an admission > that there is no way to make native threads work as flexibly as > green threads. Recall that green threads must change all > the blocking I/O operations into non-blocking ones. Thus, in Ruby 1.8 > and 1.6 all I/O is non-blocking at the OS level -- where all that > tricky file state ultimately resides. In Ruby 1.9, I/O is blocking > at the OS level. So, are you saying that, in 1.9 (and possibly JRuby), > applications would be required to avoid blocking I/O to ensure thread > safety? I think you read too much into what I said. Interrupting a truly blocking operation in a green thread is just as unsafe as it is in a native thread. The fact that MRI implements what would be blocking operations as non-blocking operations under the covers actually supports this case; it's just masking the non-blocking gymastics for you. It's blocking as far as your code goes, but it's non-blocking as far as what's happening under the covers. So it can be interrupted without damaging anything. If you implemented the same model in Java with non-blocking streams, or with blocking streams that used non-blocking system calls under the covers, you should be able to get roughly the same result. Also, I mixed some vocabulary here. I meant that it's going to always be unsafe to interrupt a blocking operation arbitrarily in such a way that it can't recover from the interruption. In Java, all blocking operations that could be interrupted also raise exceptions that the caller must handle. This allows the target thread to recover from such an interruption, and also limits the range of possible exceptions the target thread must handle to a known quantity. In Ruby, with raise, there is no such known quantity, and you have to do a lot more work to ensure the thread you're causing to raise won't enter a really bad state because you've tossed a totally foreign exception at it. I will also read that paper you provided (I have not yet). I'm certainly no threading expert. > In any case, if blocking I/O is such a problem for thread safety, > why not just convert all blocking I/O requests in JRuby to non-blocking > ones in Java? Yes, why not? :) Actually, I want to do this. But non-blocking IO is harder than blocking IO and not always (or rarely?) necessary for typical apps, especially in the presence of true concurrent threads. We just haven't gotten around to it. Most of JRuby's IO is now using NIO, which makes it possible (i.e. easier, but still challenging) to use non-blocking operations across the board. However we haven't migrated everything to NIO, and what we have migrated still uses normal blocking operations in many places when they map directly to NIO blocking API calls. Work continues. Perhaps your application(s) can provide some demonstrable cases that would raise the priority of such work, because at the moment it's outweighed by other tasks :) - Charlie