concurrent-ruby 0.1.0 → 0.1.1.pre.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.
Files changed (51) hide show
  1. data/LICENSE +21 -21
  2. data/README.md +279 -224
  3. data/lib/concurrent.rb +27 -20
  4. data/lib/concurrent/agent.rb +106 -130
  5. data/lib/concurrent/cached_thread_pool.rb +130 -122
  6. data/lib/concurrent/defer.rb +67 -69
  7. data/lib/concurrent/drb_async_demux.rb +72 -0
  8. data/lib/concurrent/event.rb +60 -60
  9. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  10. data/lib/concurrent/executor.rb +87 -0
  11. data/lib/concurrent/fixed_thread_pool.rb +89 -89
  12. data/lib/concurrent/functions.rb +120 -0
  13. data/lib/concurrent/future.rb +52 -42
  14. data/lib/concurrent/global_thread_pool.rb +3 -3
  15. data/lib/concurrent/goroutine.rb +29 -25
  16. data/lib/concurrent/obligation.rb +67 -121
  17. data/lib/concurrent/promise.rb +172 -194
  18. data/lib/concurrent/reactor.rb +162 -0
  19. data/lib/concurrent/smart_mutex.rb +66 -0
  20. data/lib/concurrent/tcp_sync_demux.rb +96 -0
  21. data/lib/concurrent/thread_pool.rb +65 -61
  22. data/lib/concurrent/utilities.rb +34 -0
  23. data/lib/concurrent/version.rb +3 -3
  24. data/lib/concurrent_ruby.rb +1 -1
  25. data/md/agent.md +123 -123
  26. data/md/defer.md +174 -174
  27. data/md/event.md +32 -32
  28. data/md/executor.md +176 -0
  29. data/md/future.md +83 -83
  30. data/md/goroutine.md +52 -52
  31. data/md/obligation.md +32 -32
  32. data/md/promise.md +225 -225
  33. data/md/thread_pool.md +197 -197
  34. data/spec/concurrent/agent_spec.rb +376 -405
  35. data/spec/concurrent/cached_thread_pool_spec.rb +112 -112
  36. data/spec/concurrent/defer_spec.rb +209 -199
  37. data/spec/concurrent/event_machine_defer_proxy_spec.rb +250 -246
  38. data/spec/concurrent/event_spec.rb +134 -134
  39. data/spec/concurrent/executor_spec.rb +146 -0
  40. data/spec/concurrent/fixed_thread_pool_spec.rb +84 -84
  41. data/spec/concurrent/functions_spec.rb +57 -0
  42. data/spec/concurrent/future_spec.rb +125 -115
  43. data/spec/concurrent/goroutine_spec.rb +67 -52
  44. data/spec/concurrent/obligation_shared.rb +121 -121
  45. data/spec/concurrent/promise_spec.rb +299 -310
  46. data/spec/concurrent/smart_mutex_spec.rb +234 -0
  47. data/spec/concurrent/thread_pool_shared.rb +209 -209
  48. data/spec/concurrent/utilities_spec.rb +74 -0
  49. data/spec/spec_helper.rb +21 -19
  50. metadata +38 -14
  51. checksums.yaml +0 -7
@@ -1,310 +1,299 @@
1
- require 'spec_helper'
2
- require_relative 'obligation_shared'
3
-
4
- module Concurrent
5
-
6
- describe Promise do
7
-
8
- let!(:fulfilled_value) { 10 }
9
- let!(:rejected_reason) { StandardError.new('mojo jojo') }
10
-
11
- let(:pending_subject) do
12
- Promise.new{ sleep(1) }
13
- end
14
-
15
- let(:fulfilled_subject) do
16
- Promise.new{ fulfilled_value }.tap(){ sleep(0.1) }
17
- end
18
-
19
- let(:rejected_subject) do
20
- Promise.new{ raise rejected_reason }.
21
- rescue{ nil }.tap(){ sleep(0.1) }
22
- end
23
-
24
- before(:each) do
25
- $GLOBAL_THREAD_POOL = CachedThreadPool.new
26
- end
27
-
28
- it_should_behave_like Obligation
29
-
30
- context 'behavior' do
31
-
32
- it 'implements :promise behavior' do
33
- lambda {
34
- Promise.new{ nil }
35
- }.should_not raise_error
36
-
37
- Promise.new{ nil }.behaves_as?(:promise).should be_true
38
- end
39
-
40
- it 'implements :future behavior' do
41
- lambda {
42
- Promise.new{ nil }
43
- }.should_not raise_error
44
-
45
- Promise.new{ nil }.behaves_as?(:future).should be_true
46
- end
47
- end
48
-
49
- context '#then' do
50
-
51
- it 'returns a new Promise when :pending' do
52
- p1 = pending_subject
53
- p2 = p1.then{}
54
- p2.should be_a(Promise)
55
- p1.should_not eq p2
56
- end
57
-
58
- it 'returns a new Promise when :fulfilled' do
59
- p1 = fulfilled_subject
60
- p2 = p1.then{}
61
- p2.should be_a(Promise)
62
- p1.should_not eq p2
63
- end
64
-
65
- it 'returns a new Promise when :rejected' do
66
- p1 = rejected_subject
67
- p2 = p1.then{}
68
- p2.should be_a(Promise)
69
- p1.should_not eq p2
70
- end
71
-
72
- it 'immediately rejects new promises when self has been rejected' do
73
- p = rejected_subject
74
- p.then.should be_rejected
75
- end
76
-
77
- it 'accepts a nil block' do
78
- lambda {
79
- pending_subject.then
80
- }.should_not raise_error
81
- end
82
-
83
- it 'can be called more than once' do
84
- p = pending_subject
85
- p1 = p.then{}
86
- p2 = p.then{}
87
- p1.object_id.should_not eq p2.object_id
88
- end
89
- end
90
-
91
- context '#rescue' do
92
-
93
- it 'returns self when a block is given' do
94
- p1 = pending_subject
95
- p2 = p1.rescue{}
96
- p1.object_id.should eq p2.object_id
97
- end
98
-
99
- it 'returns self when no block is given' do
100
- p1 = pending_subject
101
- p2 = p1.rescue
102
- p1.object_id.should eq p2.object_id
103
- end
104
-
105
- it 'accepts an exception class as the first parameter' do
106
- lambda {
107
- pending_subject.rescue(StandardError){}
108
- }.should_not raise_error
109
- end
110
- end
111
-
112
- context 'fulfillment' do
113
-
114
- it 'passes all arguments to the first promise in the chain' do
115
- @a = @b = @c = nil
116
- p = Promise.new(1, 2, 3) do |a, b, c|
117
- @a, @b, @c = a, b, c
118
- end
119
- sleep(0.1)
120
- [@a, @b, @c].should eq [1, 2, 3]
121
- end
122
-
123
- it 'passes the result of each block to all its children' do
124
- @expected = nil
125
- Promise.new(10){|a| a * 2 }.then{|result| @expected = result}
126
- sleep(0.1)
127
- @expected.should eq 20
128
- end
129
-
130
- it 'sets the promise value to the result if its block' do
131
- p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
132
- sleep(0.1)
133
- p.value.should eq 40
134
- end
135
-
136
- it 'sets the promise state to :fulfilled if the block completes' do
137
- p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
138
- sleep(0.1)
139
- p.should be_fulfilled
140
- end
141
-
142
- it 'passes the last result through when a promise has no block' do
143
- @expected = nil
144
- Promise.new(10){|a| a * 2 }.then.then{|result| @expected = result}
145
- sleep(0.1)
146
- @expected.should eq 20
147
- end
148
- end
149
-
150
- context 'rejection' do
151
-
152
- it 'sets the promise reason the error object on exception' do
153
- p = Promise.new{ raise StandardError.new('Boom!') }
154
- sleep(0.1)
155
- p.reason.should be_a(Exception)
156
- p.reason.should.to_s =~ /Boom!/
157
- end
158
-
159
- it 'sets the promise state to :rejected on exception' do
160
- p = Promise.new{ raise StandardError.new('Boom!') }
161
- sleep(0.1)
162
- p.should be_rejected
163
- end
164
-
165
- it 'recursively rejects all children' do
166
- p = Promise.new{ Thread.pass; raise StandardError.new('Boom!') }
167
- promises = 10.times.collect{ p.then{ true } }
168
- sleep(0.1)
169
-
170
- 10.times.each{|i| promises[i].should be_rejected }
171
- end
172
-
173
- it 'skips processing rejected promises' do
174
- p = Promise.new{ raise StandardError.new('Boom!') }
175
- promises = 3.times.collect{ p.then{ true } }
176
- sleep(0.1)
177
- promises.each{|p| p.value.should_not be_true }
178
- end
179
-
180
- it 'calls the first exception block with a matching class' do
181
- @expected = nil
182
- Promise.new{ raise StandardError }.
183
- rescue(StandardError){|ex| @expected = 1 }.
184
- rescue(StandardError){|ex| @expected = 2 }.
185
- rescue(StandardError){|ex| @expected = 3 }
186
- sleep(0.1)
187
- @expected.should eq 1
188
- end
189
-
190
- it 'matches all with a rescue with no class given' do
191
- @expected = nil
192
- Promise.new{ raise NoMethodError }.
193
- rescue(LoadError){|ex| @expected = 1 }.
194
- rescue{|ex| @expected = 2 }.
195
- rescue(StandardError){|ex| @expected = 3 }
196
- sleep(0.1)
197
- @expected.should eq 2
198
- end
199
-
200
- it 'searches associated rescue handlers in order' do
201
- @expected = nil
202
- Promise.new{ raise ArgumentError }.
203
- rescue(ArgumentError){|ex| @expected = 1 }.
204
- rescue(LoadError){|ex| @expected = 2 }.
205
- rescue(Exception){|ex| @expected = 3 }
206
- sleep(0.1)
207
- @expected.should eq 1
208
-
209
- @expected = nil
210
- Promise.new{ raise LoadError }.
211
- rescue(ArgumentError){|ex| @expected = 1 }.
212
- rescue(LoadError){|ex| @expected = 2 }.
213
- rescue(Exception){|ex| @expected = 3 }
214
- sleep(0.1)
215
- @expected.should eq 2
216
-
217
- @expected = nil
218
- Promise.new{ raise StandardError }.
219
- rescue(ArgumentError){|ex| @expected = 1 }.
220
- rescue(LoadError){|ex| @expected = 2 }.
221
- rescue(Exception){|ex| @expected = 3 }
222
- sleep(0.1)
223
- @expected.should eq 3
224
- end
225
-
226
- it 'passes the exception object to the matched block' do
227
- @expected = nil
228
- Promise.new{ raise StandardError }.
229
- rescue(ArgumentError){|ex| @expected = ex }.
230
- rescue(LoadError){|ex| @expected = ex }.
231
- rescue(Exception){|ex| @expected = ex }
232
- sleep(0.1)
233
- @expected.should be_a(StandardError)
234
- end
235
-
236
- it 'ignores rescuers without a block' do
237
- @expected = nil
238
- Promise.new{ raise StandardError }.
239
- rescue(StandardError).
240
- rescue(StandardError){|ex| @expected = ex }.
241
- rescue(Exception){|ex| @expected = ex }
242
- sleep(0.1)
243
- @expected.should be_a(StandardError)
244
- end
245
-
246
- it 'supresses the exception if no rescue matches' do
247
- lambda {
248
- Promise.new{ raise StandardError }.
249
- rescue(ArgumentError){|ex| @expected = ex }.
250
- rescue(StandardError){|ex| @expected = ex }.
251
- rescue(Exception){|ex| @expected = ex }
252
- sleep(0.1)
253
- }.should_not raise_error
254
- end
255
-
256
- it 'supresses exceptions thrown from rescue handlers' do
257
- lambda {
258
- Promise.new{ raise ArgumentError }.
259
- rescue(Exception){ raise StandardError }
260
- sleep(0.1)
261
- }.should_not raise_error
262
- end
263
-
264
- it 'calls matching rescue handlers on all children' do
265
- @expected = []
266
- Promise.new{ Thread.pass; raise StandardError }.
267
- then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
268
- then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
269
- then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
270
- then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
271
- then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }
272
- sleep(0.1)
273
-
274
- @expected.length.should eq 5
275
- end
276
- end
277
-
278
- context 'aliases' do
279
-
280
- it 'aliases #realized? for #fulfilled?' do
281
- fulfilled_subject.should be_realized
282
- end
283
-
284
- it 'aliases #deref for #value' do
285
- fulfilled_subject.deref.should eq fulfilled_value
286
- end
287
-
288
- it 'aliases #catch for #rescue' do
289
- @expected = nil
290
- Promise.new{ raise StandardError }.catch{ @expected = true }
291
- sleep(0.1)
292
- @expected.should be_true
293
- end
294
-
295
- it 'aliases #on_error for #rescue' do
296
- @expected = nil
297
- Promise.new{ raise StandardError }.on_error{ @expected = true }
298
- sleep(0.1)
299
- @expected.should be_true
300
- end
301
-
302
- it 'aliases Kernel#promise for Promise.new' do
303
- promise().should be_a(Promise)
304
- promise(){ nil }.should be_a(Promise)
305
- promise(1, 2, 3).should be_a(Promise)
306
- promise(1, 2, 3){ nil }.should be_a(Promise)
307
- end
308
- end
309
- end
310
- end
1
+ require 'spec_helper'
2
+ require_relative 'obligation_shared'
3
+
4
+ module Concurrent
5
+
6
+ describe Promise do
7
+
8
+ let!(:fulfilled_value) { 10 }
9
+ let!(:rejected_reason) { StandardError.new('mojo jojo') }
10
+
11
+ let(:pending_subject) do
12
+ Promise.new{ sleep(1) }
13
+ end
14
+
15
+ let(:fulfilled_subject) do
16
+ Promise.new{ fulfilled_value }.tap(){ sleep(0.1) }
17
+ end
18
+
19
+ let(:rejected_subject) do
20
+ Promise.new{ raise rejected_reason }.
21
+ rescue{ nil }.tap(){ sleep(0.1) }
22
+ end
23
+
24
+ it_should_behave_like Obligation
25
+
26
+ context 'behavior' do
27
+
28
+ it 'implements :promise behavior' do
29
+ lambda {
30
+ Promise.new{ nil }
31
+ }.should_not raise_error
32
+
33
+ Promise.new{ nil }.behaves_as?(:promise).should be_true
34
+ end
35
+
36
+ it 'implements :future behavior' do
37
+ lambda {
38
+ Promise.new{ nil }
39
+ }.should_not raise_error
40
+
41
+ Promise.new{ nil }.behaves_as?(:future).should be_true
42
+ end
43
+ end
44
+
45
+ context '#then' do
46
+
47
+ it 'returns a new Promise when :pending' do
48
+ p1 = pending_subject
49
+ p2 = p1.then{}
50
+ p2.should be_a(Promise)
51
+ p1.should_not eq p2
52
+ end
53
+
54
+ it 'returns a new Promise when :fulfilled' do
55
+ p1 = fulfilled_subject
56
+ p2 = p1.then{}
57
+ p2.should be_a(Promise)
58
+ p1.should_not eq p2
59
+ end
60
+
61
+ it 'returns a new Promise when :rejected' do
62
+ p1 = rejected_subject
63
+ p2 = p1.then{}
64
+ p2.should be_a(Promise)
65
+ p1.should_not eq p2
66
+ end
67
+
68
+ it 'immediately rejects new promises when self has been rejected' do
69
+ p = rejected_subject
70
+ p.then.should be_rejected
71
+ end
72
+
73
+ it 'accepts a nil block' do
74
+ lambda {
75
+ pending_subject.then
76
+ }.should_not raise_error
77
+ end
78
+
79
+ it 'can be called more than once' do
80
+ p = pending_subject
81
+ p1 = p.then{}
82
+ p2 = p.then{}
83
+ p1.object_id.should_not eq p2.object_id
84
+ end
85
+ end
86
+
87
+ context '#rescue' do
88
+
89
+ it 'returns self when a block is given' do
90
+ p1 = pending_subject
91
+ p2 = p1.rescue{}
92
+ p1.object_id.should eq p2.object_id
93
+ end
94
+
95
+ it 'returns self when no block is given' do
96
+ p1 = pending_subject
97
+ p2 = p1.rescue
98
+ p1.object_id.should eq p2.object_id
99
+ end
100
+
101
+ it 'accepts an exception class as the first parameter' do
102
+ lambda {
103
+ pending_subject.rescue(StandardError){}
104
+ }.should_not raise_error
105
+ end
106
+ end
107
+
108
+ context 'fulfillment' do
109
+
110
+ it 'passes all arguments to the first promise in the chain' do
111
+ @a = @b = @c = nil
112
+ p = Promise.new(1, 2, 3) do |a, b, c|
113
+ @a, @b, @c = a, b, c
114
+ end
115
+ sleep(0.1)
116
+ [@a, @b, @c].should eq [1, 2, 3]
117
+ end
118
+
119
+ it 'passes the result of each block to all its children' do
120
+ @expected = nil
121
+ Promise.new(10){|a| a * 2 }.then{|result| @expected = result}
122
+ sleep(0.1)
123
+ @expected.should eq 20
124
+ end
125
+
126
+ it 'sets the promise value to the result if its block' do
127
+ p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
128
+ sleep(0.1)
129
+ p.value.should eq 40
130
+ end
131
+
132
+ it 'sets the promise state to :fulfilled if the block completes' do
133
+ p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
134
+ sleep(0.1)
135
+ p.should be_fulfilled
136
+ end
137
+
138
+ it 'passes the last result through when a promise has no block' do
139
+ @expected = nil
140
+ Promise.new(10){|a| a * 2 }.then.then{|result| @expected = result}
141
+ sleep(0.1)
142
+ @expected.should eq 20
143
+ end
144
+ end
145
+
146
+ context 'rejection' do
147
+
148
+ it 'sets the promise reason the error object on exception' do
149
+ p = Promise.new{ raise StandardError.new('Boom!') }
150
+ sleep(0.1)
151
+ p.reason.should be_a(Exception)
152
+ p.reason.should.to_s =~ /Boom!/
153
+ end
154
+
155
+ it 'sets the promise state to :rejected on exception' do
156
+ p = Promise.new{ raise StandardError.new('Boom!') }
157
+ sleep(0.1)
158
+ p.should be_rejected
159
+ end
160
+
161
+ it 'recursively rejects all children' do
162
+ p = Promise.new{ Thread.pass; raise StandardError.new('Boom!') }
163
+ promises = 10.times.collect{ p.then{ true } }
164
+ sleep(0.1)
165
+
166
+ 10.times.each{|i| promises[i].should be_rejected }
167
+ end
168
+
169
+ it 'skips processing rejected promises' do
170
+ p = Promise.new{ raise StandardError.new('Boom!') }
171
+ promises = 3.times.collect{ p.then{ true } }
172
+ sleep(0.1)
173
+ promises.each{|p| p.value.should_not be_true }
174
+ end
175
+
176
+ it 'calls the first exception block with a matching class' do
177
+ @expected = nil
178
+ Promise.new{ raise StandardError }.
179
+ rescue(StandardError){|ex| @expected = 1 }.
180
+ rescue(StandardError){|ex| @expected = 2 }.
181
+ rescue(StandardError){|ex| @expected = 3 }
182
+ sleep(0.1)
183
+ @expected.should eq 1
184
+ end
185
+
186
+ it 'matches all with a rescue with no class given' do
187
+ @expected = nil
188
+ Promise.new{ raise NoMethodError }.
189
+ rescue(LoadError){|ex| @expected = 1 }.
190
+ rescue{|ex| @expected = 2 }.
191
+ rescue(StandardError){|ex| @expected = 3 }
192
+ sleep(0.1)
193
+ @expected.should eq 2
194
+ end
195
+
196
+ it 'searches associated rescue handlers in order' do
197
+ @expected = nil
198
+ Promise.new{ raise ArgumentError }.
199
+ rescue(ArgumentError){|ex| @expected = 1 }.
200
+ rescue(LoadError){|ex| @expected = 2 }.
201
+ rescue(Exception){|ex| @expected = 3 }
202
+ sleep(0.1)
203
+ @expected.should eq 1
204
+
205
+ @expected = nil
206
+ Promise.new{ raise LoadError }.
207
+ rescue(ArgumentError){|ex| @expected = 1 }.
208
+ rescue(LoadError){|ex| @expected = 2 }.
209
+ rescue(Exception){|ex| @expected = 3 }
210
+ sleep(0.1)
211
+ @expected.should eq 2
212
+
213
+ @expected = nil
214
+ Promise.new{ raise StandardError }.
215
+ rescue(ArgumentError){|ex| @expected = 1 }.
216
+ rescue(LoadError){|ex| @expected = 2 }.
217
+ rescue(Exception){|ex| @expected = 3 }
218
+ sleep(0.1)
219
+ @expected.should eq 3
220
+ end
221
+
222
+ it 'passes the exception object to the matched block' do
223
+ @expected = nil
224
+ Promise.new{ raise StandardError }.
225
+ rescue(ArgumentError){|ex| @expected = ex }.
226
+ rescue(LoadError){|ex| @expected = ex }.
227
+ rescue(Exception){|ex| @expected = ex }
228
+ sleep(0.1)
229
+ @expected.should be_a(StandardError)
230
+ end
231
+
232
+ it 'ignores rescuers without a block' do
233
+ @expected = nil
234
+ Promise.new{ raise StandardError }.
235
+ rescue(StandardError).
236
+ rescue(StandardError){|ex| @expected = ex }.
237
+ rescue(Exception){|ex| @expected = ex }
238
+ sleep(0.1)
239
+ @expected.should be_a(StandardError)
240
+ end
241
+
242
+ it 'supresses the exception if no rescue matches' do
243
+ lambda {
244
+ Promise.new{ raise StandardError }.
245
+ rescue(ArgumentError){|ex| @expected = ex }.
246
+ rescue(StandardError){|ex| @expected = ex }.
247
+ rescue(Exception){|ex| @expected = ex }
248
+ sleep(0.1)
249
+ }.should_not raise_error
250
+ end
251
+
252
+ it 'supresses exceptions thrown from rescue handlers' do
253
+ lambda {
254
+ Promise.new{ raise ArgumentError }.
255
+ rescue(Exception){ raise StandardError }
256
+ sleep(0.1)
257
+ }.should_not raise_error
258
+ end
259
+
260
+ it 'calls matching rescue handlers on all children' do
261
+ @expected = []
262
+ Promise.new{ Thread.pass; raise StandardError }.
263
+ then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
264
+ then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
265
+ then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
266
+ then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
267
+ then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }
268
+ sleep(0.1)
269
+
270
+ @expected.length.should eq 5
271
+ end
272
+ end
273
+
274
+ context 'aliases' do
275
+
276
+ it 'aliases #realized? for #fulfilled?' do
277
+ fulfilled_subject.should be_realized
278
+ end
279
+
280
+ it 'aliases #deref for #value' do
281
+ fulfilled_subject.deref.should eq fulfilled_value
282
+ end
283
+
284
+ it 'aliases #catch for #rescue' do
285
+ @expected = nil
286
+ Promise.new{ raise StandardError }.catch{ @expected = true }
287
+ sleep(0.1)
288
+ @expected.should be_true
289
+ end
290
+
291
+ it 'aliases #on_error for #rescue' do
292
+ @expected = nil
293
+ Promise.new{ raise StandardError }.on_error{ @expected = true }
294
+ sleep(0.1)
295
+ @expected.should be_true
296
+ end
297
+ end
298
+ end
299
+ end