From: Brian Candler Date: 2004-09-23T20:56:33+09:00 Subject: Re: TCP Socket read and write On Thu, Sep 23, 2004 at 04:25:12AM +0900, Bill Kelly wrote: > > Well, I can make my code have an API like yours, but it seems very unnatural > > to me. For exmaple, #recv_nonblock always returns "" if no data is available > > - but you cannot do a select() on a BufferedIO object, so you have to waste > > time polling it. > > The data_ready_signal passed into BufferedIO provides a means to > wait rather than poll. My application, which is currently structured > around a select() dispatch, uses global-signal instead, as in effect > a drop-in replacement for select(). > > So my main loop, instead of using select, like: > > nready = select([@tcp_clients], nil, nil, timeout) > > Has a semantically-similar, if you will, timed wait like: > > begin > @global_signal.timed_wait(timeout) > rescue Timeout::Error Ah, I see now. Perhaps what would be nice here is a version of Queue#pop with a timeout. Then you don't need two separate objects, one to signal "data ready" and one to carry the data itself. > Timeout will interrupt > your thread anywhere, even inside an ensure block. (This makes > it difficult if not impossible to use a timeout block on code > that internally uses ensure to release some critical resource.) Yes, I see the problem. It's hard to see a general-purpose solution to this; after all, a timeout has no way of knowing whether you're in a short-lived "ensure" block, or something which has gone wrong and really does need to be aborted (such as an infinite loop within your "ensure" block) However, specific solutions should be doable. Here's a first stab at a Queue#pop_timeout, see if this looks reasonable to you: require 'thread' require 'timeout' class Queue def pop_timeout(secs, e=Timeout::Error) me = Thread.current timedout = false timer = Thread.new { sleep(secs); timedout=true; me.wakeup } while (Thread.critical = true; @que.empty?) raise e if timedout @waiting.push Thread.current Thread.stop end @que.shift ensure timer.kill if timer and timer.alive? Thread.critical=false end end if __FILE__ == $0 queue = Queue.new Thread.new { queue.push("hello"); sleep(1); queue.push("world") } while true a = queue.pop_timeout(3) p a end end > It all started when I tried to do a timeout around a condition > variable wait. ... > However - should you ever need a ConditionVariable#wait with a > timeout--as my app does as it's *currently* structured--I haven't > yet found a simpler way to do it safely than that ugly timed_wait. Perhaps I've missed something, but I don't see why ConditionVariable#wait_timeout can't be implemented safely. Unlike the 'timeout' module, you don't need to have the timeout thread itself raise an exception; it can just set a flag. This code is untested but is just to sketch a solution: class ConditionVariable def wait_timeout(mutex, timeout=nil, e=Timeout::Error) timedout = false if timeout me = Thread.current timer = Thread.new { sleep(timeout); timedout=true; me.wakeup } end mutex.exclusive_unlock do @waiters.push(Thread.current) Thread.stop end raise e if timedout mutex.lock ensure timer.kill if timer and timer.alive? end end It's basically just ConditionVariable#wait copied from thread.rb, with an extra "raise e if timedout" at the end. Comments? Cheers, Brian.