rubysl-monitor 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0611fdd2b6a1443e13577664e38ea505b906bfab
4
- data.tar.gz: fcf2eaf0f16be03c284b10166cc33c658922d93e
3
+ metadata.gz: 7362242f7582618baaf408e9c8eb46b1bc5c85e2
4
+ data.tar.gz: bb1f3e3984987a4143417554aa7526e59b76d54e
5
5
  SHA512:
6
- metadata.gz: 942b331aab1098f3a7de9f8e3295bbec7e550507473f941861e37766ae2a699a428e25011a7aef8e51e52db7653a5d0bd9382315e1ec9e279990f8d93233e910
7
- data.tar.gz: ad8e8567390e5a64d68ddeeeccad617e77a66cd540f13d33eb9a3aad4546da8d60aa2f5946bcd9b56e3c9d2fe2ec92db5185b8bb41ed1be632584123f6e226a9
6
+ metadata.gz: 9f7a6cca87b357fc057709c176dfb7c7104d6fca0e6c3b446c4768c6045933111f88aa54184726f75467d8b9d9c8a0df00b8dc4eacb41e833c2419046e98b1fa
7
+ data.tar.gz: 94bc9048a65ea57066ef16059b37aa07b1d973ba795da46693acdfe0d43fffc2b033b8ebfe6c0f23bc36d6105ad38c6271c94214bd5bee4f9df6ae3507e21aa2
@@ -3,5 +3,5 @@ env:
3
3
  - RUBYLIB=lib
4
4
  script: bundle exec mspec
5
5
  rvm:
6
- - 1.8.7
7
- - rbx-nightly-18mode
6
+ - 1.9.3
7
+ - rbx-nightly-19mode
@@ -1,82 +1,91 @@
1
- =begin
2
-
3
- = monitor.rb
4
-
5
- Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
6
- Copyright (C) 2008 MenTaLguY <mental@rydia.net>
7
-
8
- This library is distributed under the terms of the Ruby license.
9
- You can freely distribute/modify this library.
10
-
11
- == example
12
-
13
- This is a simple example.
14
-
15
- require 'monitor.rb'
16
-
17
- buf = []
18
- buf.extend(MonitorMixin)
19
- empty_cond = buf.new_cond
20
-
21
- # consumer
22
- Thread.start do
23
- loop do
24
- buf.synchronize do
25
- empty_cond.wait_while { buf.empty? }
26
- print buf.shift
27
- end
28
- end
29
- end
30
-
31
- # producer
32
- while line = ARGF.gets
33
- buf.synchronize do
34
- buf.push(line)
35
- empty_cond.signal
36
- end
37
- end
38
-
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
42
- empty_cond.signal.
1
+ # = monitor.rb
2
+ #
3
+ # Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
4
+ #
5
+ # This library is distributed under the terms of the Ruby license.
6
+ # You can freely distribute/modify this library.
7
+ #
43
8
 
44
- =end
45
-
46
9
  require 'thread'
47
10
 
48
11
  #
49
- # Adds monitor functionality to an arbitrary object by mixing the module with
50
- # +include+. For example:
12
+ # In concurrent programming, a monitor is an object or module intended to be
13
+ # used safely by more than one thread. The defining characteristic of a
14
+ # monitor is that its methods are executed with mutual exclusion. That is, at
15
+ # each point in time, at most one thread may be executing any of its methods.
16
+ # This mutual exclusion greatly simplifies reasoning about the implementation
17
+ # of monitors compared to reasoning about parallel code that updates a data
18
+ # structure.
19
+ #
20
+ # You can read more about the general principles on the Wikipedia page for
21
+ # Monitors[http://en.wikipedia.org/wiki/Monitor_%28synchronization%29]
22
+ #
23
+ # == Examples
24
+ #
25
+ # === Simple object.extend
51
26
  #
52
- # require 'monitor.rb'
53
- #
54
- # buf = []
55
- # buf.extend(MonitorMixin)
56
- # empty_cond = buf.new_cond
57
- #
58
- # # consumer
59
- # Thread.start do
60
- # loop do
61
- # buf.synchronize do
62
- # empty_cond.wait_while { buf.empty? }
63
- # print buf.shift
64
- # end
65
- # end
66
- # end
67
- #
68
- # # producer
69
- # while line = ARGF.gets
70
- # buf.synchronize do
71
- # buf.push(line)
72
- # empty_cond.signal
73
- # end
74
- # end
75
- #
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
79
- # empty_cond.signal.
27
+ # require 'monitor.rb'
28
+ #
29
+ # buf = []
30
+ # buf.extend(MonitorMixin)
31
+ # empty_cond = buf.new_cond
32
+ #
33
+ # # consumer
34
+ # Thread.start do
35
+ # loop do
36
+ # buf.synchronize do
37
+ # empty_cond.wait_while { buf.empty? }
38
+ # print buf.shift
39
+ # end
40
+ # end
41
+ # end
42
+ #
43
+ # # producer
44
+ # while line = ARGF.gets
45
+ # buf.synchronize do
46
+ # buf.push(line)
47
+ # empty_cond.signal
48
+ # end
49
+ # end
50
+ #
51
+ # The consumer thread waits for the producer thread to push a line to buf
52
+ # while <tt>buf.empty?</tt>. The producer thread (main thread) reads a
53
+ # line from ARGF and pushes it into buf then calls <tt>empty_cond.signal</tt>
54
+ # to notify the consumer thread of new data.
55
+ #
56
+ # === Simple Class include
57
+ #
58
+ # require 'monitor'
59
+ #
60
+ # class SynchronizedArray < Array
61
+ #
62
+ # include MonitorMixin
63
+ #
64
+ # def initialize(*args)
65
+ # super(*args)
66
+ # end
67
+ #
68
+ # alias :old_shift :shift
69
+ # alias :old_unshift :unshift
70
+ #
71
+ # def shift(n=1)
72
+ # self.synchronize do
73
+ # self.old_shift(n)
74
+ # end
75
+ # end
76
+ #
77
+ # def unshift(item)
78
+ # self.synchronize do
79
+ # self.old_unshift(item)
80
+ # end
81
+ # end
82
+ #
83
+ # # other methods ...
84
+ # end
85
+ #
86
+ # +SynchronizedArray+ implements an Array with synchronized access to items.
87
+ # This Class is implemented as subclass of Array which includes the
88
+ # MonitorMixin module.
80
89
  #
81
90
  module MonitorMixin
82
91
  #
@@ -86,74 +95,84 @@ module MonitorMixin
86
95
  # above calls while_wait and signal, this class should be documented.
87
96
  #
88
97
  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
92
- # occurs.
98
+ class Timeout < Exception; end
99
+
100
+ #
101
+ # Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
102
+ #
103
+ # If +timeout+ is given, this method returns after +timeout+ seconds passed,
104
+ # even if no other thread doesn't signal.
105
+ #
93
106
  def wait(timeout = nil)
94
- condition = @condition
95
- @monitor.instance_eval { mon_wait_for_cond(condition, timeout) }
107
+ @monitor.__send__(:mon_check_owner)
108
+ count = @monitor.__send__(:mon_exit_for_cond)
109
+ begin
110
+ @cond.wait(@monitor.instance_variable_get(:@mon_mutex), timeout)
111
+ return true
112
+ ensure
113
+ @monitor.__send__(:mon_enter_for_cond, count)
114
+ end
96
115
  end
97
116
 
98
- # call #wait while the supplied block returns +true+.
117
+ #
118
+ # Calls wait repeatedly while the given block yields a truthy value.
119
+ #
99
120
  def wait_while
100
121
  while yield
101
- wait
122
+ wait
102
123
  end
103
124
  end
104
-
105
- # call #wait until the supplied block returns +true+.
125
+
126
+ #
127
+ # Calls wait repeatedly until the given block yields a truthy value.
128
+ #
106
129
  def wait_until
107
130
  until yield
108
- wait
131
+ wait
109
132
  end
110
133
  end
111
-
112
- # Wake up and run the next waiter
134
+
135
+ #
136
+ # Wakes up the first thread in line waiting for this lock.
137
+ #
113
138
  def signal
114
- condition = @condition
115
- @monitor.instance_eval { mon_signal_cond(condition) }
116
- nil
139
+ @monitor.__send__(:mon_check_owner)
140
+ @cond.signal
117
141
  end
118
-
119
- # Wake up all the waiters.
142
+
143
+ #
144
+ # Wakes up all threads waiting for this lock.
145
+ #
120
146
  def broadcast
121
- condition = @condition
122
- @monitor.instance_eval { mon_broadcast_cond(condition) }
123
- nil
124
- end
125
-
126
- def count_waiters
127
- condition = @condition
128
- @monitor.instance_eval { mon_count_cond_waiters(condition) }
147
+ @monitor.__send__(:mon_check_owner)
148
+ @cond.broadcast
129
149
  end
130
-
150
+
131
151
  private
132
152
 
133
- def initialize(monitor, condition)
153
+ def initialize(monitor)
134
154
  @monitor = monitor
135
- @condition = condition
155
+ @cond = ::ConditionVariable.new
136
156
  end
137
157
  end
138
-
158
+
139
159
  def self.extend_object(obj)
140
160
  super(obj)
141
- obj.instance_eval {mon_initialize()}
161
+ obj.__send__(:mon_initialize)
142
162
  end
143
-
163
+
144
164
  #
145
165
  # Attempts to enter exclusive section. Returns +false+ if lock fails.
146
166
  #
147
167
  def mon_try_enter
148
- @mon_mutex.synchronize do
149
- @mon_owner = Thread.current unless @mon_owner
150
- if @mon_owner == Thread.current
151
- @mon_count += 1
152
- true
153
- else
154
- false
168
+ if @mon_owner != Thread.current
169
+ unless @mon_mutex.try_lock
170
+ return false
155
171
  end
172
+ @mon_owner = Thread.current
156
173
  end
174
+ @mon_count += 1
175
+ return true
157
176
  end
158
177
  # For backward compatibility
159
178
  alias try_mon_enter mon_try_enter
@@ -162,21 +181,22 @@ module MonitorMixin
162
181
  # Enters exclusive section.
163
182
  #
164
183
  def mon_enter
165
- @mon_mutex.synchronize do
166
- mon_acquire(@mon_entering_cond)
167
- @mon_count += 1
184
+ if @mon_owner != Thread.current
185
+ @mon_mutex.lock
186
+ @mon_owner = Thread.current
168
187
  end
188
+ @mon_count += 1
169
189
  end
170
-
190
+
171
191
  #
172
192
  # Leaves exclusive section.
173
193
  #
174
194
  def mon_exit
175
- @mon_mutex.synchronize do
176
- mon_check_owner
177
- @mon_count -= 1
178
- mon_release if @mon_count.zero?
179
- nil
195
+ mon_check_owner
196
+ @mon_count -=1
197
+ if @mon_count == 0
198
+ @mon_owner = nil
199
+ @mon_mutex.unlock
180
200
  end
181
201
  end
182
202
 
@@ -194,121 +214,62 @@ module MonitorMixin
194
214
  end
195
215
  end
196
216
  alias synchronize mon_synchronize
197
-
217
+
198
218
  #
199
- # FIXME: This isn't documented in Nutshell.
200
- #
201
- # Create a new condition variable for this monitor.
202
- # This facilitates control of the monitor with #signal and #wait.
219
+ # Creates a new MonitorMixin::ConditionVariable associated with the
220
+ # receiver.
203
221
  #
204
222
  def new_cond
205
- condition = ::ConditionVariable.new
206
- condition.instance_eval { @mon_n_waiters = 0 }
207
- return ConditionVariable.new(self, condition)
223
+ return ConditionVariable.new(self)
208
224
  end
209
225
 
210
226
  private
211
227
 
228
+ # Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead
229
+ # of this constructor. Have look at the examples above to understand how to
230
+ # use this module.
212
231
  def initialize(*args)
213
232
  super
214
233
  mon_initialize
215
234
  end
216
235
 
217
- # called by initialize method to set defaults for instance variables.
236
+ # Initializes the MonitorMixin after being included in a class or when an
237
+ # object has been extended with the MonitorMixin
218
238
  def mon_initialize
219
- @mon_mutex = Mutex.new
220
239
  @mon_owner = nil
221
240
  @mon_count = 0
222
- @mon_total_waiting = 0
223
- @mon_entering_cond = ::ConditionVariable.new
224
- @mon_waiting_cond = ::ConditionVariable.new
225
- self
241
+ @mon_mutex = Mutex.new
226
242
  end
227
243
 
228
- # Throw a ThreadError exception if the current thread
229
- # does't own the monitor
230
244
  def mon_check_owner
231
- # called with @mon_mutex held
232
245
  if @mon_owner != Thread.current
233
246
  raise ThreadError, "current thread not owner"
234
247
  end
235
248
  end
236
249
 
237
- def mon_acquire(condition)
238
- # called with @mon_mutex held
239
- while @mon_owner && @mon_owner != Thread.current
240
- condition.wait @mon_mutex
241
- end
250
+ def mon_enter_for_cond(count)
242
251
  @mon_owner = Thread.current
252
+ @mon_count = count
243
253
  end
244
254
 
245
- def mon_release
246
- # called with @mon_mutex held
255
+ def mon_exit_for_cond
256
+ count = @mon_count
247
257
  @mon_owner = nil
248
- if @mon_total_waiting.nonzero?
249
- @mon_waiting_cond.signal
250
- else
251
- @mon_entering_cond.signal
252
- end
253
- end
254
-
255
- def mon_wait_for_cond(condition, timeout)
256
- @mon_mutex.synchronize do
257
- mon_check_owner
258
- count = @mon_count
259
- @mon_count = 0
260
- condition.instance_eval { @mon_n_waiters += 1 }
261
- begin
262
- mon_release
263
- if timeout
264
- condition.wait(@mon_mutex, timeout)
265
- else
266
- condition.wait(@mon_mutex)
267
- true
268
- end
269
- ensure
270
- @mon_total_waiting += 1
271
- # TODO: not interrupt-safe
272
- mon_acquire(@mon_waiting_cond)
273
- @mon_total_waiting -= 1
274
- @mon_count = count
275
- condition.instance_eval { @mon_n_waiters -= 1 }
276
- end
277
- end
278
- end
279
-
280
- def mon_signal_cond(condition)
281
- @mon_mutex.synchronize do
282
- mon_check_owner
283
- condition.signal
284
- end
285
- end
286
-
287
- def mon_broadcast_cond(condition)
288
- @mon_mutex.synchronize do
289
- mon_check_owner
290
- condition.broadcast
291
- end
292
- end
293
-
294
- def mon_count_cond_waiters(condition)
295
- @mon_mutex.synchronize do
296
- condition.instance_eval { @mon_n_waiters }
297
- end
258
+ @mon_count = 0
259
+ return count
298
260
  end
299
261
  end
300
262
 
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.
263
+ # Use the Monitor class when you want to have a lock object for blocks with
264
+ # mutual exclusion.
265
+ #
266
+ # require 'monitor'
267
+ #
268
+ # lock = Monitor.new
269
+ # lock.synchronize do
270
+ # # exclusive access
271
+ # end
306
272
  #
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.
312
273
  class Monitor
313
274
  include MonitorMixin
314
275
  alias try_enter try_mon_enter
@@ -324,8 +285,6 @@ end
324
285
  # - All the internals (internal modules Accessible and Initializable, class
325
286
  # ConditionVariable) appear in RDoc. It might be good to hide them, by
326
287
  # 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
288
  # - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
330
289
  # not synchronize.
331
290
  # - mon_owner is in Nutshell, but appears as an accessor in a separate module
@@ -1,5 +1,5 @@
1
1
  module RubySL
2
2
  module Monitor
3
- VERSION = "1.0.0"
3
+ VERSION = "2.0.0"
4
4
  end
5
5
  end
@@ -16,6 +16,8 @@ Gem::Specification.new do |spec|
16
16
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
17
  spec.require_paths = ["lib"]
18
18
 
19
+ spec.required_ruby_version = "~> 2.0"
20
+
19
21
  spec.add_development_dependency "bundler", "~> 1.3"
20
22
  spec.add_development_dependency "rake", "~> 10.0"
21
23
  spec.add_development_dependency "mspec", "~> 1.5"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubysl-monitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Shirai
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-08-27 00:00:00.000000000 Z
11
+ date: 2013-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,9 +80,9 @@ require_paths:
80
80
  - lib
81
81
  required_ruby_version: !ruby/object:Gem::Requirement
82
82
  requirements:
83
- - - '>='
83
+ - - ~>
84
84
  - !ruby/object:Gem::Version
85
- version: '0'
85
+ version: '2.0'
86
86
  required_rubygems_version: !ruby/object:Gem::Requirement
87
87
  requirements:
88
88
  - - '>='