5 Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
6 Copyright (C) 2008 MenTaLguY <mental@rydia.net>
8 This library is distributed under the terms of the Ruby license.
9 You can freely distribute/modify this library.
13 This is a simple example.
18 buf.extend(MonitorMixin)
19 empty_cond = buf.new_cond
25 empty_cond.wait_while { buf.empty? }
32 while line = ARGF.gets
39 The consumer thread waits for the producer thread to push a line
40 to buf while buf.empty?, and the producer thread (main thread)
41 reads a line from ARGF and push it to buf, then call
49 # Adds monitor functionality to an arbitrary object by mixing the module with
50 # +include+. For example:
52 # require 'monitor.rb'
55 # buf.extend(MonitorMixin)
56 # empty_cond = buf.new_cond
62 # empty_cond.wait_while { buf.empty? }
69 # while line = ARGF.gets
76 # The consumer thread waits for the producer thread to push a line
77 # to buf while buf.empty?, and the producer thread (main thread)
78 # reads a line from ARGF and push it to buf, then call
83 # FIXME: This isn't documented in Nutshell.
85 # Since MonitorMixin.new_cond returns a ConditionVariable, and the example
86 # above calls while_wait and signal, this class should be documented.
88 class ConditionVariable
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 condition = @condition
95 @monitor.instance_eval { mon_wait_for_cond(condition, timeout) }
98 # call #wait while the supplied block returns +true+.
105 # call #wait until the supplied block returns +true+.
112 # Wake up and run the next waiter
114 condition = @condition
115 @monitor.instance_eval { mon_signal_cond(condition) }
119 # Wake up all the waiters.
121 condition = @condition
122 @monitor.instance_eval { mon_broadcast_cond(condition) }
127 condition = @condition
128 @monitor.instance_eval { mon_count_cond_waiters(condition) }
133 def initialize(monitor, condition)
135 @condition = condition
139 def self.extend_object(obj)
141 obj.instance_eval {mon_initialize()}
145 # Attempts to enter exclusive section. Returns +false+ if lock fails.
148 @mon_mutex.synchronize do
149 @mon_owner = Thread.current unless @mon_owner
150 if @mon_owner == Thread.current
158 # For backward compatibility
159 alias try_mon_enter mon_try_enter
162 # Enters exclusive section.
165 @mon_mutex.synchronize do
166 mon_acquire(@mon_entering_cond)
172 # Leaves exclusive section.
175 @mon_mutex.synchronize do
178 mon_release if @mon_count.zero?
184 # Enters exclusive section and executes the block. Leaves the exclusive
185 # section automatically when the block exits. See example under
196 alias synchronize mon_synchronize
199 # FIXME: This isn't documented in Nutshell.
201 # Create a new condition variable for this monitor.
202 # This facilitates control of the monitor with #signal and #wait.
205 condition = ::ConditionVariable.new
206 condition.instance_eval { @mon_n_waiters = 0 }
207 return ConditionVariable.new(self, condition)
212 def initialize(*args)
217 # called by initialize method to set defaults for instance variables.
219 @mon_mutex = Mutex.new
222 @mon_total_waiting = 0
223 @mon_entering_cond = ::ConditionVariable.new
224 @mon_waiting_cond = ::ConditionVariable.new
228 # Throw a ThreadError exception if the current thread
229 # does't own the monitor
231 # called with @mon_mutex held
232 if @mon_owner != Thread.current
233 raise ThreadError, "current thread not owner"
237 def mon_acquire(condition)
238 # called with @mon_mutex held
239 while @mon_owner && @mon_owner != Thread.current
240 condition.wait @mon_mutex
242 @mon_owner = Thread.current
246 # called with @mon_mutex held
248 if @mon_total_waiting.nonzero?
249 @mon_waiting_cond.signal
251 @mon_entering_cond.signal
255 def mon_wait_for_cond(condition, timeout)
256 @mon_mutex.synchronize do
260 condition.instance_eval { @mon_n_waiters += 1 }
264 condition.wait(@mon_mutex, timeout)
266 condition.wait(@mon_mutex)
270 @mon_total_waiting += 1
271 # TODO: not interrupt-safe
272 mon_acquire(@mon_waiting_cond)
273 @mon_total_waiting -= 1
275 condition.instance_eval { @mon_n_waiters -= 1 }
280 def mon_signal_cond(condition)
281 @mon_mutex.synchronize do
287 def mon_broadcast_cond(condition)
288 @mon_mutex.synchronize do
294 def mon_count_cond_waiters(condition)
295 @mon_mutex.synchronize do
296 condition.instance_eval { @mon_n_waiters }
301 # Monitors provide means of mutual exclusion for Thread programming.
302 # A critical region is created by means of the synchronize method,
303 # which takes a block.
304 # The condition variables (created with #new_cond) may be used
305 # to control the execution of a monitor with #signal and #wait.
307 # the Monitor class wraps MonitorMixin, and provides aliases
308 # alias try_enter try_mon_enter
309 # alias enter mon_enter
310 # alias exit mon_exit
311 # to access its methods more concisely.
314 alias try_enter try_mon_enter
315 alias enter mon_enter
320 # Documentation comments:
321 # - All documentation comes from Nutshell.
322 # - MonitorMixin.new_cond appears in the example, but is not documented in
324 # - All the internals (internal modules Accessible and Initializable, class
325 # ConditionVariable) appear in RDoc. It might be good to hide them, by
326 # making them private, or marking them :nodoc:, etc.
327 # - The entire example from the RD section at the top is replicated in the RDoc
328 # comment for MonitorMixin. Does the RD section need to remain?
329 # - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
331 # - mon_owner is in Nutshell, but appears as an accessor in a separate module
332 # here, so is hard/impossible to RDoc. Some other useful accessors
333 # (mon_count and some queue stuff) are also in this module, and don't appear
334 # directly in the RDoc output.
335 # - in short, it may be worth changing the code layout in this file to make the
336 # documentation easier