* include/ruby/io.h (rb_io_t): new fields: writeconv,
[ruby-svn.git] / lib / monitor.rb
blob31234819b81c2fd80c71022b449c5cdc04f17d02
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
45 require 'thread'
48 # Adds monitor functionality to an arbitrary object by mixing the module with
49 # +include+.  For example:
51 #    require 'monitor'
52 #    
53 #    buf = []
54 #    buf.extend(MonitorMixin)
55 #    empty_cond = buf.new_cond
56 #    
57 #    # consumer
58 #    Thread.start do
59 #      loop do
60 #        buf.synchronize do
61 #          empty_cond.wait_while { buf.empty? }
62 #          print buf.shift
63 #        end
64 #      end
65 #    end
66 #    
67 #    # producer
68 #    while line = ARGF.gets
69 #      buf.synchronize do
70 #        buf.push(line)
71 #        empty_cond.signal
72 #      end
73 #    end
74
75 # The consumer thread waits for the producer thread to push a line
76 # to buf while buf.empty?, and the producer thread (main thread)
77 # reads a line from ARGF and push it to buf, then call
78 # empty_cond.signal.
80 module MonitorMixin
81   #
82   # FIXME: This isn't documented in Nutshell.
83   #
84   # Since MonitorMixin.new_cond returns a ConditionVariable, and the example
85   # above calls while_wait and signal, this class should be documented.
86   #
87   class ConditionVariable
88     class Timeout < Exception; end
89     
90     def wait(timeout = nil)
91       if timeout
92         raise NotImplementedError, "timeout is not implemented yet"
93       end
94       @monitor.send(:mon_check_owner)
95       count = @monitor.send(:mon_exit_for_cond)
96       begin
97         @cond.wait(@monitor.instance_variable_get("@mon_mutex"))
98         return true
99       ensure
100         @monitor.send(:mon_enter_for_cond, count)
101       end
102     end
103     
104     def wait_while
105       while yield
106         wait
107       end
108     end
109     
110     def wait_until
111       until yield
112         wait
113       end
114     end
115     
116     def signal
117       @monitor.send(:mon_check_owner)
118       @cond.signal
119     end
120     
121     def broadcast
122       @monitor.send(:mon_check_owner)
123       @cond.broadcast
124     end
125     
126     def count_waiters
127       raise NotImplementedError
128     end
129     
130     private
132     def initialize(monitor)
133       @monitor = monitor
134       @cond = ::ConditionVariable.new
135     end
136   end
137   
138   def self.extend_object(obj)
139     super(obj)
140     obj.send(:mon_initialize)
141   end
142   
143   #
144   # Attempts to enter exclusive section.  Returns +false+ if lock fails.
145   #
146   def mon_try_enter
147     if @mon_owner != Thread.current
148       unless @mon_mutex.try_lock
149         return false
150       end
151       @mon_owner = Thread.current
152     end
153     @mon_count += 1
154     return true
155   end
156   # For backward compatibility
157   alias try_mon_enter mon_try_enter
159   #
160   # Enters exclusive section.
161   #
162   def mon_enter
163     if @mon_owner != Thread.current
164       @mon_mutex.lock
165       @mon_owner = Thread.current
166     end
167     @mon_count += 1
168   end
169   
170   #
171   # Leaves exclusive section.
172   #
173   def mon_exit
174     mon_check_owner
175     @mon_count -=1
176     if @mon_count == 0
177       @mon_owner = nil
178       @mon_mutex.unlock
179     end
180   end
182   #
183   # Enters exclusive section and executes the block.  Leaves the exclusive
184   # section automatically when the block exits.  See example under
185   # +MonitorMixin+.
186   #
187   def mon_synchronize
188     mon_enter
189     begin
190       yield
191     ensure
192       mon_exit
193     end
194   end
195   alias synchronize mon_synchronize
196   
197   #
198   # FIXME: This isn't documented in Nutshell.
199   #
200   def new_cond
201     return ConditionVariable.new(self)
202   end
204   private
206   def initialize(*args)
207     super
208     mon_initialize
209   end
211   def mon_initialize
212     @mon_owner = nil
213     @mon_count = 0
214     @mon_mutex = Mutex.new
215   end
217   def mon_check_owner
218     if @mon_owner != Thread.current
219       raise ThreadError, "current thread not owner"
220     end
221   end
223   def mon_enter_for_cond(count)
224     @mon_owner = Thread.current
225     @mon_count = count
226   end
228   def mon_exit_for_cond
229     count = @mon_count
230     @mon_owner = nil
231     @mon_count = 0
232     return count
233   end
236 class Monitor
237   include MonitorMixin
238   alias try_enter try_mon_enter
239   alias enter mon_enter
240   alias exit mon_exit
244 # Documentation comments:
245 #  - All documentation comes from Nutshell.
246 #  - MonitorMixin.new_cond appears in the example, but is not documented in
247 #    Nutshell.
248 #  - All the internals (internal modules Accessible and Initializable, class
249 #    ConditionVariable) appear in RDoc.  It might be good to hide them, by
250 #    making them private, or marking them :nodoc:, etc.
251 #  - The entire example from the RD section at the top is replicated in the RDoc
252 #    comment for MonitorMixin.  Does the RD section need to remain?
253 #  - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
254 #    not synchronize.
255 #  - mon_owner is in Nutshell, but appears as an accessor in a separate module
256 #    here, so is hard/impossible to RDoc.  Some other useful accessors
257 #    (mon_count and some queue stuff) are also in this module, and don't appear
258 #    directly in the RDoc output.
259 #  - in short, it may be worth changing the code layout in this file to make the
260 #    documentation easier
262 # Local variables:
263 # mode: Ruby
264 # tab-width: 8
265 # End: