summaryrefslogtreecommitdiff
path: root/ruby_1_8_5/lib/monitor.rb
diff options
context:
space:
mode:
Diffstat (limited to 'ruby_1_8_5/lib/monitor.rb')
-rw-r--r--ruby_1_8_5/lib/monitor.rb352
1 files changed, 352 insertions, 0 deletions
diff --git a/ruby_1_8_5/lib/monitor.rb b/ruby_1_8_5/lib/monitor.rb
new file mode 100644
index 0000000000..4c146c8086
--- /dev/null
+++ b/ruby_1_8_5/lib/monitor.rb
@@ -0,0 +1,352 @@
+=begin
+
+= monitor.rb
+
+Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
+
+This library is distributed under the terms of the Ruby license.
+You can freely distribute/modify this library.
+
+== example
+
+This is a simple example.
+
+ require 'monitor.rb'
+
+ buf = []
+ buf.extend(MonitorMixin)
+ empty_cond = buf.new_cond
+
+ # consumer
+ Thread.start do
+ loop do
+ buf.synchronize do
+ empty_cond.wait_while { buf.empty? }
+ print buf.shift
+ end
+ end
+ end
+
+ # producer
+ while line = ARGF.gets
+ buf.synchronize do
+ buf.push(line)
+ empty_cond.signal
+ end
+ end
+
+The consumer thread waits for the producer thread to push a line
+to buf while buf.empty?, and the producer thread (main thread)
+reads a line from ARGF and push it to buf, then call
+empty_cond.signal.
+
+=end
+
+
+#
+# Adds monitor functionality to an arbitrary object by mixing the module with
+# +include+. For example:
+#
+# require 'monitor.rb'
+#
+# buf = []
+# buf.extend(MonitorMixin)
+# empty_cond = buf.new_cond
+#
+# # consumer
+# Thread.start do
+# loop do
+# buf.synchronize do
+# empty_cond.wait_while { buf.empty? }
+# print buf.shift
+# end
+# end
+# end
+#
+# # producer
+# while line = ARGF.gets
+# buf.synchronize do
+# buf.push(line)
+# empty_cond.signal
+# end
+# end
+#
+# The consumer thread waits for the producer thread to push a line
+# to buf while buf.empty?, and the producer thread (main thread)
+# reads a line from ARGF and push it to buf, then call
+# empty_cond.signal.
+#
+module MonitorMixin
+ #
+ # FIXME: This isn't documented in Nutshell.
+ #
+ # Since MonitorMixin.new_cond returns a ConditionVariable, and the example
+ # above calls while_wait and signal, this class should be documented.
+ #
+ class ConditionVariable
+ class Timeout < Exception; end
+
+ # Create a new timer with the argument timeout, and add the
+ # current thread to the list of waiters. Then the thread is
+ # stopped. It will be resumed when a corresponding #signal
+ # occurs.
+ def wait(timeout = nil)
+ @monitor.instance_eval {mon_check_owner()}
+ timer = create_timer(timeout)
+
+ Thread.critical = true
+ count = @monitor.instance_eval {mon_exit_for_cond()}
+ @waiters.push(Thread.current)
+
+ begin
+ Thread.stop
+ return true
+ rescue Timeout
+ return false
+ ensure
+ Thread.critical = true
+ begin
+ if timer && timer.alive?
+ Thread.kill(timer)
+ end
+ if @waiters.include?(Thread.current) # interrupted?
+ @waiters.delete(Thread.current)
+ end
+ @monitor.instance_eval {mon_enter_for_cond(count)}
+ ensure
+ Thread.critical = false
+ end
+ end
+ end
+
+
+ # call #wait while the supplied block returns +true+.
+ def wait_while
+ while yield
+ wait
+ end
+ end
+
+ # call #wait until the supplied block returns +true+.
+ def wait_until
+ until yield
+ wait
+ end
+ end
+
+ # Wake up and run the next waiter
+ def signal
+ @monitor.instance_eval {mon_check_owner()}
+ Thread.critical = true
+ t = @waiters.shift
+ t.wakeup if t
+ Thread.critical = false
+ Thread.pass
+ end
+
+ # Wake up all the waiters.
+ def broadcast
+ @monitor.instance_eval {mon_check_owner()}
+ Thread.critical = true
+ for t in @waiters
+ t.wakeup
+ end
+ @waiters.clear
+ Thread.critical = false
+ Thread.pass
+ end
+
+ def count_waiters
+ return @waiters.length
+ end
+
+ private
+
+ def initialize(monitor)
+ @monitor = monitor
+ @waiters = []
+ end
+
+ def create_timer(timeout)
+ if timeout
+ waiter = Thread.current
+ return Thread.start {
+ Thread.pass
+ sleep(timeout)
+ Thread.critical = true
+ waiter.raise(Timeout.new)
+ }
+ else
+ return nil
+ end
+ end
+ end
+
+ def self.extend_object(obj)
+ super(obj)
+ obj.instance_eval {mon_initialize()}
+ end
+
+ #
+ # Attempts to enter exclusive section. Returns +false+ if lock fails.
+ #
+ def mon_try_enter
+ result = false
+ Thread.critical = true
+ if @mon_owner.nil?
+ @mon_owner = Thread.current
+ end
+ if @mon_owner == Thread.current
+ @mon_count += 1
+ result = true
+ end
+ Thread.critical = false
+ return result
+ end
+ # For backward compatibility
+ alias try_mon_enter mon_try_enter
+
+ #
+ # Enters exclusive section.
+ #
+ def mon_enter
+ Thread.critical = true
+ mon_acquire(@mon_entering_queue)
+ @mon_count += 1
+ ensure
+ Thread.critical = false
+ end
+
+ #
+ # Leaves exclusive section.
+ #
+ def mon_exit
+ mon_check_owner
+ Thread.critical = true
+ @mon_count -= 1
+ if @mon_count == 0
+ mon_release
+ end
+ Thread.critical = false
+ Thread.pass
+ end
+
+ #
+ # Enters exclusive section and executes the block. Leaves the exclusive
+ # section automatically when the block exits. See example under
+ # +MonitorMixin+.
+ #
+ def mon_synchronize
+ mon_enter
+ begin
+ yield
+ ensure
+ mon_exit
+ end
+ end
+ alias synchronize mon_synchronize
+
+ #
+ # FIXME: This isn't documented in Nutshell.
+ #
+ # Create a new condition variable for this monitor.
+ # This facilitates control of the monitor with #signal and #wait.
+ #
+ def new_cond
+ return ConditionVariable.new(self)
+ end
+
+ private
+
+ def initialize(*args)
+ super
+ mon_initialize
+ end
+
+ # called by initialize method to set defaults for instance variables.
+ def mon_initialize
+ @mon_owner = nil
+ @mon_count = 0
+ @mon_entering_queue = []
+ @mon_waiting_queue = []
+ end
+
+ # Throw a ThreadError exception if the current thread
+ # does't own the monitor
+ def mon_check_owner
+ if @mon_owner != Thread.current
+ raise ThreadError, "current thread not owner"
+ end
+ end
+
+ def mon_acquire(queue)
+ while @mon_owner && @mon_owner != Thread.current
+ queue.push(Thread.current)
+ Thread.stop
+ Thread.critical = true
+ end
+ @mon_owner = Thread.current
+ end
+
+ def mon_release
+ @mon_owner = nil
+ t = @mon_waiting_queue.shift
+ t = @mon_entering_queue.shift unless t
+ t.wakeup if t
+ end
+
+ def mon_enter_for_cond(count)
+ mon_acquire(@mon_waiting_queue)
+ @mon_count = count
+ end
+
+ def mon_exit_for_cond
+ count = @mon_count
+ @mon_count = 0
+ return count
+ ensure
+ mon_release
+ end
+end
+
+# Monitors provide means of mutual exclusion for Thread programming.
+# A critical region is created by means of the synchronize method,
+# which takes a block.
+# The condition variables (created with #new_cond) may be used
+# to control the execution of a monitor with #signal and #wait.
+#
+# the Monitor class wraps MonitorMixin, and provides aliases
+# alias try_enter try_mon_enter
+# alias enter mon_enter
+# alias exit mon_exit
+# to access its methods more concisely.
+class Monitor
+ include MonitorMixin
+ alias try_enter try_mon_enter
+ alias enter mon_enter
+ alias exit mon_exit
+end
+
+
+# Documentation comments:
+# - All documentation comes from Nutshell.
+# - MonitorMixin.new_cond appears in the example, but is not documented in
+# Nutshell.
+# - All the internals (internal modules Accessible and Initializable, class
+# ConditionVariable) appear in RDoc. It might be good to hide them, by
+# making them private, or marking them :nodoc:, etc.
+# - The entire example from the RD section at the top is replicated in the RDoc
+# comment for MonitorMixin. Does the RD section need to remain?
+# - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
+# not synchronize.
+# - mon_owner is in Nutshell, but appears as an accessor in a separate module
+# here, so is hard/impossible to RDoc. Some other useful accessors
+# (mon_count and some queue stuff) are also in this module, and don't appear
+# directly in the RDoc output.
+# - in short, it may be worth changing the code layout in this file to make the
+# documentation easier
+
+# Local variables:
+# mode: Ruby
+# tab-width: 8
+# End: