variable 0.0.1

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: e2c6b1e6250c9d32d21b5e087df105f75b1f16843cc5ed982cf4d29c6ace1e80
4
+ data.tar.gz: a81dbcafed634cca911644f44c11d68d8fd13d75baa34ab53dcd4ac27cb7b534
5
+ SHA512:
6
+ metadata.gz: 71325915d362933079d97a368c19dfa7e6a5f749ee5f392d4b9e263cac14aae416757456613cb3d26b2f88098218e996026b7ae094f7dc1593abdf7864fdb00c
7
+ data.tar.gz: 554c196e7a3c40d15b35217179b358c23f3aaebdaaab1f456389a13a5f6bd687d70efa3eafce2e14318e6488d5df26e51f392ced35aa35769cac219f5aa5856a
@@ -0,0 +1,68 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ env:
10
+ BUNDLE_PATH: vendor/bundle
11
+
12
+ jobs:
13
+ build:
14
+ name: setup
15
+ strategy:
16
+ matrix:
17
+ ruby: [2.5, 2.6, 2.7, jruby]
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v2
21
+ - uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby }}
24
+ - uses: actions/cache@v1
25
+ with:
26
+ path: vendor/bundle
27
+ key: ${{ matrix.ruby }}-${{ hashFiles('Gemfile.lock') }}-3
28
+ - name: bundle
29
+ run: bundle --jobs 4 --retry 3
30
+ spec:
31
+ needs: build
32
+ strategy:
33
+ matrix:
34
+ ruby: [2.5, 2.6, 2.7, jruby]
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v2
38
+ - uses: ruby/setup-ruby@v1
39
+ with:
40
+ ruby-version: ${{ matrix.ruby }}
41
+ - uses: actions/cache@v1
42
+ with:
43
+ path: vendor/bundle
44
+ key: ${{ matrix.ruby }}-${{ hashFiles('Gemfile.lock') }}-3
45
+ - run: bundle exec rspec spec
46
+ mutant:
47
+ needs: build
48
+ strategy:
49
+ matrix:
50
+ ruby: [2.5, 2.6, 2.7]
51
+ runs-on: ubuntu-latest
52
+ steps:
53
+ - uses: actions/checkout@v2
54
+ - uses: ruby/setup-ruby@v1
55
+ with:
56
+ ruby-version: ${{ matrix.ruby }}
57
+ - uses: actions/cache@v1
58
+ with:
59
+ path: vendor/bundle
60
+ key: ${{ matrix.ruby }}-${{ hashFiles('Gemfile.lock') }}-3
61
+ - run: |
62
+ bundle exec mutant \
63
+ --include lib \
64
+ --require variable \
65
+ --use rspec \
66
+ --zombie \
67
+ -- \
68
+ 'Variable*'
data/.rspec ADDED
@@ -0,0 +1,5 @@
1
+ --color
2
+ --format progress
3
+ --order random
4
+ --require spec_helper
5
+ --warnings
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :development do
8
+ gem 'mutant', '~> 0.8.24'
9
+ gem 'mutant-rspec', '~> 0.8.24'
10
+ end
11
+
12
+ gem(
13
+ 'devtools',
14
+ git: 'https://github.com/mbj/devtools.git',
15
+ ref: '26ba0a1053e6cf7b79fc72d513a73457f9a38ead'
16
+ )
@@ -0,0 +1,179 @@
1
+ GIT
2
+ remote: https://github.com/mbj/devtools.git
3
+ revision: 26ba0a1053e6cf7b79fc72d513a73457f9a38ead
4
+ ref: 26ba0a1053e6cf7b79fc72d513a73457f9a38ead
5
+ specs:
6
+ devtools (0.1.23)
7
+ abstract_type (~> 0.0.7)
8
+ adamantium (~> 0.2.0)
9
+ anima (~> 0.3.0)
10
+ concord (~> 0.1.5)
11
+ flay (~> 2.12.0)
12
+ flog (~> 4.6.2)
13
+ procto (~> 0.0.3)
14
+ rake (~> 12.3.0)
15
+ reek (~> 5.3.0)
16
+ rspec (~> 3.8.0)
17
+ rspec-core (~> 3.8.0)
18
+ rspec-its (~> 1.2.0)
19
+ rubocop (~> 0.61.1)
20
+ simplecov (~> 0.16.1)
21
+ yard (~> 0.9.16)
22
+ yardstick (~> 0.9.9)
23
+
24
+ PATH
25
+ remote: .
26
+ specs:
27
+ variable (0.0.1)
28
+ equalizer (~> 0.0.11)
29
+
30
+ GEM
31
+ remote: https://rubygems.org/
32
+ specs:
33
+ abstract_type (0.0.7)
34
+ adamantium (0.2.0)
35
+ ice_nine (~> 0.11.0)
36
+ memoizable (~> 0.4.0)
37
+ anima (0.3.1)
38
+ abstract_type (~> 0.0.7)
39
+ adamantium (~> 0.2)
40
+ equalizer (~> 0.0.11)
41
+ ast (2.4.0)
42
+ axiom-types (0.1.1)
43
+ descendants_tracker (~> 0.0.4)
44
+ ice_nine (~> 0.11.0)
45
+ thread_safe (~> 0.3, >= 0.3.1)
46
+ codeclimate-engine-rb (0.4.1)
47
+ virtus (~> 1.0)
48
+ coercible (1.0.0)
49
+ descendants_tracker (~> 0.0.1)
50
+ concord (0.1.5)
51
+ adamantium (~> 0.2.0)
52
+ equalizer (~> 0.0.9)
53
+ descendants_tracker (0.0.4)
54
+ thread_safe (~> 0.3, >= 0.3.1)
55
+ diff-lcs (1.3)
56
+ docile (1.3.2)
57
+ equalizer (0.0.11)
58
+ erubis (2.7.0)
59
+ flay (2.12.1)
60
+ erubis (~> 2.7.0)
61
+ path_expander (~> 1.0)
62
+ ruby_parser (~> 3.0)
63
+ sexp_processor (~> 4.0)
64
+ flog (4.6.4)
65
+ path_expander (~> 1.0)
66
+ ruby_parser (~> 3.1, > 3.1.0)
67
+ sexp_processor (~> 4.8)
68
+ ice_nine (0.11.2)
69
+ jaro_winkler (1.5.4)
70
+ json (2.3.0)
71
+ kwalify (0.7.2)
72
+ memoizable (0.4.2)
73
+ thread_safe (~> 0.3, >= 0.3.1)
74
+ morpher (0.2.6)
75
+ abstract_type (~> 0.0.7)
76
+ adamantium (~> 0.2.0)
77
+ anima (~> 0.3.0)
78
+ ast (~> 2.2)
79
+ concord (~> 0.1.5)
80
+ equalizer (~> 0.0.9)
81
+ ice_nine (~> 0.11.0)
82
+ procto (~> 0.0.2)
83
+ mutant (0.8.24)
84
+ abstract_type (~> 0.0.7)
85
+ adamantium (~> 0.2.0)
86
+ anima (~> 0.3.0)
87
+ ast (~> 2.2)
88
+ concord (~> 0.1.5)
89
+ diff-lcs (~> 1.3)
90
+ equalizer (~> 0.0.9)
91
+ ice_nine (~> 0.11.1)
92
+ memoizable (~> 0.4.2)
93
+ morpher (~> 0.2.6)
94
+ parser (~> 2.5.1)
95
+ procto (~> 0.0.2)
96
+ regexp_parser (~> 1.2)
97
+ unparser (~> 0.4.2)
98
+ mutant-rspec (0.8.24)
99
+ mutant (~> 0.8.24)
100
+ rspec-core (>= 3.4.0, < 4.0.0)
101
+ parallel (1.19.1)
102
+ parser (2.5.3.0)
103
+ ast (~> 2.4.0)
104
+ path_expander (1.1.0)
105
+ powerpack (0.1.2)
106
+ procto (0.0.3)
107
+ psych (3.1.0)
108
+ rainbow (3.0.0)
109
+ rake (12.3.3)
110
+ reek (5.3.2)
111
+ codeclimate-engine-rb (~> 0.4.0)
112
+ kwalify (~> 0.7.0)
113
+ parser (>= 2.5.0.0, < 2.7, != 2.5.1.1)
114
+ psych (~> 3.1.0)
115
+ rainbow (>= 2.0, < 4.0)
116
+ regexp_parser (1.7.0)
117
+ rspec (3.8.0)
118
+ rspec-core (~> 3.8.0)
119
+ rspec-expectations (~> 3.8.0)
120
+ rspec-mocks (~> 3.8.0)
121
+ rspec-core (3.8.2)
122
+ rspec-support (~> 3.8.0)
123
+ rspec-expectations (3.8.6)
124
+ diff-lcs (>= 1.2.0, < 2.0)
125
+ rspec-support (~> 3.8.0)
126
+ rspec-its (1.2.0)
127
+ rspec-core (>= 3.0.0)
128
+ rspec-expectations (>= 3.0.0)
129
+ rspec-mocks (3.8.2)
130
+ diff-lcs (>= 1.2.0, < 2.0)
131
+ rspec-support (~> 3.8.0)
132
+ rspec-support (3.8.3)
133
+ rubocop (0.61.1)
134
+ jaro_winkler (~> 1.5.1)
135
+ parallel (~> 1.10)
136
+ parser (>= 2.5, != 2.5.1.1)
137
+ powerpack (~> 0.1)
138
+ rainbow (>= 2.2.2, < 4.0)
139
+ ruby-progressbar (~> 1.7)
140
+ unicode-display_width (~> 1.4.0)
141
+ ruby-progressbar (1.10.1)
142
+ ruby_parser (3.14.2)
143
+ sexp_processor (~> 4.9)
144
+ sexp_processor (4.14.1)
145
+ simplecov (0.16.1)
146
+ docile (~> 1.1)
147
+ json (>= 1.8, < 3)
148
+ simplecov-html (~> 0.10.0)
149
+ simplecov-html (0.10.2)
150
+ thread_safe (0.3.6)
151
+ unicode-display_width (1.4.1)
152
+ unparser (0.4.2)
153
+ abstract_type (~> 0.0.7)
154
+ adamantium (~> 0.2.0)
155
+ concord (~> 0.1.5)
156
+ diff-lcs (~> 1.3)
157
+ equalizer (~> 0.0.9)
158
+ parser (>= 2.3.1.2, < 2.6)
159
+ procto (~> 0.0.2)
160
+ virtus (1.0.5)
161
+ axiom-types (~> 0.1)
162
+ coercible (~> 1.0)
163
+ descendants_tracker (~> 0.0, >= 0.0.3)
164
+ equalizer (~> 0.0, >= 0.0.9)
165
+ yard (0.9.24)
166
+ yardstick (0.9.9)
167
+ yard (~> 0.8, >= 0.8.7.2)
168
+
169
+ PLATFORMS
170
+ ruby
171
+
172
+ DEPENDENCIES
173
+ devtools!
174
+ mutant (~> 0.8.24)
175
+ mutant-rspec (~> 0.8.24)
176
+ variable!
177
+
178
+ BUNDLED WITH
179
+ 2.1.4
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2020 Markus Schirp
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,5 @@
1
+ ![CI](https://github.com/mbj/variable/workflows/CI/badge.svg)
2
+
3
+ # variable
4
+
5
+ Ruby concurrency variables inspired by Haskells IVar and MVar.
@@ -0,0 +1,2 @@
1
+ ---
2
+ unit_test_timeout: 0.5
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'equalizer'
4
+
5
+ # Lightweight concurrency variables
6
+ #
7
+ # These are inspired by Haskells MVar and IVar types.
8
+ class Variable
9
+ EMPTY = Class.new do
10
+ const_set(:INSPECT, 'Variable::EMPTY')
11
+ end.new.freeze
12
+
13
+ TIMEOUT = Class.new do
14
+ const_set(:INSPECT, 'Variable::TIMEOUT')
15
+ end.new.freeze
16
+
17
+ # Result of operation that may time out
18
+ class Result
19
+ include Equalizer.new(:value)
20
+ attr_reader :value
21
+
22
+ # Initialize result
23
+ #
24
+ # @return [undefined]
25
+ def initialize(value)
26
+ @value = value
27
+ freeze
28
+ end
29
+
30
+ # Test if take resulted in a timeout
31
+ #
32
+ # @return [Boolean]
33
+ #
34
+ # @api private
35
+ def timeout?
36
+ instance_of?(Timeout)
37
+ end
38
+
39
+ # Instance returned on timeouts
40
+ class Timeout < self
41
+ INSTANCE = new(nil)
42
+
43
+ # Construct new object
44
+ #
45
+ # @return [Timeout]
46
+ def self.new
47
+ INSTANCE
48
+ end
49
+ end # Timeout
50
+
51
+ # Instance returned without timeouts
52
+ class Value < self
53
+ end # Value
54
+ end # Result
55
+
56
+ private_constant(*constants(false))
57
+
58
+ module Timer
59
+ # Monotonic elapsed time of block execution
60
+ #
61
+ # @return [Float]
62
+ def self.elapsed
63
+ start = now
64
+ yield
65
+ now - start
66
+ end
67
+
68
+ # The now monotonic time
69
+ #
70
+ # @return [Float]
71
+ def self.now
72
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
73
+ end
74
+ private_class_method :now
75
+ end # Timer
76
+
77
+ # Initialize object
78
+ #
79
+ # @param [Object] value
80
+ # the initial value
81
+ #
82
+ # @return [undefined]
83
+ def initialize(condition_variable:, mutex:, value: EMPTY)
84
+ @full = condition_variable.new
85
+ @mutex = mutex.new
86
+ @value = value
87
+ end
88
+
89
+ # Take value, block on empty
90
+ #
91
+ # @return [Object]
92
+ def take
93
+ synchronize do
94
+ wait_full
95
+ perform_take
96
+ end
97
+ end
98
+
99
+ # Take value, with timeout
100
+ #
101
+ # @param [Float] Timeout
102
+ #
103
+ # @return [Result::Timeout]
104
+ # in case take resulted in a timeout
105
+ #
106
+ # @return [Result::Value]
107
+ # in case take resulted in a value
108
+ def take_timeout(timeout)
109
+ synchronize do
110
+ if wait_timeout(@full, timeout, &method(:full?))
111
+ Result::Timeout.new
112
+ else
113
+ Result::Value.new(perform_take)
114
+ end
115
+ end
116
+ end
117
+
118
+ # Read value, block on empty
119
+ #
120
+ # @return [Object]
121
+ # the variable value
122
+ def read
123
+ synchronize do
124
+ wait_full
125
+ @value
126
+ end
127
+ end
128
+
129
+ # Try put value into the variable, non blocking
130
+ #
131
+ # @param [Object] value
132
+ #
133
+ # @return [self]
134
+ def try_put(value)
135
+ synchronize do
136
+ perform_put(value) if empty?
137
+ end
138
+
139
+ self
140
+ end
141
+
142
+ # Execute block with value, blocking
143
+ #
144
+ # @yield [Object]
145
+ #
146
+ # @return [Object]
147
+ # the blocks return value
148
+ def with
149
+ synchronize do
150
+ wait_full
151
+ yield @value
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ # Perform the put
158
+ #
159
+ # @param [Object] value
160
+ def perform_put(value)
161
+ (@value = value).tap { @full.signal }
162
+ end
163
+
164
+ # Execute block under mutex
165
+ #
166
+ # @return [self]
167
+ def synchronize(&block)
168
+ @mutex.synchronize(&block)
169
+ end
170
+
171
+ # Wait for block predicate
172
+ #
173
+ # @param [ConditionVariable] event
174
+ #
175
+ # @return [undefined]
176
+ def wait(event)
177
+ event.wait(@mutex) until yield
178
+ end
179
+
180
+ # Wait with timeout for block predicate
181
+ #
182
+ # @param [ConditionVariable] event
183
+ #
184
+ # @return [Boolean]
185
+ # if wait was terminated due a timeout
186
+ #
187
+ # @return [undefined]
188
+ # otherwise
189
+ def wait_timeout(event, timeout)
190
+ loop do
191
+ break true if timeout <= 0
192
+ break if yield
193
+ timeout -= Timer.elapsed { event.wait(@mutex, timeout) }
194
+ end
195
+ end
196
+
197
+ # Wait till mvar is full
198
+ #
199
+ # @return [undefined]
200
+ def wait_full
201
+ wait(@full, &method(:full?))
202
+ end
203
+
204
+ # Test if state is full
205
+ #
206
+ # @return [Boolean]
207
+ def full?
208
+ !empty?
209
+ end
210
+
211
+ # Test if state is empty
212
+ #
213
+ # @return [Boolean]
214
+ def empty?
215
+ @value.equal?(EMPTY)
216
+ end
217
+
218
+ # Shared variable that can be written at most once
219
+ #
220
+ # ignore :reek:InstanceVariableAssumption
221
+ class IVar < self
222
+
223
+ # Exception raised on ivar errors
224
+ class Error < RuntimeError; end
225
+
226
+ # Put value, raises if already full
227
+ #
228
+ # @param [Object] value
229
+ #
230
+ # @return [self]
231
+ #
232
+ # @raise Error
233
+ # if already full
234
+ def put(value)
235
+ synchronize do
236
+ fail Error, 'is immutable' if full?
237
+ perform_put(value)
238
+ end
239
+
240
+ self
241
+ end
242
+
243
+ # Populate and return value, use block to compute value if empty
244
+ #
245
+ # The block is guaranteed to be executed at max once.
246
+ #
247
+ # Subsequent reads are guaranteed to return the block value.
248
+ #
249
+ # @return [Object]
250
+ def populate_with
251
+ return @value if full?
252
+
253
+ synchronize do
254
+ perform_put(yield) if empty?
255
+ end
256
+
257
+ @value
258
+ end
259
+
260
+ private
261
+
262
+ # Perform take operation
263
+ #
264
+ # @return [Object]
265
+ def perform_take
266
+ @value
267
+ end
268
+ end # IVar
269
+
270
+ # Shared variable that can be written multiple times
271
+ #
272
+ # ignore :reek:InstanceVariableAssumption
273
+ class MVar < self
274
+
275
+ # Initialize object
276
+ #
277
+ # @param [Object] value
278
+ # the initial value
279
+ #
280
+ # @return [undefined]
281
+ def initialize(condition_variable:, mutex:, value: EMPTY)
282
+ super
283
+ @empty = condition_variable.new
284
+ end
285
+
286
+ # Put value, block on full
287
+ #
288
+ # @param [Object] value
289
+ #
290
+ # @return [self]
291
+ def put(value)
292
+ synchronize do
293
+ wait(@empty, &method(:empty?))
294
+ perform_put(value)
295
+ end
296
+
297
+ self
298
+ end
299
+
300
+ # Modify value, blocks if empty
301
+ #
302
+ # @return [Object]
303
+ def modify
304
+ synchronize do
305
+ wait_full
306
+ perform_put(yield(@value))
307
+ end
308
+ end
309
+
310
+ private
311
+
312
+ # Empty the variable
313
+ #
314
+ # @return [Object]
315
+ def perform_take
316
+ @value.tap do
317
+ @value = EMPTY
318
+ @empty.signal
319
+ end
320
+ end
321
+ end # MVar
322
+ end # Variable