From: Roger Pack Date: 2008-03-18T00:06:47+09:00 Subject: Re: Thread#raise, Thread#kill, and timeout.rb are unsafe > What should safe point declaration mechanism look like? > > matz. Tongue in cheek I would say something like uninterruptible do end or ensureUninterruptible end or uninterruptible do # some stuff handle_exceptions_that_have_been_thrown end # where handle_exceptions is where they can all get handled. Kind of scary still, though. :) Anyway my latest thought on the whole timeout difficulty is that there are a few difficulties with it that might be overcomeable: Unfortunately, as a previous poster noted, using raise seems itself almost always inherently dangerous, so these would probably all be band-aids to a bigger problem. 1) if you have two nested timeouts then "you don't know where the timeout came from" timeout 5 do timeout 5 do begin sleep rescue Exception # which one is it? What did I just rescue? end end end You could overcome this by just ensuring that it throws distinct classes per timeout (i.e. if different timeouts). timeout 5 do # will by default throw class Timeout::ErrorDepth1 < Timeout::Error timeout 5 do # will by default throw class Timeout::ErrorDepth2 < Timeout::Error I suppose this would help people decipher what is happening when timeouts are thrown, though not be helpful otherwise. 1.5) It creates a new thread once per call to 'timeout' which is less than efficient. While I typically don't care about efficiency when using timeout, it might be possible to just have a "single timer thread" that handles all the raise'ings on the others, to save on thread creation (theoretically). So something like this (sorry if the syntax is wrong--this is just pseudo-code for now) $timer_thread = nil # just so I remember the name--you wouldn't have to use a global, of course class TimerThread < Thread def handle_any_requests # if any thread requests a timeout, add it to some list if some_thread_is_waiting sleep_until_should_wake_next_thread if waiting_thread_is_still_running_its_timeout_block waiting_thread.raise Timeout::Error end else sleep -1 end end def TimerThread.add_me_to_the_queue_to_interrupt_me_in seconds, &block $timer_thread.add_it_to_queue Thread.current, seconds $timer_thread.wakeup # might need to synchronize this call so that we don't get confusion block.call # if we get interrupted between this line and the next we are DEAD. Exactly the same problem that we had before--an exception could be raised "far in the future" to interrupt it, when it has already left and it shouldn't be raised. $timer_thread.take_me_off_the_queue_I_am_done Thread.current # might need to synchronize this call, too end end # begin program. Note that you could just fire this up the first time the script calls Timeout::timeout $timer_thread = Thread.new { loop do handle_any_requests end } Now when you want timeouts you do something like TimerThread.add_me_to_the_queue_to_interrupt_me_in 5 do # seconds # do some stuff for a long time end That would help with efficiency, since you wouldn't have to spawn a new thread for each call (though it would, sigh, leave one running always in the background). It would alleviate some concurrency problems, and still leave at least one in there. Just throwing it out there. 2) nested timeouts can interrupt each other and leave and 'errant thread' running which raises a timeout 5 do timeout 50 do begin sleep end end This causes two threads to be generated, each with a "time out bomb" ticking, waiting to happen. The outer loops' thread could (as Charles Nutter noted) interrupt the inner thread during its ensure block, so it ends up not killing its ticking "time out bomb" thread. Which means that 45 seconds later it will go off and randomly raise on the parent thread. To avoid this I "think" you could wrap the things with mutexes. However, this also doesn't work because the mutex "synchronize" stuff itself uses an ensure block, which could be interrupted by the same confusion (and does, in my experience). <> Never mind. I give up. The only safe way to generate asynchronous calls seems to be to use EventMachine or rev (which have asynchronous timers) and have your own "exception handling" blocks (i.e. blocks where it is designated to handle those exceptions). Thanks. -R -- Posted via http://www.ruby-forum.com/.