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 +4 -4
- data/.travis.yml +2 -2
- data/lib/rubysl/monitor/monitor.rb +167 -208
- data/lib/rubysl/monitor/version.rb +1 -1
- data/rubysl-monitor.gemspec +2 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7362242f7582618baaf408e9c8eb46b1bc5c85e2
|
4
|
+
data.tar.gz: bb1f3e3984987a4143417554aa7526e59b76d54e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f7a6cca87b357fc057709c176dfb7c7104d6fca0e6c3b446c4768c6045933111f88aa54184726f75467d8b9d9c8a0df00b8dc4eacb41e833c2419046e98b1fa
|
7
|
+
data.tar.gz: 94bc9048a65ea57066ef16059b37aa07b1d973ba795da46693acdfe0d43fffc2b033b8ebfe6c0f23bc36d6105ad38c6271c94214bd5bee4f9df6ae3507e21aa2
|
data/.travis.yml
CHANGED
@@ -1,82 +1,91 @@
|
|
1
|
-
=
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
#
|
50
|
-
#
|
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
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
# The consumer thread waits for the producer thread to push a line
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
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
|
-
|
90
|
-
|
91
|
-
#
|
92
|
-
#
|
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
|
-
|
95
|
-
@monitor.
|
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
|
-
#
|
117
|
+
#
|
118
|
+
# Calls wait repeatedly while the given block yields a truthy value.
|
119
|
+
#
|
99
120
|
def wait_while
|
100
121
|
while yield
|
101
|
-
|
122
|
+
wait
|
102
123
|
end
|
103
124
|
end
|
104
|
-
|
105
|
-
#
|
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
|
-
|
131
|
+
wait
|
109
132
|
end
|
110
133
|
end
|
111
|
-
|
112
|
-
#
|
134
|
+
|
135
|
+
#
|
136
|
+
# Wakes up the first thread in line waiting for this lock.
|
137
|
+
#
|
113
138
|
def signal
|
114
|
-
|
115
|
-
@
|
116
|
-
nil
|
139
|
+
@monitor.__send__(:mon_check_owner)
|
140
|
+
@cond.signal
|
117
141
|
end
|
118
|
-
|
119
|
-
#
|
142
|
+
|
143
|
+
#
|
144
|
+
# Wakes up all threads waiting for this lock.
|
145
|
+
#
|
120
146
|
def broadcast
|
121
|
-
|
122
|
-
@
|
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
|
153
|
+
def initialize(monitor)
|
134
154
|
@monitor = monitor
|
135
|
-
@
|
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.
|
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
|
-
@
|
149
|
-
|
150
|
-
|
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
|
-
@
|
166
|
-
|
167
|
-
@
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
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
|
-
@
|
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
|
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
|
246
|
-
|
255
|
+
def mon_exit_for_cond
|
256
|
+
count = @mon_count
|
247
257
|
@mon_owner = nil
|
248
|
-
|
249
|
-
|
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
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
#
|
305
|
-
#
|
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
|
data/rubysl-monitor.gemspec
CHANGED
@@ -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:
|
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-
|
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
|
- - '>='
|