Last active
December 20, 2015 04:39
-
-
Save wjessop/6072662 to your computer and use it in GitHub Desktop.
I need a "rolling timeout" for some code. If something doesn't happen for x seconds, timeout, but if it does, reset the timer. The Ruby Timeout module doesn't help with this, it provides only a static timeout.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The API I thought up, This example will | |
# sleep for 16 seconds (4 * 4 seconds + 5 seconds) | |
begin | |
RollingTimeout.new(5) {|timer| | |
sleep(4) | |
timer.reset | |
sleep(4) | |
timer.reset | |
sleep(4) | |
timer.reset | |
sleep(4) | |
timer.reset | |
sleep(6) # This will timeout | |
end | |
} | |
rescue | |
# Do something | |
puts "Timed out!" | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ ruby rolling_timeout.rb | |
Rolling timout window open for 5 seconds total | |
Rolling timout window open for 10 seconds total | |
Rolling timout window open for 15 seconds total | |
Rolling timout window open for 20 seconds total | |
Rolling timout window open for 25 seconds total | |
That was the last item on the queue, the pop will now timeout in 5 seconds | |
Rolling timout window open for 30 seconds total | |
Timed out! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A working solution. I'm not sure I like | |
# the thread destruction/creation. See | |
# output.txt for the output. | |
require 'timeout' | |
require 'thread' | |
class RollingTimeout | |
attr_accessor :code_thread | |
def initialize(time_in_seconds, &block) | |
@time_in_seconds = time_in_seconds | |
start | |
@timed_out = false | |
@code_thread = Thread.new{ yield self } | |
@code_thread.join | |
@timer_thread.join if @timed_out | |
end | |
def reset | |
stop | |
start | |
end | |
def stop | |
@timer_thread.kill | |
end | |
def start | |
@timer_thread = new_timer_thread | |
end | |
private | |
def new_timer_thread | |
Thread.new { | |
sleep(@time_in_seconds) | |
@timed_out = true | |
@code_thread.kill | |
raise Timeout::Error | |
} | |
end | |
end | |
data_queue = Queue.new | |
3.times { data_queue << Proc.new {sleep(0.09)} } | |
data_queue << Proc.new {sleep(0.09); puts "That was the last item on the queue, the pop will now timeout in 0.1 seconds"} | |
begin | |
slept_for = 0 | |
x = RollingTimeout.new(0.1) {|timer| | |
while data = data_queue.pop do | |
data.call | |
puts "Rolling timout window open for #{slept_for += 0.09} seconds total" | |
timer.reset | |
end | |
} | |
rescue Timeout::Error | |
puts "Timed out!" | |
end |
jeremy
commented
Jul 24, 2013
You need to start the @code_thread
and set @timed_out
before starting @timer_thread
, or you've got a race.
I was going to suggest doing away with @code_thread
and just yielding so the code in the block happens on the same thread as that outside it, but that would mean using a Thread#raise
in the @timeout_thread
which might accidentally get caught by the user code...
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment