MenTaLguY
5/12/2007 8:14:00 PM
On Sat, 2007-05-12 at 22:39 +0900, Tim Becker wrote:
> Any thought? Am I missing an easier/build-in method to block a thread
> only for a limited about of time?
Unless/until the built-in classes support timeouts, using a thread to
track the timeout is sadly your best option.
However, there are a number of problems with the specific approach
you've used here (e.g. not all accesses to @t are protected by the
mutex)... here's an alternate implementation...
require 'thread'
begin
require 'fastthread'
rescue LoadError
end
class MessageQueue
def initialize
@lock = Mutex.new
@messages = []
@readers = []
end
def enqueue msg
@lock.synchronize do
unless @readers.empty?
@readers.pop << msg
else
@messages.push msg
end
end
end
def dequeue timeout=nil
timeout_thread = nil
begin
reader = nil
@lock.synchronize do
unless @messages.empty?
# fast path
return @messages.shift
else
reader = Queue.new
@readers.push reader
if timeout
timeout_thread = Thread.new do
sleep timeout
@lock.synchronize do
@readers.delete reader
reader << nil
end
end
end
end
end
# either timeout or writer will send to us
reader.shift
ensure
# (try to) clean up timeout thread
timeout_thread.run if timeout_thread
end
end
end
Queue may seem sort of heavyweight to use for "callbacks" this way, but
if you use fastthread they are pretty inexpensive, and queues take care
of a _lot_ of the bookkeeping you would need to do to make this safe
otherwise.
One remaining issue is that it's possible for the timeout thread to
sleep _after_ the call to timeout_thread.run, so the cleanup isn't
necessarily that effective. However, Thread#kill (or Thread#raise)
isn't an option, since those can leave things in an inconsistent state.
A better solution would be to maintain a dedicated thread for tracking
timeouts which you don't have to worry about cleaning up -- I'll be
releasing a library to do that soon, but for now the above solution is
the best I can do.
-mental