concurrent-ruby 0.1.0 → 0.1.1.pre.1

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