monitor 0.2.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 72702da1a3456b7e030cff3224eb4d8ab598ba0a7b4d1dfcd751b3586b7d871f
4
+ data.tar.gz: 2c5fc8302e7c38198b45dbaa287a68b64ff82790586646053054836b7bb1d641
5
+ SHA512:
6
+ metadata.gz: 2b95f512ce9d5de335ca4e44386b1e5fee2767ea5c6716bf3265eec1fe3411f74b87d2f12c056c0116acb9f70eb7501c8c1ed9ef39de7e507f7bae9c92634e7e
7
+ data.tar.gz: 9fb8a3b869fc1db34cb0fdb1347e9160653a7b36d57b7513100cf5dd6912bd02603a3e15ec630e3215fc4b2c69f44ec6d46450700bd82d54ce9b79695731a94c
@@ -0,0 +1,24 @@
1
+ name: ubuntu
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ name: build (${{ matrix.ruby }} / ${{ matrix.os }})
8
+ strategy:
9
+ matrix:
10
+ ruby: [ 2.7, 2.6, 2.5, 2.4, head ]
11
+ os: [ ubuntu-latest, macos-latest ]
12
+ runs-on: ${{ matrix.os }}
13
+ steps:
14
+ - uses: actions/checkout@master
15
+ - name: Set up Ruby
16
+ uses: ruby/setup-ruby@v1
17
+ with:
18
+ ruby-version: ${{ matrix.ruby }}
19
+ - name: Install dependencies
20
+ run: |
21
+ gem install bundler --no-document
22
+ bundle install
23
+ - name: Run test
24
+ run: rake test
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.dll
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "bundler"
7
+ gem "rake"
8
+ gem "test-unit"
9
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+ 1. Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ 2. Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22
+ SUCH DAMAGE.
@@ -0,0 +1,72 @@
1
+ # Monitor
2
+
3
+ In concurrent programming, a monitor is an object or module intended to be
4
+ used safely by more than one thread. The defining characteristic of a
5
+ monitor is that its methods are executed with mutual exclusion. That is, at
6
+ each point in time, at most one thread may be executing any of its methods.
7
+ This mutual exclusion greatly simplifies reasoning about the implementation
8
+ of monitors compared to reasoning about parallel code that updates a data
9
+ structure.
10
+
11
+ You can read more about the general principles on the Wikipedia page for
12
+ Monitors[http://en.wikipedia.org/wiki/Monitor_%28synchronization%29]
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'monitor'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle install
25
+
26
+ Or install it yourself as:
27
+
28
+ $ gem install monitor
29
+
30
+ ## Usage
31
+
32
+ ```ruby
33
+ require 'monitor.rb'
34
+
35
+ buf = []
36
+ buf.extend(MonitorMixin)
37
+ empty_cond = buf.new_cond
38
+
39
+ # consumer
40
+ Thread.start do
41
+ loop do
42
+ buf.synchronize do
43
+ empty_cond.wait_while { buf.empty? }
44
+ print buf.shift
45
+ end
46
+ end
47
+ end
48
+
49
+ # producer
50
+ while line = ARGF.gets
51
+ buf.synchronize do
52
+ buf.push(line)
53
+ empty_cond.signal
54
+ end
55
+ end
56
+ ```
57
+
58
+ The consumer thread waits for the producer thread to push a line to buf
59
+ while <tt>buf.empty?</tt>. The producer thread (main thread) reads a
60
+ line from ARGF and pushes it into buf then calls <tt>empty_cond.signal</tt>
61
+ to notify the consumer thread of new data.
62
+
63
+ ## Development
64
+
65
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
66
+
67
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
68
+
69
+ ## Contributing
70
+
71
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/monitor.
72
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test/lib"
6
+ t.ruby_opts << "-rhelper"
7
+ t.test_files = FileList["test/**/test_*.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "monitor"
5
+
6
+ require "irb"
7
+ IRB.start(__FILE__)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,320 @@
1
+ # frozen_string_literal: false
2
+ # = monitor.rb
3
+ #
4
+ # Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
5
+ #
6
+ # This library is distributed under the terms of the Ruby license.
7
+ # You can freely distribute/modify this library.
8
+ #
9
+
10
+ #
11
+ # In concurrent programming, a monitor is an object or module intended to be
12
+ # used safely by more than one thread. The defining characteristic of a
13
+ # monitor is that its methods are executed with mutual exclusion. That is, at
14
+ # each point in time, at most one thread may be executing any of its methods.
15
+ # This mutual exclusion greatly simplifies reasoning about the implementation
16
+ # of monitors compared to reasoning about parallel code that updates a data
17
+ # structure.
18
+ #
19
+ # You can read more about the general principles on the Wikipedia page for
20
+ # Monitors[http://en.wikipedia.org/wiki/Monitor_%28synchronization%29]
21
+ #
22
+ # == Examples
23
+ #
24
+ # === Simple object.extend
25
+ #
26
+ # require 'monitor.rb'
27
+ #
28
+ # buf = []
29
+ # buf.extend(MonitorMixin)
30
+ # empty_cond = buf.new_cond
31
+ #
32
+ # # consumer
33
+ # Thread.start do
34
+ # loop do
35
+ # buf.synchronize do
36
+ # empty_cond.wait_while { buf.empty? }
37
+ # print buf.shift
38
+ # end
39
+ # end
40
+ # end
41
+ #
42
+ # # producer
43
+ # while line = ARGF.gets
44
+ # buf.synchronize do
45
+ # buf.push(line)
46
+ # empty_cond.signal
47
+ # end
48
+ # end
49
+ #
50
+ # The consumer thread waits for the producer thread to push a line to buf
51
+ # while <tt>buf.empty?</tt>. The producer thread (main thread) reads a
52
+ # line from ARGF and pushes it into buf then calls <tt>empty_cond.signal</tt>
53
+ # to notify the consumer thread of new data.
54
+ #
55
+ # === Simple Class include
56
+ #
57
+ # require 'monitor'
58
+ #
59
+ # class SynchronizedArray < Array
60
+ #
61
+ # include MonitorMixin
62
+ #
63
+ # def initialize(*args)
64
+ # super(*args)
65
+ # end
66
+ #
67
+ # alias :old_shift :shift
68
+ # alias :old_unshift :unshift
69
+ #
70
+ # def shift(n=1)
71
+ # self.synchronize do
72
+ # self.old_shift(n)
73
+ # end
74
+ # end
75
+ #
76
+ # def unshift(item)
77
+ # self.synchronize do
78
+ # self.old_unshift(item)
79
+ # end
80
+ # end
81
+ #
82
+ # # other methods ...
83
+ # end
84
+ #
85
+ # +SynchronizedArray+ implements an Array with synchronized access to items.
86
+ # This Class is implemented as subclass of Array which includes the
87
+ # MonitorMixin module.
88
+ #
89
+ module MonitorMixin
90
+ #
91
+ # FIXME: This isn't documented in Nutshell.
92
+ #
93
+ # Since MonitorMixin.new_cond returns a ConditionVariable, and the example
94
+ # above calls while_wait and signal, this class should be documented.
95
+ #
96
+ class ConditionVariable
97
+ class Timeout < Exception; end
98
+
99
+ #
100
+ # Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
101
+ #
102
+ # If +timeout+ is given, this method returns after +timeout+ seconds passed,
103
+ # even if no other thread doesn't signal.
104
+ #
105
+ def wait(timeout = nil)
106
+ Thread.handle_interrupt(Exception => :never) do
107
+ @monitor.__send__(:mon_check_owner)
108
+ count = @monitor.__send__(:mon_exit_for_cond)
109
+ begin
110
+ Thread.handle_interrupt(Exception => :immediate) do
111
+ @cond.wait(@monitor.instance_variable_get(:@mon_mutex), timeout)
112
+ end
113
+ return true
114
+ ensure
115
+ @monitor.__send__(:mon_enter_for_cond, count)
116
+ end
117
+ end
118
+ end
119
+
120
+ #
121
+ # Calls wait repeatedly while the given block yields a truthy value.
122
+ #
123
+ def wait_while
124
+ while yield
125
+ wait
126
+ end
127
+ end
128
+
129
+ #
130
+ # Calls wait repeatedly until the given block yields a truthy value.
131
+ #
132
+ def wait_until
133
+ until yield
134
+ wait
135
+ end
136
+ end
137
+
138
+ #
139
+ # Wakes up the first thread in line waiting for this lock.
140
+ #
141
+ def signal
142
+ @monitor.__send__(:mon_check_owner)
143
+ @cond.signal
144
+ end
145
+
146
+ #
147
+ # Wakes up all threads waiting for this lock.
148
+ #
149
+ def broadcast
150
+ @monitor.__send__(:mon_check_owner)
151
+ @cond.broadcast
152
+ end
153
+
154
+ private
155
+
156
+ def initialize(monitor)
157
+ @monitor = monitor
158
+ @cond = Thread::ConditionVariable.new
159
+ end
160
+ end
161
+
162
+ def self.extend_object(obj)
163
+ super(obj)
164
+ obj.__send__(:mon_initialize)
165
+ end
166
+
167
+ #
168
+ # Attempts to enter exclusive section. Returns +false+ if lock fails.
169
+ #
170
+ def mon_try_enter
171
+ if @mon_owner != Thread.current
172
+ unless @mon_mutex.try_lock
173
+ return false
174
+ end
175
+ @mon_owner = Thread.current
176
+ @mon_count = 0
177
+ end
178
+ @mon_count += 1
179
+ return true
180
+ end
181
+ # For backward compatibility
182
+ alias try_mon_enter mon_try_enter
183
+
184
+ #
185
+ # Enters exclusive section.
186
+ #
187
+ def mon_enter
188
+ if @mon_owner != Thread.current
189
+ @mon_mutex.lock
190
+ @mon_owner = Thread.current
191
+ @mon_count = 0
192
+ end
193
+ @mon_count += 1
194
+ end
195
+
196
+ #
197
+ # Leaves exclusive section.
198
+ #
199
+ def mon_exit
200
+ mon_check_owner
201
+ @mon_count -=1
202
+ if @mon_count == 0
203
+ @mon_owner = nil
204
+ @mon_mutex.unlock
205
+ end
206
+ end
207
+
208
+ #
209
+ # Returns true if this monitor is locked by any thread
210
+ #
211
+ def mon_locked?
212
+ @mon_mutex.locked?
213
+ end
214
+
215
+ #
216
+ # Returns true if this monitor is locked by current thread.
217
+ #
218
+ def mon_owned?
219
+ @mon_mutex.locked? && @mon_owner == Thread.current
220
+ end
221
+
222
+ #
223
+ # Enters exclusive section and executes the block. Leaves the exclusive
224
+ # section automatically when the block exits. See example under
225
+ # +MonitorMixin+.
226
+ #
227
+ def mon_synchronize
228
+ # Prevent interrupt on handling interrupts; for example timeout errors
229
+ # it may break locking state.
230
+ Thread.handle_interrupt(Exception => :never){ mon_enter }
231
+ begin
232
+ yield
233
+ ensure
234
+ Thread.handle_interrupt(Exception => :never){ mon_exit }
235
+ end
236
+ end
237
+ alias synchronize mon_synchronize
238
+
239
+ #
240
+ # Creates a new MonitorMixin::ConditionVariable associated with the
241
+ # receiver.
242
+ #
243
+ def new_cond
244
+ return ConditionVariable.new(self)
245
+ end
246
+
247
+ private
248
+
249
+ # Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead
250
+ # of this constructor. Have look at the examples above to understand how to
251
+ # use this module.
252
+ def initialize(*args)
253
+ super
254
+ mon_initialize
255
+ end
256
+
257
+ # Initializes the MonitorMixin after being included in a class or when an
258
+ # object has been extended with the MonitorMixin
259
+ def mon_initialize
260
+ if defined?(@mon_mutex) && @mon_mutex_owner_object_id == object_id
261
+ raise ThreadError, "already initialized"
262
+ end
263
+ @mon_mutex = Thread::Mutex.new
264
+ @mon_mutex_owner_object_id = object_id
265
+ @mon_owner = nil
266
+ @mon_count = 0
267
+ end
268
+
269
+ def mon_check_owner
270
+ if @mon_owner != Thread.current
271
+ raise ThreadError, "current thread not owner"
272
+ end
273
+ end
274
+
275
+ def mon_enter_for_cond(count)
276
+ @mon_owner = Thread.current
277
+ @mon_count = count
278
+ end
279
+
280
+ def mon_exit_for_cond
281
+ count = @mon_count
282
+ @mon_owner = nil
283
+ @mon_count = 0
284
+ return count
285
+ end
286
+ end
287
+
288
+ # Use the Monitor class when you want to have a lock object for blocks with
289
+ # mutual exclusion.
290
+ #
291
+ # require 'monitor'
292
+ #
293
+ # lock = Monitor.new
294
+ # lock.synchronize do
295
+ # # exclusive access
296
+ # end
297
+ #
298
+ class Monitor
299
+ include MonitorMixin
300
+ alias try_enter try_mon_enter
301
+ alias enter mon_enter
302
+ alias exit mon_exit
303
+ end
304
+
305
+
306
+ # Documentation comments:
307
+ # - All documentation comes from Nutshell.
308
+ # - MonitorMixin.new_cond appears in the example, but is not documented in
309
+ # Nutshell.
310
+ # - All the internals (internal modules Accessible and Initializable, class
311
+ # ConditionVariable) appear in RDoc. It might be good to hide them, by
312
+ # making them private, or marking them :nodoc:, etc.
313
+ # - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
314
+ # not synchronize.
315
+ # - mon_owner is in Nutshell, but appears as an accessor in a separate module
316
+ # here, so is hard/impossible to RDoc. Some other useful accessors
317
+ # (mon_count and some queue stuff) are also in this module, and don't appear
318
+ # directly in the RDoc output.
319
+ # - in short, it may be worth changing the code layout in this file to make the
320
+ # documentation easier
@@ -0,0 +1,3 @@
1
+ class Monitor
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,24 @@
1
+ require_relative 'lib/monitor/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "monitor"
5
+ spec.version = Monitor::VERSION
6
+ spec.authors = ["Hiroshi SHIBATA"]
7
+ spec.email = ["hsbt@ruby-lang.org"]
8
+
9
+ spec.summary = %q{Provides an object or module to use safely by more than one thread}
10
+ spec.description = %q{Provides an object or module to use safely by more than one thread}
11
+ spec.homepage = "https://github.com/ruby/monitor"
12
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
13
+ spec.license = "BSD-2-Clause"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = spec.homepage
17
+
18
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
19
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: monitor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi SHIBATA
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-04-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Provides an object or module to use safely by more than one thread
14
+ email:
15
+ - hsbt@ruby-lang.org
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".github/workflows/test.yml"
21
+ - ".gitignore"
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - bin/console
27
+ - bin/setup
28
+ - lib/monitor.rb
29
+ - lib/monitor/version.rb
30
+ - monitor.gemspec
31
+ homepage: https://github.com/ruby/monitor
32
+ licenses:
33
+ - BSD-2-Clause
34
+ metadata:
35
+ homepage_uri: https://github.com/ruby/monitor
36
+ source_code_uri: https://github.com/ruby/monitor
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 2.3.0
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.2.0.pre1
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Provides an object or module to use safely by more than one thread
56
+ test_files: []