concurrent-ruby 0.4.1 → 0.5.0.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 (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