Change soft-fail to use the config, rather than env
[rbx.git] / stdlib / monitor.rb
blob4ed4e412223cdd44fcee11a18350e7f41964ec8a
1 =begin
3 = monitor.rb
5 Copyright (C) 2001  Shugo Maeda <shugo@ruby-lang.org>
7 This library is distributed under the terms of the Ruby license.
8 You can freely distribute/modify this library.
10 == example
12 This is a simple example.
14   require 'monitor.rb'
15   
16   buf = []
17   buf.extend(MonitorMixin)
18   empty_cond = buf.new_cond
19   
20   # consumer
21   Thread.start do
22     loop do
23       buf.synchronize do
24         empty_cond.wait_while { buf.empty? }
25         print buf.shift
26       end
27     end
28   end
29   
30   # producer
31   while line = ARGF.gets
32     buf.synchronize do
33       buf.push(line)
34       empty_cond.signal
35     end
36   end
38 The consumer thread waits for the producer thread to push a line
39 to buf while buf.empty?, and the producer thread (main thread)
40 reads a line from ARGF and push it to buf, then call
41 empty_cond.signal.
43 =end
44   
47 # Adds monitor functionality to an arbitrary object by mixing the module with
48 # +include+.  For example:
50 #    require 'monitor.rb'
51 #    
52 #    buf = []
53 #    buf.extend(MonitorMixin)
54 #    empty_cond = buf.new_cond
55 #    
56 #    # consumer
57 #    Thread.start do
58 #      loop do
59 #        buf.synchronize do
60 #          empty_cond.wait_while { buf.empty? }
61 #          print buf.shift
62 #        end
63 #      end
64 #    end
65 #    
66 #    # producer
67 #    while line = ARGF.gets
68 #      buf.synchronize do
69 #        buf.push(line)
70 #        empty_cond.signal
71 #      end
72 #    end
73
74 # The consumer thread waits for the producer thread to push a line
75 # to buf while buf.empty?, and the producer thread (main thread)
76 # reads a line from ARGF and push it to buf, then call
77 # empty_cond.signal.
79 module MonitorMixin
80   #
81   # FIXME: This isn't documented in Nutshell.
82   #
83   # Since MonitorMixin.new_cond returns a ConditionVariable, and the example
84   # above calls while_wait and signal, this class should be documented.
85   #
86   class ConditionVariable
87     class Timeout < Exception; end
88     
89     # Create a new timer with the argument timeout, and add the
90     # current thread to the list of waiters.  Then the thread is
91     # stopped.  It will be resumed when a corresponding #signal 
92     # occurs.
93     def wait(timeout = nil)
94       @monitor.instance_eval {mon_check_owner()}
95       timer = create_timer(timeout)
96       
97       Thread.critical = true
98       count = @monitor.instance_eval {mon_exit_for_cond()}
99       @waiters.push(Thread.current)
101       begin
102         Thread.stop
103         return true
104       rescue Timeout
105         return false
106       ensure
107         Thread.critical = true
108         if timer && timer.alive?
109           Thread.kill(timer)
110         end
111         if @waiters.include?(Thread.current)  # interrupted?
112           @waiters.delete(Thread.current)
113         end
114         @monitor.instance_eval {mon_enter_for_cond(count)}
115         Thread.critical = false
116       end
117     end
118     
120     # call #wait while the supplied block returns +true+.
121     def wait_while
122       while yield
123         wait
124       end
125     end
126     
127     # call #wait until the supplied block returns +true+.
128     def wait_until
129       until yield
130         wait
131       end
132     end
133     
134     # Wake up and run the next waiter
135     def signal
136       @monitor.instance_eval {mon_check_owner()}
137       Thread.critical = true
138       t = @waiters.shift
139       t.wakeup if t
140       Thread.critical = false
141       Thread.pass
142     end
143     
144     # Wake up all the waiters.
145     def broadcast
146       @monitor.instance_eval {mon_check_owner()}
147       Thread.critical = true
148       for t in @waiters
149         t.wakeup
150       end
151       @waiters.clear
152       Thread.critical = false
153       Thread.pass
154     end
155     
156     def count_waiters
157       return @waiters.length
158     end
159     
160     private
162     def initialize(monitor)
163       @monitor = monitor
164       @waiters = []
165     end
167     def create_timer(timeout)
168       if timeout
169         waiter = Thread.current
170         return Thread.start {
171           Thread.pass
172           sleep(timeout)
173           Thread.critical = true
174           waiter.raise(Timeout.new)
175         }
176       else
177         return nil
178       end
179     end
180   end
181   
182   def self.extend_object(obj)
183     super(obj)
184     obj.instance_eval {mon_initialize()}
185   end
186   
187   #
188   # Attempts to enter exclusive section.  Returns +false+ if lock fails.
189   #
190   def mon_try_enter
191     result = false
192     Thread.critical = true
193     if @mon_owner.nil?
194       @mon_owner = Thread.current
195     end
196     if @mon_owner == Thread.current
197       @mon_count += 1
198       result = true
199     end
200     Thread.critical = false
201     return result
202   end
203   # For backward compatibility
204   alias try_mon_enter mon_try_enter
206   #
207   # Enters exclusive section.
208   #
209   def mon_enter
210     Thread.critical = true
211     mon_acquire(@mon_entering_queue)
212     @mon_count += 1
213     Thread.critical = false
214   end
215   
216   #
217   # Leaves exclusive section.
218   #
219   def mon_exit
220     mon_check_owner
221     Thread.critical = true
222     @mon_count -= 1
223     if @mon_count == 0
224       mon_release
225     end
226     Thread.critical = false
227     Thread.pass
228   end
230   #
231   # Enters exclusive section and executes the block.  Leaves the exclusive
232   # section automatically when the block exits.  See example under
233   # +MonitorMixin+.
234   #
235   def mon_synchronize
236     mon_enter
237     begin
238       yield
239     ensure
240       mon_exit
241     end
242   end
243   alias synchronize mon_synchronize
244   
245   #
246   # FIXME: This isn't documented in Nutshell.
247   # 
248   # Create a new condition variable for this monitor.
249   # This facilitates control of the monitor with #signal and #wait.
250   #
251   def new_cond
252     return ConditionVariable.new(self)
253   end
255   private
257   def initialize(*args)
258     super
259     mon_initialize
260   end
262   # called by initialize method to set defaults for instance variables.
263   def mon_initialize
264     @mon_owner = nil
265     @mon_count = 0
266     @mon_entering_queue = []
267     @mon_waiting_queue = []
268   end
270   # Throw a ThreadError exception if the current thread
271   # does't own the monitor
272   def mon_check_owner
273     if @mon_owner != Thread.current
274       raise ThreadError, "current thread not owner"
275     end
276   end
278   def mon_acquire(queue)
279     while @mon_owner && @mon_owner != Thread.current
280       queue.push(Thread.current)
281       Thread.stop
282       Thread.critical = true
283     end
284     @mon_owner = Thread.current
285   end
287   def mon_release
288     @mon_owner = nil
289     t = @mon_waiting_queue.shift
290     t = @mon_entering_queue.shift unless t
291     t.wakeup if t
292   end
294   def mon_enter_for_cond(count)
295     mon_acquire(@mon_waiting_queue)
296     @mon_count = count
297   end
299   def mon_exit_for_cond
300     count = @mon_count
301     @mon_count = 0
302     mon_release
303     return count
304   end
307 # Monitors provide means of mutual exclusion for Thread programming.
308 # A critical region is created by means of the synchronize method,
309 # which takes a block.
310 # The condition variables (created with #new_cond) may be used 
311 # to control the execution of a monitor with #signal and #wait.
313 # the Monitor class wraps MonitorMixin, and provides aliases
314 #  alias try_enter try_mon_enter
315 #  alias enter mon_enter
316 #  alias exit mon_exit
317 # to access its methods more concisely.
318 class Monitor
319   include MonitorMixin
320   alias try_enter try_mon_enter
321   alias enter mon_enter
322   alias exit mon_exit
326 # Documentation comments:
327 #  - All documentation comes from Nutshell.
328 #  - MonitorMixin.new_cond appears in the example, but is not documented in
329 #    Nutshell.
330 #  - All the internals (internal modules Accessible and Initializable, class
331 #    ConditionVariable) appear in RDoc.  It might be good to hide them, by
332 #    making them private, or marking them :nodoc:, etc.
333 #  - The entire example from the RD section at the top is replicated in the RDoc
334 #    comment for MonitorMixin.  Does the RD section need to remain?
335 #  - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
336 #    not synchronize.
337 #  - mon_owner is in Nutshell, but appears as an accessor in a separate module
338 #    here, so is hard/impossible to RDoc.  Some other useful accessors
339 #    (mon_count and some queue stuff) are also in this module, and don't appear
340 #    directly in the RDoc output.
341 #  - in short, it may be worth changing the code layout in this file to make the
342 #    documentation easier
344 # Local variables:
345 # mode: Ruby
346 # tab-width: 8
347 # End: