Skip to content

Commit

Permalink
Handle Timeout + fork and add test for it
Browse files Browse the repository at this point in the history
  • Loading branch information
eregon committed May 15, 2022
1 parent 74add5e commit 4baee63
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 22 deletions.
51 changes: 29 additions & 22 deletions lib/timeout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,30 +95,37 @@ def finished
end
private_constant :Request

def self.create_timeout_thread
Thread.new do
requests = []
while true
until QUEUE.empty? and !requests.empty? # wait to have at least one request
req = QUEUE.pop
requests << req unless req.done?
end
closest_deadline = requests.min_by(&:deadline).deadline

now = 0.0
QUEUE_MUTEX.synchronize do
while (now = Process.clock_gettime(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty?
CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now)
end
end

requests.each do |req|
req.interrupt if req.expired?(now)
end
requests.reject!(&:done?)
end
end
end
private_class_method :create_timeout_thread

def self.ensure_timeout_thread_created
unless @timeout_thread
unless @timeout_thread and @timeout_thread.alive?
TIMEOUT_THREAD_MUTEX.synchronize do
@timeout_thread ||= Thread.new do
requests = []
while true
until QUEUE.empty? and !requests.empty? # wait to have at least one request
req = QUEUE.pop
requests << req unless req.done?
end
closest_deadline = requests.min_by(&:deadline).deadline

now = 0.0
QUEUE_MUTEX.synchronize do
while (now = Process.clock_gettime(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty?
CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now)
end
end

requests.each do |req|
req.interrupt if req.expired?(now)
end
requests.reject!(&:done?)
end
unless @timeout_thread and @timeout_thread.alive?
@timeout_thread = create_timeout_thread
end
end
end
Expand Down
20 changes: 20 additions & 0 deletions test/test_timeout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,24 @@ def test_handle_interrupt
}
assert(ok, bug11344)
end

def test_fork
omit 'fork not supported' unless Process.respond_to?(:fork)
r, w = IO.pipe
pid = fork do
r.close
begin
r = Timeout.timeout(0.01) { sleep 5 }
w.write r.inspect
rescue Timeout::Error
w.write 'timeout'
ensure
w.close
end
end
w.close
Process.wait pid
assert_equal 'timeout', r.read
r.close
end
end

0 comments on commit 4baee63

Please sign in to comment.