Skip to content

Commit cd68976

Browse files
committed
Merge pull request #270 from ruby-concurrency/ivar-promise
Promise extends IVar
2 parents c27d304 + 1314eda commit cd68976

20 files changed

+554
-397
lines changed

lib/concurrent/agent.rb

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,7 @@ class Agent
2424
#
2525
# @param [Object] initial the initial value
2626
#
27-
# @!macro [attach] executor_and_deref_options
28-
#
29-
# @param [Hash] opts the options used to define the behavior at update and deref
30-
# and to specify the executor on which to perform actions
31-
# @option opts [Executor] :executor when set use the given `Executor` instance.
32-
# Three special values are also supported: `:task` returns the global task pool,
33-
# `:operation` returns the global operation pool, and `:immediate` returns a new
34-
# `ImmediateExecutor` object.
35-
# @option opts [Boolean] :dup_on_deref (false) call `#dup` before returning the data
36-
# @option opts [Boolean] :freeze_on_deref (false) call `#freeze` before returning the data
37-
# @option opts [Proc] :copy_on_deref (nil) call the given `Proc` passing
38-
# the internal value and returning the value returned from the proc
27+
# @!macro executor_and_deref_options
3928
def initialize(initial, opts = {})
4029
@value = initial
4130
@rescuers = []

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/atomic/copy_on_notify_observer_set.rb

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,40 +33,38 @@ def add_observer(observer=nil, func=:update, &block)
3333
begin
3434
@mutex.lock
3535
@observers[observer] = func
36+
observer
3637
ensure
3738
@mutex.unlock
3839
end
39-
40-
observer
4140
end
4241

4342
# @param [Object] observer the observer to remove
4443
# @return [Object] the deleted observer
4544
def delete_observer(observer)
4645
@mutex.lock
4746
@observers.delete(observer)
48-
@mutex.unlock
49-
5047
observer
48+
ensure
49+
@mutex.unlock
5150
end
5251

5352
# Deletes all observers
5453
# @return [CopyOnWriteObserverSet] self
5554
def delete_observers
5655
@mutex.lock
5756
@observers.clear
58-
@mutex.unlock
59-
6057
self
58+
ensure
59+
@mutex.unlock
6160
end
6261

6362
# @return [Integer] the observers count
6463
def count_observers
6564
@mutex.lock
66-
result = @observers.count
65+
@observers.count
66+
ensure
6767
@mutex.unlock
68-
69-
result
7068
end
7169

7270
# Notifies all registered observers with optional args
@@ -75,7 +73,6 @@ def count_observers
7573
def notify_observers(*args, &block)
7674
observers = duplicate_observers
7775
notify_to(observers, *args, &block)
78-
7976
self
8077
end
8178

@@ -86,7 +83,6 @@ def notify_observers(*args, &block)
8683
def notify_and_delete_observers(*args, &block)
8784
observers = duplicate_and_clear_observers
8885
notify_to(observers, *args, &block)
89-
9086
self
9187
end
9288

@@ -96,17 +92,17 @@ def duplicate_and_clear_observers
9692
@mutex.lock
9793
observers = @observers.dup
9894
@observers.clear
99-
@mutex.unlock
100-
10195
observers
96+
ensure
97+
@mutex.unlock
10298
end
10399

104100
def duplicate_observers
105101
@mutex.lock
106102
observers = @observers.dup
107-
@mutex.unlock
108-
109103
observers
104+
ensure
105+
@mutex.unlock
110106
end
111107

112108
def notify_to(observers, *args)

lib/concurrent/channel/buffered_channel.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def select(probe)
4040
@probe_set.put(probe)
4141
true
4242
else
43-
shift_buffer if probe.set_unless_assigned(peek_buffer, self)
43+
shift_buffer if probe.try_set([peek_buffer, self])
4444
end
4545

4646
end
@@ -76,7 +76,7 @@ def set_probe_or_push_into_buffer(value)
7676
push_into_buffer(value)
7777
true
7878
else
79-
@probe_set.take.set_unless_assigned(value, self)
79+
@probe_set.take.try_set([value, self])
8080
end
8181
end
8282
end

lib/concurrent/channel/channel.rb

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,12 @@
33
module Concurrent
44
module Channel
55

6-
class Probe < Concurrent::IVar
7-
8-
def initialize(value = NO_VALUE, opts = {})
9-
super(value, opts)
10-
end
11-
12-
def set_unless_assigned(value, channel)
13-
mutex.synchronize do
14-
return false if [:fulfilled, :rejected].include? @state
15-
16-
set_state(true, [value, channel], nil)
17-
event.set
18-
true
19-
end
20-
end
21-
22-
alias_method :composite_value, :value
23-
24-
def value
25-
composite_value.nil? ? nil : composite_value[0]
26-
end
27-
28-
def channel
29-
composite_value.nil? ? nil : composite_value[1]
30-
end
31-
end
6+
Probe = IVar
327

338
def self.select(*channels)
349
probe = Probe.new
3510
channels.each { |channel| channel.select(probe) }
36-
result = probe.composite_value
11+
result = probe.value
3712
channels.each { |channel| channel.remove_probe(probe) }
3813
result
3914
end

lib/concurrent/channel/unbuffered_channel.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ def probe_set_size
1212
end
1313

1414
def push(value)
15-
# TODO set_unless_assigned define on IVar as #set_state? or #try_set_state
16-
until @probe_set.take.set_unless_assigned(value, self)
15+
until @probe_set.take.try_set([value, self])
1716
end
1817
end
1918

lib/concurrent/delay.rb

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'concurrent/obligation'
44
require 'concurrent/executor/executor_options'
55
require 'concurrent/executor/immediate_executor'
6+
require 'concurrent/synchronization'
67

78
module Concurrent
89

@@ -37,7 +38,7 @@ module Concurrent
3738
# execute on the given executor, allowing the call to timeout.
3839
#
3940
# @see Concurrent::Dereferenceable
40-
class Delay
41+
class Delay < Synchronization::Object
4142
include Obligation
4243
include ExecutorOptions
4344

@@ -51,15 +52,27 @@ class Delay
5152

5253
# Create a new `Delay` in the `:pending` state.
5354
#
54-
# @yield the delayed operation to perform
55+
# @!macro [attach] executor_and_deref_options
56+
#
57+
# @param [Hash] opts the options used to define the behavior at update and deref
58+
# and to specify the executor on which to perform actions
59+
# @option opts [Executor] :executor when set use the given `Executor` instance.
60+
# Three special values are also supported: `:task` returns the global task pool,
61+
# `:operation` returns the global operation pool, and `:immediate` returns a new
62+
# `ImmediateExecutor` object.
63+
# @option opts [Boolean] :dup_on_deref (false) call `#dup` before returning the data
64+
# @option opts [Boolean] :freeze_on_deref (false) call `#freeze` before returning the data
65+
# @option opts [Proc] :copy_on_deref (nil) call the given `Proc` passing
66+
# the internal value and returning the value returned from the proc
5567
#
56-
# @!macro executor_and_deref_options
68+
# @yield the delayed operation to perform
5769
#
5870
# @raise [ArgumentError] if no block is given
5971
def initialize(opts = {}, &block)
6072
raise ArgumentError.new('no block given') unless block_given?
6173

62-
init_obligation
74+
super()
75+
init_obligation(self)
6376
set_deref_options(opts)
6477
@task_executor = get_executor_from(opts)
6578

@@ -145,16 +158,15 @@ def wait(timeout = nil)
145158
# @yield the delayed operation to perform
146159
# @return [true, false] if success
147160
def reconfigure(&block)
148-
mutex.lock
149-
raise ArgumentError.new('no block given') unless block_given?
150-
unless @computing
151-
@task = block
152-
true
153-
else
154-
false
161+
mutex.synchronize do
162+
raise ArgumentError.new('no block given') unless block_given?
163+
unless @computing
164+
@task = block
165+
true
166+
else
167+
false
168+
end
155169
end
156-
ensure
157-
mutex.unlock
158170
end
159171

160172
private
@@ -163,10 +175,11 @@ def reconfigure(&block)
163175
def execute_task_once # :nodoc:
164176
# this function has been optimized for performance and
165177
# should not be modified without running new benchmarks
166-
mutex.lock
167-
execute = @computing = true unless @computing
168-
task = @task
169-
mutex.unlock
178+
execute = task = nil
179+
mutex.synchronize do
180+
execute = @computing = true unless @computing
181+
task = @task
182+
end
170183

171184
if execute
172185
@task_executor.post do
@@ -176,10 +189,10 @@ def execute_task_once # :nodoc:
176189
rescue => ex
177190
reason = ex
178191
end
179-
mutex.lock
180-
set_state(success, result, reason)
181-
event.set
182-
mutex.unlock
192+
mutex.synchronize do
193+
set_state(success, result, reason)
194+
event.set
195+
end
183196
end
184197
end
185198
end

lib/concurrent/dereferenceable.rb

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,17 @@ module Dereferenceable
3939
#
4040
# @return [Object] the current value of the object
4141
def value
42-
mutex.lock
43-
apply_deref_options(@value)
44-
ensure
45-
mutex.unlock
42+
mutex.synchronize { apply_deref_options(@value) }
4643
end
47-
4844
alias_method :deref, :value
4945

5046
protected
5147

5248
# Set the internal value of this object
5349
#
54-
# @param [Object] val the new value
55-
def value=(val)
56-
mutex.lock
57-
@value = val
58-
ensure
59-
mutex.unlock
50+
# @param [Object] value the new value
51+
def value=(value)
52+
mutex.synchronize{ @value = value }
6053
end
6154

6255
# A mutex lock used for synchronizing thread-safe operations. Methods defined
@@ -74,8 +67,8 @@ def mutex
7467
# @note This method *must* be called from within the constructor of the including class.
7568
#
7669
# @see #mutex
77-
def init_mutex
78-
@mutex = Mutex.new
70+
def init_mutex(mutex = Mutex.new)
71+
@mutex = mutex
7972
end
8073

8174
# Set the options which define the operations #value performs before
@@ -91,14 +84,13 @@ def init_mutex
9184
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing
9285
# the internal value and returning the value returned from the proc
9386
def set_deref_options(opts = {})
94-
mutex.lock
95-
@dup_on_deref = opts[:dup_on_deref] || opts[:dup]
96-
@freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze]
97-
@copy_on_deref = opts[:copy_on_deref] || opts[:copy]
98-
@do_nothing_on_deref = !(@dup_on_deref || @freeze_on_deref || @copy_on_deref)
99-
nil
100-
ensure
101-
mutex.unlock
87+
mutex.synchronize do
88+
@dup_on_deref = opts[:dup_on_deref] || opts[:dup]
89+
@freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze]
90+
@copy_on_deref = opts[:copy_on_deref] || opts[:copy]
91+
@do_nothing_on_deref = !(@dup_on_deref || @freeze_on_deref || @copy_on_deref)
92+
nil
93+
end
10294
end
10395

10496
# @!visibility private

lib/concurrent/future.rb

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

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

8192
private
8293

0 commit comments

Comments
 (0)