Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 67 additions & 8 deletions lib/timeout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,57 @@ def add_request(request)
end
private_constant :State

class FiberState
attr_reader :mutex

def initialize
@interruptible = false
@interrupting = false
@mutex = Mutex.new
@condvar = ConditionVariable.new
end

def interruptible
prev = nil
Sync.synchronize @mutex do
prev = @interruptible
@interruptible = true
@condvar.signal
end
begin
yield
ensure
Sync.synchronize @mutex do
@interruptible = prev
@condvar.signal
end
end
end

def start_interrupt(request)
raise unless @mutex.owned?

until @interruptible
# We hold the Mutex so the Request can't finish,
# except at this wait call, so we check right after
@condvar.wait(@mutex)
return false if request.done?
end
@interrupting = true
@interruptible = false
true
end

def finished
raise unless @mutex.owned?

if @interrupting
@interrupting = false
end
end
end
private_constant :FiberState

class Request
attr_reader :deadline

Expand All @@ -149,13 +200,18 @@ def initialize(thread, timeout, exception_class, message)
@exception_class = exception_class
@message = message

@mutex = Mutex.new
@done = false # protected by @mutex
@fiber_state = (Thread.current[:timeout_interrupt_queue] ||= FiberState.new)
@mutex = @fiber_state.mutex
end

def interruptible(&block)
@fiber_state.interruptible(&block)
end

# Only called by the timeout thread, so does not need Sync.synchronize
def done?
@mutex.synchronize do
return @done if @mutex.owned?
Sync.synchronize @mutex do
@done
end
end
Expand All @@ -167,16 +223,17 @@ def expired?(now)
# Only called by the timeout thread, so does not need Sync.synchronize
def interrupt
@mutex.synchronize do
unless @done
@thread.raise @exception_class, @message
@done = true
end
return if @done
return unless @fiber_state.start_interrupt(self)
@thread.raise @exception_class, @message
@done = true
end
end

def finished
Sync.synchronize @mutex do
@done = true
@fiber_state.finished
end
end
end
Expand Down Expand Up @@ -292,7 +349,9 @@ def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
request = Request.new(Thread.current, sec, exc, message)
state.add_request(request)
begin
return yield(sec)
request.interruptible do
yield(sec)
end
ensure
request.finished
end
Expand Down
21 changes: 21 additions & 0 deletions repro_nesting1.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require 'timeout'

begin
Timeout.timeout(2) do
Timeout.timeout(2) do
sleep 3
end
end
rescue Exception => e
p :HERE
puts ">"+e.full_message
end
puts

begin
Timeout.timeout(0.1) do
p sleep 3
end
rescue Exception => e
p e.class
end