concurrent-ruby 0.4.1 → 0.5.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -33
  3. data/lib/concurrent.rb +11 -3
  4. data/lib/concurrent/actor.rb +29 -29
  5. data/lib/concurrent/agent.rb +98 -16
  6. data/lib/concurrent/atomic.rb +125 -0
  7. data/lib/concurrent/channel.rb +36 -1
  8. data/lib/concurrent/condition.rb +67 -0
  9. data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
  10. data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
  11. data/lib/concurrent/count_down_latch.rb +60 -0
  12. data/lib/concurrent/dataflow.rb +85 -0
  13. data/lib/concurrent/dereferenceable.rb +69 -31
  14. data/lib/concurrent/event.rb +27 -21
  15. data/lib/concurrent/future.rb +103 -43
  16. data/lib/concurrent/ivar.rb +78 -0
  17. data/lib/concurrent/mvar.rb +154 -0
  18. data/lib/concurrent/obligation.rb +94 -9
  19. data/lib/concurrent/postable.rb +11 -9
  20. data/lib/concurrent/promise.rb +101 -127
  21. data/lib/concurrent/safe_task_executor.rb +28 -0
  22. data/lib/concurrent/scheduled_task.rb +60 -54
  23. data/lib/concurrent/stoppable.rb +2 -2
  24. data/lib/concurrent/supervisor.rb +36 -29
  25. data/lib/concurrent/thread_local_var.rb +117 -0
  26. data/lib/concurrent/timer_task.rb +28 -30
  27. data/lib/concurrent/utilities.rb +1 -1
  28. data/lib/concurrent/version.rb +1 -1
  29. data/spec/concurrent/agent_spec.rb +121 -230
  30. data/spec/concurrent/atomic_spec.rb +201 -0
  31. data/spec/concurrent/condition_spec.rb +171 -0
  32. data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
  33. data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
  34. data/spec/concurrent/count_down_latch_spec.rb +125 -0
  35. data/spec/concurrent/dataflow_spec.rb +160 -0
  36. data/spec/concurrent/dereferenceable_shared.rb +145 -0
  37. data/spec/concurrent/event_spec.rb +44 -9
  38. data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
  39. data/spec/concurrent/future_spec.rb +184 -69
  40. data/spec/concurrent/ivar_spec.rb +192 -0
  41. data/spec/concurrent/mvar_spec.rb +380 -0
  42. data/spec/concurrent/obligation_spec.rb +193 -0
  43. data/spec/concurrent/observer_set_shared.rb +233 -0
  44. data/spec/concurrent/postable_shared.rb +3 -7
  45. data/spec/concurrent/promise_spec.rb +270 -192
  46. data/spec/concurrent/safe_task_executor_spec.rb +58 -0
  47. data/spec/concurrent/scheduled_task_spec.rb +142 -38
  48. data/spec/concurrent/thread_local_var_spec.rb +113 -0
  49. data/spec/concurrent/thread_pool_shared.rb +2 -3
  50. data/spec/concurrent/timer_task_spec.rb +31 -1
  51. data/spec/spec_helper.rb +2 -3
  52. data/spec/support/functions.rb +4 -0
  53. data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
  54. metadata +50 -30
  55. data/lib/concurrent/contract.rb +0 -21
  56. data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
  57. data/md/actor.md +0 -404
  58. data/md/agent.md +0 -142
  59. data/md/channel.md +0 -40
  60. data/md/dereferenceable.md +0 -49
  61. data/md/future.md +0 -125
  62. data/md/obligation.md +0 -32
  63. data/md/promise.md +0 -217
  64. data/md/scheduled_task.md +0 -156
  65. data/md/supervisor.md +0 -246
  66. data/md/thread_pool.md +0 -225
  67. data/md/timer_task.md +0 -191
  68. data/spec/concurrent/contract_spec.rb +0 -34
  69. data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+
3
+ module Concurrent
4
+
5
+ describe 'dataflow' do
6
+
7
+ before(:each) do
8
+ Future.thread_pool = ImmediateExecutor.new
9
+ end
10
+
11
+ it 'raises an exception when no block given' do
12
+ expect { Concurrent::dataflow }.to raise_error(ArgumentError)
13
+ end
14
+
15
+ it 'accepts zero or more dependencies' do
16
+ Concurrent::dataflow(){0}
17
+ Concurrent::dataflow(Future.execute{0}){0}
18
+ Concurrent::dataflow(Future.execute{0}, Future.execute{0}){0}
19
+ end
20
+
21
+ it 'accepts uncompleted dependencies' do
22
+ d = Future.new{0}
23
+ Concurrent::dataflow(d){0}
24
+ d.execute
25
+ end
26
+
27
+ it 'accepts completed dependencies' do
28
+ d = Future.new{0}
29
+ d.execute
30
+ Concurrent::dataflow(d){0}
31
+ end
32
+
33
+ it 'raises an exception if any dependencies are not IVars' do
34
+ expect { Concurrent::dataflow(nil) }.to raise_error(ArgumentError)
35
+ expect { Concurrent::dataflow(Future.execute{0}, nil) }.to raise_error(ArgumentError)
36
+ expect { Concurrent::dataflow(nil, Future.execute{0}) }.to raise_error(ArgumentError)
37
+ end
38
+
39
+ it 'returns a Future' do
40
+ Concurrent::dataflow{0}.should be_a(Future)
41
+ end
42
+
43
+ context 'does not schedule the Future' do
44
+
45
+ specify 'if no dependencies are completed' do
46
+ d = Future.new{0}
47
+ f = Concurrent::dataflow(d){0}
48
+ f.should be_unscheduled
49
+ d.execute
50
+ end
51
+
52
+ specify 'if one dependency of two is completed' do
53
+ d1 = Future.new{0}
54
+ d2 = Future.new{0}
55
+ f = Concurrent::dataflow(d1, d2){0}
56
+ d1.execute
57
+ f.should be_unscheduled
58
+ d2.execute
59
+ end
60
+ end
61
+
62
+ context 'schedules the Future when all dependencies are available' do
63
+
64
+ specify 'if there is just one' do
65
+ d = Future.new{0}
66
+ f = Concurrent::dataflow(d){0}
67
+ d.execute
68
+ f.value.should eq 0
69
+ end
70
+
71
+ specify 'if there is more than one' do
72
+ d1 = Future.new{0}
73
+ d2 = Future.new{0}
74
+ f = Concurrent::dataflow(d1, d2){0}
75
+ d1.execute
76
+ d2.execute
77
+ f.value.should eq 0
78
+ end
79
+ end
80
+
81
+ context 'counts already executed dependencies' do
82
+
83
+ specify 'if there is just one' do
84
+ d = Future.new{0}
85
+ d.execute
86
+ f = Concurrent::dataflow(d){0}
87
+ f.value.should eq 0
88
+ end
89
+
90
+ specify 'if there is more than one' do
91
+ d1 = Future.new{0}
92
+ d2 = Future.new{0}
93
+ d1.execute
94
+ d2.execute
95
+ f = Concurrent::dataflow(d1, d2){0}
96
+ f.value.should eq 0
97
+ end
98
+ end
99
+
100
+ context 'passes the values of dependencies into the block' do
101
+
102
+ specify 'if there is just one' do
103
+ d = Future.new{14}
104
+ f = Concurrent::dataflow(d) do |v|
105
+ v
106
+ end
107
+ d.execute
108
+ f.value.should eq 14
109
+ end
110
+
111
+ specify 'if there is more than one' do
112
+ d1 = Future.new{14}
113
+ d2 = Future.new{2}
114
+ f = Concurrent::dataflow(d1, d2) do |v1, v2|
115
+ v1 + v2
116
+ end
117
+ d1.execute
118
+ d2.execute
119
+ f.value.should eq 16
120
+ end
121
+ end
122
+
123
+ context 'module function' do
124
+
125
+ it 'can be called as Concurrent.dataflow' do
126
+
127
+ def fib_with_dot(n)
128
+ if n < 2
129
+ Concurrent.dataflow { n }
130
+ else
131
+ n1 = fib_with_dot(n - 1)
132
+ n2 = fib_with_dot(n - 2)
133
+ Concurrent.dataflow(n1, n2) { n1.value + n2.value }
134
+ end
135
+ end
136
+
137
+ expected = fib_with_dot(14)
138
+ sleep(0.1)
139
+ expected.value.should eq 377
140
+ end
141
+
142
+ it 'can be called as Concurrent::dataflow' do
143
+
144
+ def fib_with_colons(n)
145
+ if n < 2
146
+ Concurrent::dataflow { n }
147
+ else
148
+ n1 = fib_with_colons(n - 1)
149
+ n2 = fib_with_colons(n - 2)
150
+ Concurrent::dataflow(n1, n2) { n1.value + n2.value }
151
+ end
152
+ end
153
+
154
+ expected = fib_with_colons(14)
155
+ sleep(0.1)
156
+ expected.value.should eq 377
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+
3
+ share_examples_for :dereferenceable do
4
+
5
+ it 'defaults :dup_on_deref to false' do
6
+ value = 'value'
7
+ value.should_not_receive(:dup).with(any_args)
8
+
9
+ subject = dereferenceable_subject(value)
10
+ subject.value.should eq 'value'
11
+
12
+ subject = dereferenceable_subject(value, dup_on_deref: false)
13
+ subject.value.should eq 'value'
14
+
15
+ subject = dereferenceable_subject(value, dup: false)
16
+ subject.value.should eq 'value'
17
+ end
18
+
19
+ it 'calls #dup when the :dup_on_deref option is true' do
20
+ value = 'value'
21
+
22
+ subject = dereferenceable_subject(value, dup_on_deref: true)
23
+ subject.value.object_id.should_not eq value.object_id
24
+ subject.value.should eq 'value'
25
+
26
+ subject = dereferenceable_subject(value, dup: true)
27
+ subject.value.object_id.should_not eq value.object_id
28
+ subject.value.should eq 'value'
29
+ end
30
+
31
+ it 'defaults :freeze_on_deref to false' do
32
+ value = 'value'
33
+ value.should_not_receive(:freeze).with(any_args)
34
+
35
+ subject = dereferenceable_subject(value)
36
+ subject.value.should eq 'value'
37
+
38
+ subject = dereferenceable_subject(value, freeze_on_deref: false)
39
+ subject.value.should eq 'value'
40
+
41
+ subject = dereferenceable_subject(value, freeze: false)
42
+ subject.value.should eq 'value'
43
+ end
44
+
45
+ it 'calls #freeze when the :freeze_on_deref option is true' do
46
+ value = 'value'
47
+
48
+ subject = dereferenceable_subject(value, freeze_on_deref: true)
49
+ subject.value.should be_frozen
50
+ subject.value.should eq 'value'
51
+
52
+ subject = dereferenceable_subject(value, freeze: true)
53
+ subject.value.should be_frozen
54
+ subject.value.should eq 'value'
55
+ end
56
+
57
+ it 'defaults :copy_on_deref to nil' do
58
+ value = 'value'
59
+
60
+ subject = dereferenceable_subject(value)
61
+ subject.value.object_id.should == value.object_id
62
+ subject.value.should eq 'value'
63
+
64
+ subject = dereferenceable_subject(value, copy_on_deref: nil)
65
+ subject.value.object_id.should == value.object_id
66
+ subject.value.should eq 'value'
67
+
68
+ subject = dereferenceable_subject(value, copy: nil)
69
+ subject.value.object_id.should == value.object_id
70
+ subject.value.should eq 'value'
71
+ end
72
+
73
+ it 'calls the block when the :copy_on_deref option is passed a proc' do
74
+ value = 'value'
75
+ copy = proc{|val| 'copy' }
76
+
77
+ subject = dereferenceable_subject(value, copy_on_deref: copy)
78
+ subject.value.object_id.should_not == value.object_id
79
+
80
+ subject = dereferenceable_subject(value, copy: copy)
81
+ subject.value.object_id.should_not == value.object_id
82
+ end
83
+
84
+ it 'calls the :copy block first followed by #dup followed by #freeze' do
85
+ value = 'value'
86
+ copied = 'copied'
87
+ dup = 'dup'
88
+ frozen = 'frozen'
89
+ copy = proc{|val| copied }
90
+
91
+ copied.should_receive(:dup).at_least(:once).with(no_args).and_return(dup)
92
+ dup.should_receive(:freeze).at_least(:once).with(no_args).and_return(frozen)
93
+
94
+ subject = dereferenceable_subject(value, dup_on_deref: true, freeze_on_deref: true, copy_on_deref: copy)
95
+ subject.value.should eq frozen
96
+ end
97
+
98
+ it 'does not call #dup when #dup_on_deref is set and the value is nil' do
99
+ allow_message_expectations_on_nil
100
+ result = nil
101
+ result.should_not_receive(:dup).with(any_args)
102
+ subject = dereferenceable_subject(result, dup_on_deref: true)
103
+ subject.value
104
+ end
105
+
106
+ it 'does not call #freeze when #freeze_on_deref is set and the value is nil' do
107
+ allow_message_expectations_on_nil
108
+ result = nil
109
+ result.should_not_receive(:freeze).with(any_args)
110
+ subject = dereferenceable_subject(result, freeze_on_deref: true)
111
+ subject.value
112
+ end
113
+
114
+ it 'does not call the #copy_on_deref block when the value is nil' do
115
+ copier = proc { 42 }
116
+ subject = dereferenceable_subject(nil, copy_on_deref: copier)
117
+ subject.value.should be_nil
118
+ end
119
+
120
+ it 'locks when all options are false' do
121
+ subject = dereferenceable_subject(0)
122
+ mutex = double('mutex')
123
+ subject.stub(:mutex).and_return(mutex)
124
+ mutex.should_receive(:synchronize).at_least(:once)
125
+ subject.value
126
+ end
127
+
128
+ it 'supports dereference flags with observers' do
129
+ if dereferenceable_subject(0).respond_to?(:add_observer)
130
+
131
+ result = 'result'
132
+ result.should_receive(:dup).and_return(result)
133
+ result.should_receive(:freeze).and_return(result)
134
+ copier = proc { result }
135
+
136
+ observer = double('observer')
137
+ observer.should_receive(:update).with(any_args)
138
+
139
+ subject = dereferenceable_observable(dup_on_deref: true, freeze_on_deref: true, copy_on_deref: copier)
140
+
141
+ subject.add_observer(observer)
142
+ execute_dereferenceable(subject)
143
+ end
144
+ end
145
+ end
@@ -118,7 +118,7 @@ module Concurrent
118
118
  subject.reset
119
119
  @expected = false
120
120
  Thread.new{ subject.wait(0.5); @expected = true}
121
- sleep(2)
121
+ sleep(1)
122
122
  @expected.should be_true
123
123
  end
124
124
 
@@ -128,22 +128,57 @@ module Concurrent
128
128
  end
129
129
 
130
130
  it 'triggers multiple waiting threads' do
131
+ latch = CountDownLatch.new(5)
131
132
  subject.reset
132
- @expected = []
133
- 5.times{ Thread.new{ subject.wait; @expected << Thread.current.object_id } }
133
+ 5.times{ Thread.new{ subject.wait; latch.count_down } }
134
134
  subject.set
135
- sleep(1)
136
- @expected.length.should eq 5
135
+ latch.wait(0.2).should be_true
137
136
  end
138
137
 
139
138
  it 'behaves appropriately if wait begins while #set is processing' do
140
139
  subject.reset
141
- @expected = []
140
+ latch = CountDownLatch.new(5)
142
141
  5.times{ Thread.new{ subject.wait(5) } }
143
142
  subject.set
144
- 5.times{ Thread.new{ subject.wait; @expected << Thread.current.object_id } }
145
- sleep(1)
146
- @expected.length.should eq 5
143
+ 5.times{ Thread.new{ subject.wait; latch.count_down } }
144
+ latch.wait(0.2).should be_true
145
+ end
146
+ end
147
+
148
+ context 'spurious wake ups' do
149
+
150
+ before(:each) do
151
+ def subject.simulate_spurious_wake_up
152
+ @mutex.synchronize do
153
+ @condition.signal
154
+ @condition.broadcast
155
+ end
156
+ end
157
+ end
158
+
159
+ it 'should resist to spurious wake ups without timeout' do
160
+ @expected = false
161
+ Thread.new { subject.wait; @expected = true }
162
+
163
+ sleep(0.1)
164
+ subject.simulate_spurious_wake_up
165
+
166
+ sleep(0.1)
167
+ @expected.should be_false
168
+ end
169
+
170
+ it 'should resist to spurious wake ups with timeout' do
171
+ @expected = false
172
+ Thread.new { subject.wait(0.5); @expected = true }
173
+
174
+ sleep(0.1)
175
+ subject.simulate_spurious_wake_up
176
+
177
+ sleep(0.1)
178
+ @expected.should be_false
179
+
180
+ sleep(0.4)
181
+ @expected.should be_true
147
182
  end
148
183
  end
149
184
  end
@@ -82,7 +82,6 @@ module Concurrent
82
82
  context 'exception handling' do
83
83
 
84
84
  it 'restarts threads that experience exception' do
85
- pending
86
85
  pool = FixedThreadPool.new(5)
87
86
  5.times{ pool << proc{ raise StandardError } }
88
87
  sleep(1)
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require_relative 'dereferenceable_shared'
2
3
  require_relative 'obligation_shared'
3
4
  require_relative 'uses_global_thread_pool_shared'
4
5
 
@@ -6,106 +7,199 @@ module Concurrent
6
7
 
7
8
  describe Future do
8
9
 
9
- let!(:thread_pool_user){ Future }
10
- it_should_behave_like Concurrent::UsesGlobalThreadPool
10
+ let!(:value) { 10 }
11
+ subject { Future.new{ value }.execute.tap{ sleep(0.1) } }
11
12
 
12
- let!(:fulfilled_value) { 10 }
13
- let!(:rejected_reason) { StandardError.new('mojo jojo') }
14
-
15
- let(:pending_subject) do
16
- Future.new{ sleep(3); fulfilled_value }
13
+ before(:each) do
14
+ Future.thread_pool = FixedThreadPool.new(1)
17
15
  end
18
16
 
19
- let(:fulfilled_subject) do
20
- Future.new{ fulfilled_value }.tap(){ sleep(0.1) }
21
- end
17
+ context 'behavior' do
22
18
 
23
- let(:rejected_subject) do
24
- Future.new{ raise rejected_reason }.tap(){ sleep(0.1) }
25
- end
19
+ # uses_global_thread_pool
26
20
 
27
- before(:each) do
28
- Future.thread_pool = FixedThreadPool.new(1)
21
+ let!(:thread_pool_user){ Future }
22
+ it_should_behave_like Concurrent::UsesGlobalThreadPool
23
+
24
+ # obligation
25
+
26
+ let!(:fulfilled_value) { 10 }
27
+ let!(:rejected_reason) { StandardError.new('mojo jojo') }
28
+
29
+ let(:pending_subject) do
30
+ Future.new{ sleep(3); fulfilled_value }.execute
31
+ end
32
+
33
+ let(:fulfilled_subject) do
34
+ Future.new{ fulfilled_value }.execute.tap{ sleep(0.1) }
35
+ end
36
+
37
+ let(:rejected_subject) do
38
+ Future.new{ raise rejected_reason }.execute.tap{ sleep(0.1) }
39
+ end
40
+
41
+ it_should_behave_like :obligation
42
+
43
+ # dereferenceable
44
+
45
+ def dereferenceable_subject(value, opts = {})
46
+ Future.new(opts){ value }.execute.tap{ sleep(0.1) }
47
+ end
48
+
49
+ def dereferenceable_observable(opts = {})
50
+ Future.new(opts){ 'value' }
51
+ end
52
+
53
+ def execute_dereferenceable(subject)
54
+ subject.execute
55
+ sleep(0.1)
56
+ end
57
+
58
+ it_should_behave_like :dereferenceable
29
59
  end
30
60
 
31
- it_should_behave_like :obligation
61
+ context 'subclassing' do
62
+
63
+ subject{ Future.execute{ 42 } }
64
+
65
+ it 'protects #set' do
66
+ expect{ subject.set(100) }.to raise_error
67
+ end
68
+
69
+ it 'protects #fail' do
70
+ expect{ subject.fail }.to raise_error
71
+ end
32
72
 
33
- it 'includes Dereferenceable' do
34
- future = Future.new{ nil }
35
- future.should be_a(Dereferenceable)
73
+ it 'protects #complete' do
74
+ expect{ subject.complete(true, 100, nil) }.to raise_error
75
+ end
36
76
  end
37
77
 
38
78
  context '#initialize' do
39
79
 
40
- it 'spawns a new thread when a block is given' do
41
- Future.thread_pool.should_receive(:post).once.with(any_args())
80
+ it 'sets the state to :unscheduled' do
81
+ Future.new{ nil }.should be_unscheduled
82
+ end
83
+
84
+ it 'does not spawn a new thread' do
85
+ Future.thread_pool.should_not_receive(:post).with(any_args)
86
+ Thread.should_not_receive(:new).with(any_args)
42
87
  Future.new{ nil }
43
88
  end
44
89
 
45
- it 'does not spawns a new thread when no block given' do
46
- Thread.should_not_receive(:new).with(any_args())
47
- Future.new
90
+ it 'raises an exception when no block given' do
91
+ expect {
92
+ Future.new.execute
93
+ }.to raise_error(ArgumentError)
94
+ end
95
+ end
96
+
97
+ context 'instance #execute' do
98
+
99
+ it 'does nothing unless the state is :unscheduled' do
100
+ Future.should_not_receive(:thread_pool).with(any_args)
101
+ future = Future.new{ nil }
102
+ future.instance_variable_set(:@state, :pending)
103
+ future.execute
104
+ future.instance_variable_set(:@state, :rejected)
105
+ future.execute
106
+ future.instance_variable_set(:@state, :fulfilled)
107
+ future.execute
48
108
  end
49
109
 
50
- it 'immediately sets the state to :fulfilled when no block given' do
51
- Future.new.should be_fulfilled
110
+ it 'posts the block given on construction' do
111
+ Future.thread_pool.should_receive(:post).with(any_args)
112
+ future = Future.new { nil }
113
+ future.execute
52
114
  end
53
115
 
54
- it 'immediately sets the value to nil when no block given' do
55
- Future.new.value.should be_nil
116
+ it 'sets the state to :pending' do
117
+ future = Future.new { sleep(0.1) }
118
+ future.execute
119
+ future.should be_pending
120
+ end
121
+
122
+ it 'returns self' do
123
+ future = Future.new { nil }
124
+ future.execute.should be future
56
125
  end
57
126
  end
58
127
 
59
- context 'fulfillment' do
128
+ context 'class #execute' do
60
129
 
61
130
  before(:each) do
62
131
  Future.thread_pool = ImmediateExecutor.new
63
132
  end
64
133
 
65
- it 'passes all arguments to handler' do
66
- result = nil
134
+ it 'creates a new Future' do
135
+ future = Future.execute{ nil }
136
+ future.should be_a(Future)
137
+ end
67
138
 
68
- Future.new(1, 2, 3) do |a, b, c|
69
- result = [a, b, c]
70
- end
139
+ it 'passes the block to the new Future' do
140
+ @expected = false
141
+ Future.execute { @expected = true }
142
+ @expected.should be_true
143
+ end
71
144
 
72
- result.should eq [1, 2, 3]
145
+ it 'calls #execute on the new Future' do
146
+ future = double('future')
147
+ Future.stub(:new).with(any_args).and_return(future)
148
+ future.should_receive(:execute).with(no_args)
149
+ Future.execute{ nil }
150
+ end
151
+ end
152
+
153
+ context 'fulfillment' do
154
+
155
+ before(:each) do
156
+ Future.thread_pool = ImmediateExecutor.new
157
+ end
158
+
159
+ it 'passes all arguments to handler' do
160
+ @expected = false
161
+ Future.new{ @expected = true }.execute
162
+ @expected.should be_true
73
163
  end
74
164
 
75
165
  it 'sets the value to the result of the handler' do
76
- f = Future.new(10){|a| a * 2 }
77
- f.value.should eq 20
166
+ future = Future.new{ 42 }.execute
167
+ future.value.should eq 42
78
168
  end
79
169
 
80
170
  it 'sets the state to :fulfilled when the block completes' do
81
- f = Future.new(10){|a| a * 2 }
82
- f.should be_fulfilled
171
+ future = Future.new{ 42 }.execute
172
+ future.should be_fulfilled
83
173
  end
84
174
 
85
175
  it 'sets the value to nil when the handler raises an exception' do
86
- f = Future.new{ raise StandardError }
87
- f.value.should be_nil
176
+ future = Future.new{ raise StandardError }.execute
177
+ future.value.should be_nil
88
178
  end
89
179
 
90
180
  it 'sets the state to :rejected when the handler raises an exception' do
91
- f = Future.new{ raise StandardError }
92
- f.should be_rejected
181
+ future = Future.new{ raise StandardError }.execute
182
+ future.should be_rejected
93
183
  end
94
184
 
95
185
  context 'aliases' do
96
186
 
97
187
  it 'aliases #realized? for #fulfilled?' do
98
- fulfilled_subject.should be_realized
188
+ subject.should be_realized
99
189
  end
100
190
 
101
191
  it 'aliases #deref for #value' do
102
- fulfilled_subject.deref.should eq fulfilled_value
192
+ subject.deref.should eq value
103
193
  end
104
194
  end
105
195
  end
106
196
 
107
197
  context 'observation' do
108
198
 
199
+ before(:each) do
200
+ Future.thread_pool = ImmediateExecutor.new
201
+ end
202
+
109
203
  let(:clazz) do
110
204
  Class.new do
111
205
  attr_reader :value
@@ -122,72 +216,93 @@ module Concurrent
122
216
  let(:observer) { clazz.new }
123
217
 
124
218
  it 'notifies all observers on fulfillment' do
125
- future = Future.new{ sleep(0.1); 42 }
219
+ future = Future.new{ 42 }
126
220
  future.add_observer(observer)
127
- future.value.should == 42
128
- future.reason.should be_nil
129
- sleep(0.1)
221
+
222
+ future.execute
223
+
130
224
  observer.value.should == 42
131
225
  observer.reason.should be_nil
132
226
  end
133
227
 
134
228
  it 'notifies all observers on rejection' do
135
- future = Future.new{ sleep(0.1); raise StandardError }
229
+ future = Future.new{ raise StandardError }
136
230
  future.add_observer(observer)
137
- future.value.should be_nil
138
- future.reason.should be_a(StandardError)
139
- sleep(0.1)
231
+
232
+ future.execute
233
+
140
234
  observer.value.should be_nil
141
235
  observer.reason.should be_a(StandardError)
142
236
  end
143
237
 
144
238
  it 'notifies an observer added after fulfillment' do
145
- future = Future.new{ 42 }
146
- sleep(0.1)
147
- future.value.should == 42
239
+ future = Future.new{ 42 }.execute
148
240
  future.add_observer(observer)
149
- sleep(0.1)
150
241
  observer.value.should == 42
151
242
  end
152
243
 
153
244
  it 'notifies an observer added after rejection' do
154
- future = Future.new{ raise StandardError }
155
- sleep(0.1)
156
- future.reason.should be_a(StandardError)
245
+ future = Future.new{ raise StandardError }.execute
157
246
  future.add_observer(observer)
158
- sleep(0.1)
159
247
  observer.reason.should be_a(StandardError)
160
248
  end
161
249
 
162
250
  it 'does not notify existing observers when a new observer added after fulfillment' do
163
- future = Future.new{ 42 }
251
+ future = Future.new{ 42 }.execute
164
252
  future.add_observer(observer)
165
- sleep(0.1)
166
- future.value.should == 42
253
+
167
254
  observer.count.should == 1
168
255
 
169
256
  o2 = clazz.new
170
257
  future.add_observer(o2)
171
- sleep(0.1)
172
258
 
173
259
  observer.count.should == 1
174
260
  o2.value.should == 42
175
261
  end
176
262
 
177
263
  it 'does not notify existing observers when a new observer added after rejection' do
178
- future = Future.new{ raise StandardError }
264
+ future = Future.new{ raise StandardError }.execute
179
265
  future.add_observer(observer)
180
- sleep(0.1)
181
- future.reason.should be_a(StandardError)
266
+
182
267
  observer.count.should == 1
183
268
 
184
269
  o2 = clazz.new
185
270
  future.add_observer(o2)
186
- sleep(0.1)
187
271
 
188
272
  observer.count.should == 1
189
273
  o2.reason.should be_a(StandardError)
190
274
  end
275
+
276
+ context 'deadlock avoidance' do
277
+
278
+ def reentrant_observer(future)
279
+ obs = Object.new
280
+ obs.define_singleton_method(:update) do |time, value, reason|
281
+ @value = future.value
282
+ end
283
+ obs.define_singleton_method(:value) { @value }
284
+ obs
285
+ end
286
+
287
+ it 'should notify observers outside mutex lock' do
288
+ future = Future.new{ 42 }
289
+ obs = reentrant_observer(future)
290
+
291
+ future.add_observer(obs)
292
+ future.execute
293
+
294
+ obs.value.should eq 42
295
+ end
296
+
297
+ it 'should notify a new observer added after fulfillment outside lock' do
298
+ future = Future.new{ 42 }.execute
299
+ obs = reentrant_observer(future)
300
+
301
+ future.add_observer(obs)
302
+
303
+ obs.value.should eq 42
304
+ end
305
+ end
191
306
  end
192
307
  end
193
308
  end