@@ -140,6 +140,57 @@ def add_request(request)
140140 end
141141 private_constant :State
142142
143+ class FiberState
144+ attr_reader :mutex
145+
146+ def initialize
147+ @interruptible = false
148+ @interrupting = false
149+ @mutex = Mutex . new
150+ @condvar = ConditionVariable . new
151+ end
152+
153+ def interruptible
154+ prev = nil
155+ Sync . synchronize @mutex do
156+ prev = @interruptible
157+ @interruptible = true
158+ @condvar . signal
159+ end
160+ begin
161+ yield
162+ ensure
163+ Sync . synchronize @mutex do
164+ @interruptible = prev
165+ @condvar . signal
166+ end
167+ end
168+ end
169+
170+ def start_interrupt ( request )
171+ raise unless @mutex . owned?
172+
173+ until @interruptible
174+ # We hold the Mutex so the Request can't finish,
175+ # except at this wait call, so we check right after
176+ @condvar . wait ( @mutex )
177+ return false if request . done?
178+ end
179+ @interrupting = true
180+ @interruptible = false
181+ true
182+ end
183+
184+ def finished
185+ raise unless @mutex . owned?
186+
187+ if @interrupting
188+ @interrupting = false
189+ end
190+ end
191+ end
192+ private_constant :FiberState
193+
143194 class Request
144195 attr_reader :deadline
145196
@@ -149,13 +200,18 @@ def initialize(thread, timeout, exception_class, message)
149200 @exception_class = exception_class
150201 @message = message
151202
152- @mutex = Mutex . new
153203 @done = false # protected by @mutex
204+ @fiber_state = ( Thread . current [ :timeout_interrupt_queue ] ||= FiberState . new )
205+ @mutex = @fiber_state . mutex
206+ end
207+
208+ def interruptible ( &block )
209+ @fiber_state . interruptible ( &block )
154210 end
155211
156- # Only called by the timeout thread, so does not need Sync.synchronize
157212 def done?
158- @mutex . synchronize do
213+ return @done if @mutex . owned?
214+ Sync . synchronize @mutex do
159215 @done
160216 end
161217 end
@@ -167,16 +223,17 @@ def expired?(now)
167223 # Only called by the timeout thread, so does not need Sync.synchronize
168224 def interrupt
169225 @mutex . synchronize do
170- unless @done
171- @thread . raise @exception_class , @message
172- @done = true
173- end
226+ return if @done
227+ return unless @fiber_state . start_interrupt ( self )
228+ @thread . raise @exception_class , @message
229+ @done = true
174230 end
175231 end
176232
177233 def finished
178234 Sync . synchronize @mutex do
179235 @done = true
236+ @fiber_state . finished
180237 end
181238 end
182239 end
@@ -292,7 +349,9 @@ def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
292349 request = Request . new ( Thread . current , sec , exc , message )
293350 state . add_request ( request )
294351 begin
295- return yield ( sec )
352+ request . interruptible do
353+ yield ( sec )
354+ end
296355 ensure
297356 request . finished
298357 end
0 commit comments