1- # frozen_string_literal: false  
1+ # frozen_string_literal: true  
22# Timeout long-running blocks 
33# 
44# == Synopsis 
2323# Copyright:: (C) 2000  Information-technology Promotion Agency, Japan 
2424
2525module  Timeout 
26-   VERSION  =  "0.2.0" . freeze 
26+   VERSION  =  "0.2.0" 
2727
2828  # Raised by Timeout.timeout when the block times out. 
2929  class  Error  < RuntimeError 
@@ -50,9 +50,79 @@ def exception(*)
5050  end 
5151
5252  # :stopdoc: 
53-   THIS_FILE  =  /\A #{ Regexp . quote ( __FILE__ ) }  :/o 
54-   CALLER_OFFSET  =  ( ( c  =  caller [ 0 ] )  && THIS_FILE  =~ c )  ? 1  : 0 
55-   private_constant  :THIS_FILE ,  :CALLER_OFFSET 
53+   CONDVAR  =  ConditionVariable . new 
54+   QUEUE  =  Queue . new 
55+   QUEUE_MUTEX  =  Mutex . new 
56+   TIMEOUT_THREAD_MUTEX  =  Mutex . new 
57+   @timeout_thread  =  nil 
58+   private_constant  :CONDVAR ,  :QUEUE ,  :QUEUE_MUTEX ,  :TIMEOUT_THREAD_MUTEX 
59+ 
60+   class  Request 
61+     attr_reader  :deadline 
62+ 
63+     def  initialize ( thread ,  timeout ,  exception_class ,  message ) 
64+       @thread  =  thread 
65+       @deadline  =  Process . clock_gettime ( Process ::CLOCK_MONOTONIC )  + timeout 
66+       @exception_class  =  exception_class 
67+       @message  =  message 
68+ 
69+       @mutex  =  Mutex . new 
70+       @done  =  false 
71+     end 
72+ 
73+     def  done? 
74+       @done 
75+     end 
76+ 
77+     def  expired? ( now ) 
78+       now  >= @deadline  and  !done? 
79+     end 
80+ 
81+     def  interrupt 
82+       @mutex . synchronize  do 
83+         unless  @done 
84+           @thread . raise  @exception_class ,  @message 
85+           @done  =  true 
86+         end 
87+       end 
88+     end 
89+ 
90+     def  finished 
91+       @mutex . synchronize  do 
92+         @done  =  true 
93+       end 
94+     end 
95+   end 
96+   private_constant  :Request 
97+ 
98+   def  self . ensure_timeout_thread_created 
99+     unless  @timeout_thread 
100+       TIMEOUT_THREAD_MUTEX . synchronize  do 
101+         @timeout_thread  ||= Thread . new  do 
102+           requests  =  [ ] 
103+           while  true 
104+             until  QUEUE . empty?  and  !requests . empty?  # wait to have at least one request 
105+               req  =  QUEUE . pop 
106+               requests  << req  unless  req . done? 
107+             end 
108+             closest_deadline  =  requests . min_by ( &:deadline ) . deadline 
109+ 
110+             now  =  0.0 
111+             QUEUE_MUTEX . synchronize  do 
112+               while  ( now  =  Process . clock_gettime ( Process ::CLOCK_MONOTONIC ) )  < closest_deadline  and  QUEUE . empty? 
113+                 CONDVAR . wait ( QUEUE_MUTEX ,  closest_deadline  - now ) 
114+               end 
115+             end 
116+ 
117+             requests . each  do  |req |
118+               req . interrupt  if  req . expired? ( now ) 
119+             end 
120+             requests . reject! ( &:done? ) 
121+           end 
122+         end 
123+       end 
124+     end 
125+   end 
56126  # :startdoc: 
57127
58128  # Perform an operation in a block, raising an error if it takes longer than 
@@ -83,51 +153,32 @@ def exception(*)
83153  def  timeout ( sec ,  klass  =  nil ,  message  =  nil ,  &block )    #:yield: +sec+ 
84154    return  yield ( sec )  if  sec  == nil  or  sec . zero? 
85155
86-     message  ||= "execution expired" . freeze 
156+     message  ||= "execution expired" 
87157
88158    if  Fiber . respond_to? ( :current_scheduler )  && ( scheduler  =  Fiber . current_scheduler ) &.respond_to? ( :timeout_after ) 
89159      return  scheduler . timeout_after ( sec ,  klass  || Error ,  message ,  &block ) 
90160    end 
91161
92-     from  =  "from #{ caller_locations ( 1 ,  1 ) [ 0 ] }  "  if  $DEBUG
93-     e  =  Error 
94-     bl  =  proc  do  |exception |
162+     Timeout . ensure_timeout_thread_created 
163+     perform  =  Proc . new  do  |exc |
164+       request  =  Request . new ( Thread . current ,  sec ,  exc ,  message ) 
165+       QUEUE_MUTEX . synchronize  do 
166+         QUEUE  << request 
167+         CONDVAR . signal 
168+       end 
95169      begin 
96-         x  =  Thread . current 
97-         y  =  Thread . start  { 
98-           Thread . current . name  =  from 
99-           begin 
100-             sleep  sec 
101-           rescue  =>  e 
102-             x . raise  e 
103-           else 
104-             x . raise  exception ,  message 
105-           end 
106-         } 
107170        return  yield ( sec ) 
108171      ensure 
109-         if  y 
110-           y . kill 
111-           y . join  # make sure y is dead. 
112-         end 
172+         request . finished 
113173      end 
114174    end 
175+ 
115176    if  klass 
116-       begin 
117-         bl . call ( klass ) 
118-       rescue  klass  =>  e 
119-         message  =  e . message 
120-         bt  =  e . backtrace 
121-       end 
177+       perform . call ( klass ) 
122178    else 
123-       bt  =  Error . catch ( message ,  &bl ) 
179+       backtrace  =  Error . catch ( &perform ) 
180+       raise  Error ,  message ,  backtrace 
124181    end 
125-     level  =  -caller ( CALLER_OFFSET ) . size -2 
126-     while  THIS_FILE  =~ bt [ level ] 
127-       bt . delete_at ( level ) 
128-     end 
129-     raise ( e ,  message ,  bt ) 
130182  end 
131- 
132183  module_function  :timeout 
133184end 
0 commit comments