Skip to content

Instantly share code, notes, and snippets.

@wjessop
Last active December 20, 2015 04:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wjessop/6072662 to your computer and use it in GitHub Desktop.
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.
# 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
$ 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!
# 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
Copy link

jeremy commented Jul 24, 2013

loop do
  timeout 4 do
    if data = data_queue.pop
      data.call
    else
      break
    end
  end
end

@regularfry
Copy link

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