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.
12 This is a simple example.
17 buf.extend(MonitorMixin)
18 empty_cond = buf.new_cond
24 empty_cond.wait_while { buf.empty? }
31 while line = ARGF.gets
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
47 # Adds monitor functionality to an arbitrary object by mixing the module with
48 # +include+. For example:
50 # require 'monitor.rb'
53 # buf.extend(MonitorMixin)
54 # empty_cond = buf.new_cond
60 # empty_cond.wait_while { buf.empty? }
67 # while line = ARGF.gets
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
81 # FIXME: This isn't documented in Nutshell.
83 # Since MonitorMixin.new_cond returns a ConditionVariable, and the example
84 # above calls while_wait and signal, this class should be documented.
86 class ConditionVariable
87 class Timeout < Exception; end
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
93 def wait(timeout = nil)
94 @monitor.instance_eval {mon_check_owner()}
95 timer = create_timer(timeout)
97 Thread.critical = true
98 count = @monitor.instance_eval {mon_exit_for_cond()}
99 @waiters.push(Thread.current)
107 Thread.critical = true
108 if timer && timer.alive?
111 if @waiters.include?(Thread.current) # interrupted?
112 @waiters.delete(Thread.current)
114 @monitor.instance_eval {mon_enter_for_cond(count)}
115 Thread.critical = false
120 # call #wait while the supplied block returns +true+.
127 # call #wait until the supplied block returns +true+.
134 # Wake up and run the next waiter
136 @monitor.instance_eval {mon_check_owner()}
137 Thread.critical = true
140 Thread.critical = false
144 # Wake up all the waiters.
146 @monitor.instance_eval {mon_check_owner()}
147 Thread.critical = true
152 Thread.critical = false
157 return @waiters.length
162 def initialize(monitor)
167 def create_timer(timeout)
169 waiter = Thread.current
170 return Thread.start {
173 Thread.critical = true
174 waiter.raise(Timeout.new)
182 def self.extend_object(obj)
184 obj.instance_eval {mon_initialize()}
188 # Attempts to enter exclusive section. Returns +false+ if lock fails.
192 Thread.critical = true
194 @mon_owner = Thread.current
196 if @mon_owner == Thread.current
200 Thread.critical = false
203 # For backward compatibility
204 alias try_mon_enter mon_try_enter
207 # Enters exclusive section.
210 Thread.critical = true
211 mon_acquire(@mon_entering_queue)
213 Thread.critical = false
217 # Leaves exclusive section.
221 Thread.critical = true
226 Thread.critical = false
231 # Enters exclusive section and executes the block. Leaves the exclusive
232 # section automatically when the block exits. See example under
243 alias synchronize mon_synchronize
246 # FIXME: This isn't documented in Nutshell.
248 # Create a new condition variable for this monitor.
249 # This facilitates control of the monitor with #signal and #wait.
252 return ConditionVariable.new(self)
257 def initialize(*args)
262 # called by initialize method to set defaults for instance variables.
266 @mon_entering_queue = []
267 @mon_waiting_queue = []
270 # Throw a ThreadError exception if the current thread
271 # does't own the monitor
273 if @mon_owner != Thread.current
274 raise ThreadError, "current thread not owner"
278 def mon_acquire(queue)
279 while @mon_owner && @mon_owner != Thread.current
280 queue.push(Thread.current)
282 Thread.critical = true
284 @mon_owner = Thread.current
289 t = @mon_waiting_queue.shift
290 t = @mon_entering_queue.shift unless t
294 def mon_enter_for_cond(count)
295 mon_acquire(@mon_waiting_queue)
299 def mon_exit_for_cond
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.
320 alias try_enter try_mon_enter
321 alias enter mon_enter
326 # Documentation comments:
327 # - All documentation comes from Nutshell.
328 # - MonitorMixin.new_cond appears in the example, but is not documented in
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
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