variable 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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