From: Brent Roman Date: 2004-12-29T04:29:18+09:00 Subject: Thread rendezvous with timeouts We needed an event scheduler to manage our multi-threaded motion control application -- a robotic bio-tech lab instrument. The core of the scheduler thread keeps a list of events sorted by future time and processes them after the appropriate delays. This leads naturally to something like: def awaitNextEvent loop { lock #the scheduler event queue if (nxtEv=nextEvent) #pops nextEvent from the event queue break if (@wakeTime=nxtEv.timestamp)<=(present=Time.now) lock exclusive_unlock {sleep @wakeTime-present} else #there is no next event, the queue was empty @wakeTime=MaxTime lock exclusive_unlock {sleep} end end } end All the locking and unlocking ensures that the queue cannot be modified by other threads until the scheduler is waiting for the next event. But, as the scheduler waits, other threads can insert events earlier than the one for which the scheduler is waiting. The astute reader may already spot at least one problem with the above pseudo-code. We need to unlock access to the scheduler queue before sleeping, but because sleep, unlike Thread.stop, does not affect Thread.critical, Thread.critical remains true and no other thread can run (except, of course, if a trap occurs :-) If one were to insert an explicit Thread.critical=false before each of the two sleeps in the above code, one would introduce a race condition because an event could then be inserted into the queue before the event scheduler is asleep awaiting the next. My current fix for this is a 'C' extension called "doze". doze is just a version of sleep that clears Thread.critical before sleeping. This works, but it feels like a hack and it does not really address the broader issue, which is, should it be possible for a thread to sleep with Thread.critical set? Sleeping with Thread.critical set is certainly bad practice, but I have used it for debugging purposes to freeze the system state so I could inspect it. rb_thread_wait_for() in eval.c handles the case of having Thread.critical set, so whoever coded that must have thought it was an important case. My "doze" hack does not handle the case where the sleeping thread also wants to be awoken on an I/O event. I suppose I could add an analogous "selectDoze" hack as well, but, again, this feels wrong. Why should the commonly desirable case require loading a special extension? I think it might make more sense to remove the rb_thread_critical check from the beginning of rb_thread_wait_for(time) in eval.c. Combined with the patch I submitted in ruby-core:4039, this would cause Thread.critical to be cleared on any attempt to sleep what would cause a different thread to be awoken. I'd advocate adding a special function to sleep with Thread.critical set to be used for debugging purposes only. Or maybe this could be implemented as an extra defaulted parameter to sleep and select. Any thoughts? -- Brent Roman MBARI mailto:brent@mbari.org http://www.mbari.org/~brent