PageRenderTime 120ms CodeModel.GetById 90ms app.highlight 26ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/Ruby/lib/ruby/1.8/monitor.rb

http://github.com/agross/netopenspace
Ruby | 356 lines | 177 code | 32 blank | 147 comment | 20 complexity | 1c08cadb6646bbef771c0153e920a5b5 MD5 | raw file
  1=begin
  2
  3= monitor.rb
  4
  5Copyright (C) 2001  Shugo Maeda <shugo@ruby-lang.org>
  6
  7This library is distributed under the terms of the Ruby license.
  8You can freely distribute/modify this library.
  9
 10== example
 11
 12This is a simple example.
 13
 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
 37
 38The consumer thread waits for the producer thread to push a line
 39to buf while buf.empty?, and the producer thread (main thread)
 40reads a line from ARGF and push it to buf, then call
 41empty_cond.signal.
 42
 43=end
 44  
 45
 46#
 47# Adds monitor functionality to an arbitrary object by mixing the module with
 48# +include+.  For example:
 49#
 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.
 78#
 79module 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)
100
101      begin
102	Thread.stop
103        return true
104      rescue Timeout
105        return false
106      ensure
107	Thread.critical = true
108	begin
109	  if timer && timer.alive?
110	    Thread.kill(timer)
111	  end
112	  if @waiters.include?(Thread.current)  # interrupted?
113	    @waiters.delete(Thread.current)
114	  end
115	  @monitor.instance_eval {mon_enter_for_cond(count)}
116	ensure
117	  Thread.critical = false
118	end
119      end
120    end
121    
122
123    # call #wait while the supplied block returns +true+.
124    def wait_while
125      while yield
126	wait
127      end
128    end
129    
130    # call #wait until the supplied block returns +true+.
131    def wait_until
132      until yield
133	wait
134      end
135    end
136    
137    # Wake up and run the next waiter
138    def signal
139      @monitor.instance_eval {mon_check_owner()}
140      Thread.critical = true
141      t = @waiters.shift
142      t.wakeup if t
143      Thread.critical = false
144      Thread.pass
145    end
146    
147    # Wake up all the waiters.
148    def broadcast
149      @monitor.instance_eval {mon_check_owner()}
150      Thread.critical = true
151      for t in @waiters
152	t.wakeup
153      end
154      @waiters.clear
155      Thread.critical = false
156      Thread.pass
157    end
158    
159    def count_waiters
160      return @waiters.length
161    end
162    
163    private
164
165    def initialize(monitor)
166      @monitor = monitor
167      @waiters = []
168    end
169
170    def create_timer(timeout)
171      if timeout
172	waiter = Thread.current
173	return Thread.start {
174	  Thread.pass
175	  sleep(timeout)
176	  Thread.critical = true
177	  waiter.raise(Timeout.new)
178	}
179      else
180        return nil
181      end
182    end
183  end
184  
185  def self.extend_object(obj)
186    super(obj)
187    obj.instance_eval {mon_initialize()}
188  end
189  
190  #
191  # Attempts to enter exclusive section.  Returns +false+ if lock fails.
192  #
193  def mon_try_enter
194    result = false
195    Thread.critical = true
196    if @mon_owner.nil?
197      @mon_owner = Thread.current
198    end
199    if @mon_owner == Thread.current
200      @mon_count += 1
201      result = true
202    end
203    Thread.critical = false
204    return result
205  end
206  # For backward compatibility
207  alias try_mon_enter mon_try_enter
208
209  #
210  # Enters exclusive section.
211  #
212  def mon_enter
213    Thread.critical = true
214    mon_acquire(@mon_entering_queue)
215    @mon_count += 1
216  ensure
217    Thread.critical = false
218  end
219  
220  #
221  # Leaves exclusive section.
222  #
223  def mon_exit
224    mon_check_owner
225    Thread.critical = true
226    @mon_count -= 1
227    if @mon_count == 0
228      mon_release
229    end
230    Thread.critical = false
231    Thread.pass
232  end
233
234  #
235  # Enters exclusive section and executes the block.  Leaves the exclusive
236  # section automatically when the block exits.  See example under
237  # +MonitorMixin+.
238  #
239  def mon_synchronize
240    mon_enter
241    begin
242      yield
243    ensure
244      mon_exit
245    end
246  end
247  alias synchronize mon_synchronize
248  
249  #
250  # FIXME: This isn't documented in Nutshell.
251  # 
252  # Create a new condition variable for this monitor.
253  # This facilitates control of the monitor with #signal and #wait.
254  #
255  def new_cond
256    return ConditionVariable.new(self)
257  end
258
259  private
260
261  def initialize(*args)
262    super
263    mon_initialize
264  end
265
266  # called by initialize method to set defaults for instance variables.
267  def mon_initialize
268    @mon_owner = nil
269    @mon_count = 0
270    @mon_entering_queue = []
271    @mon_waiting_queue = []
272  end
273
274  # Throw a ThreadError exception if the current thread
275  # does't own the monitor
276  def mon_check_owner
277    if @mon_owner != Thread.current
278      raise ThreadError, "current thread not owner"
279    end
280  end
281
282  def mon_acquire(queue)
283    while @mon_owner && @mon_owner != Thread.current
284      queue.push(Thread.current)
285      Thread.stop
286      Thread.critical = true
287    end
288    @mon_owner = Thread.current
289  end
290
291  # mon_release requires Thread.critical == true
292  def mon_release
293    @mon_owner = nil
294    while t = @mon_waiting_queue.shift || @mon_entering_queue.shift
295      if t.alive?
296        t.wakeup
297        return
298      end
299    end
300  end
301
302  def mon_enter_for_cond(count)
303    mon_acquire(@mon_waiting_queue)
304    @mon_count = count
305  end
306
307  def mon_exit_for_cond
308    count = @mon_count
309    @mon_count = 0
310    return count
311  ensure
312    mon_release
313  end
314end
315
316# Monitors provide means of mutual exclusion for Thread programming.
317# A critical region is created by means of the synchronize method,
318# which takes a block.
319# The condition variables (created with #new_cond) may be used 
320# to control the execution of a monitor with #signal and #wait.
321#
322# the Monitor class wraps MonitorMixin, and provides aliases
323#  alias try_enter try_mon_enter
324#  alias enter mon_enter
325#  alias exit mon_exit
326# to access its methods more concisely.
327class Monitor
328  include MonitorMixin
329  alias try_enter try_mon_enter
330  alias enter mon_enter
331  alias exit mon_exit
332end
333
334
335# Documentation comments:
336#  - All documentation comes from Nutshell.
337#  - MonitorMixin.new_cond appears in the example, but is not documented in
338#    Nutshell.
339#  - All the internals (internal modules Accessible and Initializable, class
340#    ConditionVariable) appear in RDoc.  It might be good to hide them, by
341#    making them private, or marking them :nodoc:, etc.
342#  - The entire example from the RD section at the top is replicated in the RDoc
343#    comment for MonitorMixin.  Does the RD section need to remain?
344#  - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
345#    not synchronize.
346#  - mon_owner is in Nutshell, but appears as an accessor in a separate module
347#    here, so is hard/impossible to RDoc.  Some other useful accessors
348#    (mon_count and some queue stuff) are also in this module, and don't appear
349#    directly in the RDoc output.
350#  - in short, it may be worth changing the code layout in this file to make the
351#    documentation easier
352
353# Local variables:
354# mode: Ruby
355# tab-width: 8
356# End: