Skip to content

Commit 49f6718

Browse files
committed
Added shared specs for IVar, Future, and Promise.
1 parent 700ddcb commit 49f6718

File tree

9 files changed

+140
-135
lines changed

9 files changed

+140
-135
lines changed

lib/concurrent/async.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,11 @@ def method_missing(method, *args, &block)
8282
self.define_singleton_method(method) do |*args2|
8383
Async::validate_argc(@delegate, method, *args2)
8484
ivar = Concurrent::IVar.new
85-
value, reason = nil, nil
8685
@serializer.post(@executor.value) do
8786
begin
88-
value = @delegate.send(method, *args2, &block)
87+
ivar.set(@delegate.send(method, *args2, &block))
8988
rescue => reason
90-
# caught
91-
ensure
92-
ivar.complete(reason.nil?, value, reason)
89+
ivar.fail(reason)
9390
end
9491
end
9592
ivar.value if @blocking

lib/concurrent/future.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,19 @@ def self.execute(opts = {}, &block)
7676
Future.new(opts, &block).execute
7777
end
7878

79-
protected :set, :fail, :complete
79+
def set(value = IVar::NO_VALUE, &block)
80+
check_for_block_or_value!(block_given?, value)
81+
mutex.synchronize do
82+
if @state != :unscheduled
83+
raise MultipleAssignmentError
84+
else
85+
@task = block || Proc.new { value }
86+
end
87+
end
88+
execute
89+
end
90+
91+
protected :complete
8092

8193
private
8294

lib/concurrent/ivar.rb

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,9 @@ def initialize(value = NO_VALUE, opts = {})
6060
init_obligation
6161
self.observers = CopyOnWriteObserverSet.new
6262
set_deref_options(opts)
63+
@state = :pending
6364

64-
if value == NO_VALUE
65-
@state = :pending
66-
else
67-
set(value)
68-
end
65+
set(value) unless value == NO_VALUE
6966
end
7067

7168
# Add an observer on this object that will receive notification on update.
@@ -108,6 +105,8 @@ def add_observer(observer = nil, func = :update, &block)
108105
def set(value = NO_VALUE)
109106
if (block_given? && value != NO_VALUE) || (!block_given? && value == NO_VALUE)
110107
raise ArgumentError.new('must set with either a value or a block')
108+
elsif ! compare_and_set_state(:processing, :pending)
109+
raise MultipleAssignmentError
111110
end
112111

113112
begin
@@ -124,13 +123,15 @@ def set(value = NO_VALUE)
124123
# @raise [Concurrent::MultipleAssignmentError] if the `IVar` has already
125124
# been set or otherwise completed
126125
def fail(reason = StandardError.new)
127-
complete(false, nil, reason)
126+
set { raise reason }
128127
end
129128

129+
protected
130+
130131
# @!visibility private
131132
def complete(success, value, reason) # :nodoc:
132133
mutex.synchronize do
133-
raise MultipleAssignmentError.new('multiple assignment') if [:fulfilled, :rejected].include? @state
134+
raise MultipleAssignmentError if [:fulfilled, :rejected].include? @state
134135
set_state(success, value, reason)
135136
event.set
136137
end
@@ -139,5 +140,12 @@ def complete(success, value, reason) # :nodoc:
139140
observers.notify_and_delete_observers{ [time, self.value, reason] }
140141
self
141142
end
143+
144+
# @!visibility private
145+
def check_for_block_or_value!(block_given, value) # :nodoc:
146+
if (block_given && value != NO_VALUE) || (! block_given && value == NO_VALUE)
147+
raise ArgumentError.new('must set with either a value or a block')
148+
end
149+
end
142150
end
143151
end

lib/concurrent/obligation.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def completed?
5959
#
6060
# @return [Boolean]
6161
def incomplete?
62-
[:unscheduled, :pending].include? state
62+
! complete?
6363
end
6464

6565
# The current value of the obligation. Will be `nil` while the state is

lib/concurrent/promise.rb

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -242,24 +242,17 @@ def execute
242242

243243
def set(value = IVar::NO_VALUE, &block)
244244
raise PromiseExecutionError.new('supported only on root promise') unless root?
245-
if (block_given? && value != NO_VALUE) || (!block_given? && value == NO_VALUE)
246-
raise ArgumentError.new('must set with either a value or a block')
247-
end
245+
check_for_block_or_value!(block_given?, value)
248246
mutex.synchronize do
249247
if @state != :unscheduled
250-
raise PromiseExecutionError.new('execution has already begun')
248+
raise MultipleAssignmentError
251249
else
252250
@promise_body = block || Proc.new { |result| value }
253251
end
254252
end
255253
execute
256254
end
257255

258-
def fail(reason = StandardError.new)
259-
raise PromiseExecutionError.new('supported only on root promise') unless root?
260-
super
261-
end
262-
263256
# Create a new `Promise` object with the given block, execute it, and return the
264257
# `:pending` object.
265258
#

spec/concurrent/future_spec.rb

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require_relative 'dereferenceable_shared'
2+
require_relative 'ivar_shared'
23
require_relative 'obligation_shared'
34
require_relative 'observable_shared'
45
require_relative 'thread_arguments_shared'
@@ -16,6 +17,11 @@ module Concurrent
1617
}.execute.tap{ sleep(0.1) }
1718
end
1819

20+
context 'manual completion' do
21+
subject { Future.new(executor: :immediate){ nil } }
22+
it_should_behave_like :ivar
23+
end
24+
1925
context 'behavior' do
2026

2127
# thread_arguments
@@ -80,23 +86,6 @@ def trigger_observable(observable)
8086
it_should_behave_like :observable
8187
end
8288

83-
context 'subclassing' do
84-
85-
subject{ Future.execute(executor: executor){ 42 } }
86-
87-
it 'protects #set' do
88-
expect{ subject.set(100) }.to raise_error
89-
end
90-
91-
it 'protects #fail' do
92-
expect{ subject.fail }.to raise_error
93-
end
94-
95-
it 'protects #complete' do
96-
expect{ subject.complete(true, 100, nil) }.to raise_error
97-
end
98-
end
99-
10089
context '#initialize' do
10190

10291
let(:executor) { ImmediateExecutor.new }

spec/concurrent/ivar_shared.rb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
shared_examples :ivar do
2+
3+
context 'initialization' do
4+
5+
it 'sets the state to incomplete' do
6+
expect(subject).to be_incomplete
7+
end
8+
end
9+
10+
context '#set' do
11+
12+
it 'sets the state to be fulfilled' do
13+
subject.set(14)
14+
expect(subject).to be_fulfilled
15+
end
16+
17+
it 'sets the value' do
18+
subject.set(14)
19+
expect(subject.value).to eq 14
20+
end
21+
22+
it 'raises an exception if set more than once' do
23+
subject.set(14)
24+
expect {subject.set(2)}.to raise_error(Concurrent::MultipleAssignmentError)
25+
expect(subject.value).to eq 14
26+
end
27+
28+
it 'returns self' do
29+
expect(subject.set(42)).to eq subject
30+
end
31+
it 'fulfils when given a block which executes successfully' do
32+
subject.set{ 42 }
33+
expect(subject.value).to eq 42
34+
end
35+
36+
it 'rejects when given a block which raises an exception' do
37+
expected = ArgumentError.new
38+
subject.set{ raise expected }
39+
expect(subject.reason).to eq expected
40+
end
41+
42+
it 'raises an exception when given a value and a block' do
43+
expect {
44+
subject.set(42){ :guide }
45+
}.to raise_error(ArgumentError)
46+
end
47+
48+
it 'raises an exception when given neither a value nor a block' do
49+
expect {
50+
subject.set
51+
}.to raise_error(ArgumentError)
52+
end
53+
end
54+
55+
context '#fail' do
56+
57+
it 'sets the state to be rejected' do
58+
subject.fail
59+
expect(subject).to be_rejected
60+
end
61+
62+
it 'sets the value to be nil' do
63+
subject.fail
64+
expect(subject.value).to be_nil
65+
end
66+
67+
it 'sets the reason to the given exception' do
68+
expected = ArgumentError.new
69+
subject.fail(expected)
70+
expect(subject.reason).to eq expected
71+
end
72+
73+
it 'raises an exception if set more than once' do
74+
subject.fail
75+
expect {subject.fail}.to raise_error(Concurrent::MultipleAssignmentError)
76+
expect(subject.value).to be_nil
77+
end
78+
79+
it 'defaults the reason to a StandardError' do
80+
subject.fail
81+
expect(subject.reason).to be_a StandardError
82+
end
83+
84+
it 'returns self' do
85+
expect(subject.fail).to eq subject
86+
end
87+
end
88+
end

spec/concurrent/ivar_spec.rb

Lines changed: 7 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require_relative 'dereferenceable_shared'
2+
require_relative 'ivar_shared'
23
require_relative 'obligation_shared'
34
require_relative 'observable_shared'
45

@@ -14,6 +15,11 @@ module Concurrent
1415
i
1516
end
1617

18+
context 'manual completion' do
19+
subject{ IVar.new }
20+
it_should_behave_like :ivar
21+
end
22+
1723
context 'behavior' do
1824

1925
# obligation
@@ -85,105 +91,11 @@ def trigger_observable(observable)
8591

8692
it 'can set an initial value' do
8793
i = IVar.new(14)
88-
expect(i).to be_completed
94+
expect(i).to be_complete
8995
end
9096

9197
end
9298

93-
context '#set' do
94-
95-
it 'sets the state to be fulfilled' do
96-
i = IVar.new
97-
i.set(14)
98-
expect(i).to be_fulfilled
99-
end
100-
101-
it 'sets the value' do
102-
i = IVar.new
103-
i.set(14)
104-
expect(i.value).to eq 14
105-
end
106-
107-
it 'raises an exception if set more than once' do
108-
i = IVar.new
109-
i.set(14)
110-
expect {i.set(2)}.to raise_error(Concurrent::MultipleAssignmentError)
111-
expect(i.value).to eq 14
112-
end
113-
114-
it 'returns self' do
115-
i = IVar.new
116-
expect(i.set(42)).to eq i
117-
end
118-
119-
it 'fulfils when given a block which executes successfully' do
120-
i = IVar.new
121-
i.set{ 42 }
122-
expect(i.value).to eq 42
123-
end
124-
125-
it 'rejects when given a block which raises an exception' do
126-
i = IVar.new
127-
expected = ArgumentError.new
128-
i.set{ raise expected }
129-
expect(i.reason).to eq expected
130-
end
131-
132-
it 'raises an exception when given a value and a block' do
133-
i = IVar.new
134-
expect {
135-
i.set(42){ :guide }
136-
}.to raise_error(ArgumentError)
137-
end
138-
139-
it 'raises an exception when given neither a value nor a block' do
140-
i = IVar.new
141-
expect {
142-
i.set
143-
}.to raise_error(ArgumentError)
144-
end
145-
end
146-
147-
context '#fail' do
148-
149-
it 'sets the state to be rejected' do
150-
i = IVar.new
151-
i.fail
152-
expect(i).to be_rejected
153-
end
154-
155-
it 'sets the value to be nil' do
156-
i = IVar.new
157-
i.fail
158-
expect(i.value).to be_nil
159-
end
160-
161-
it 'sets the reason to the given exception' do
162-
i = IVar.new
163-
expected = ArgumentError.new
164-
i.fail(expected)
165-
expect(i.reason).to eq expected
166-
end
167-
168-
it 'raises an exception if set more than once' do
169-
i = IVar.new
170-
i.fail
171-
expect {i.fail}.to raise_error(Concurrent::MultipleAssignmentError)
172-
expect(i.value).to be_nil
173-
end
174-
175-
it 'defaults the reason to a StandardError' do
176-
i = IVar.new
177-
i.fail
178-
expect(i.reason).to be_a StandardError
179-
end
180-
181-
it 'returns self' do
182-
i = IVar.new
183-
expect(i.fail).to eq i
184-
end
185-
end
186-
18799
context 'observation' do
188100

189101
let(:clazz) do

0 commit comments

Comments
 (0)