Skip to content

Commit e3e0501

Browse files
committed
Added :args to Future and Promise options hash.
1 parent 474bdf6 commit e3e0501

File tree

8 files changed

+175
-25
lines changed

8 files changed

+175
-25
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
### Next Release v0.7.2 (24 January 2015)
1+
### Next Release v0.8.0 (25 January 2015)
2+
3+
* Better variable isolation in `Promise` and `Future` via an `:args` option
4+
5+
## Current Release v0.7.2 (24 January 2015)
26

37
* New `Semaphore` class based on [java.util.concurrent.Semaphore](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html)
48
* New `Promise.all?` and `Promise.any?` class methods
@@ -15,7 +19,7 @@
1519
* Tests now run on new Travis build environment
1620
* Multiple documentation updates
1721

18-
## Current Release v0.7.1 (4 December 2014)
22+
### Release v0.7.1 (4 December 2014)
1923

2024
Please see the [roadmap](https://github.com/ruby-concurrency/concurrent-ruby/issues/142) for more information on the next planned release.
2125

lib/concurrent/future.rb

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class Future < IVar
2323
# global task pool (for short-running tasks)
2424
# @option opts [object] :executor when provided will run all operations on
2525
# this executor rather than the global thread pool (overrides :operation)
26+
# @option opts [object, Array] :args zero or more arguments to be passed the task block on execution
27+
#
2628
# @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
2729
# @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
2830
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
@@ -35,6 +37,7 @@ def initialize(opts = {}, &block)
3537
@state = :unscheduled
3638
@task = block
3739
@executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_operation_pool
40+
@args = OptionsParser::get_arguments_from(opts)
3841
end
3942

4043
# Execute an `:unscheduled` `Future`. Immediately sets the state to `:pending` and
@@ -52,11 +55,9 @@ def initialize(opts = {}, &block)
5255
# @example Instance and execute in one line
5356
# future = Concurrent::Future.new{ sleep(1); 42 }.execute
5457
# future.state #=> :pending
55-
#
56-
# @since 0.5.0
5758
def execute
5859
if compare_and_set_state(:pending, :unscheduled)
59-
@executor.post{ work }
60+
@executor.post(@args){ work }
6061
self
6162
end
6263
end
@@ -72,6 +73,8 @@ def execute
7273
# global task pool (for short-running tasks)
7374
# @option opts [object] :executor when provided will run all operations on
7475
# this executor rather than the global thread pool (overrides :operation)
76+
# @option opts [object, Array] :args zero or more arguments to be passed the task block on execution
77+
#
7578
# @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
7679
# @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
7780
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
@@ -84,8 +87,6 @@ def execute
8487
# @example
8588
# future = Concurrent::Future.execute{ sleep(1); 42 }
8689
# future.state #=> :pending
87-
#
88-
# @since 0.5.0
8990
def self.execute(opts = {}, &block)
9091
Future.new(opts, &block).execute
9192
end
@@ -96,7 +97,7 @@ def self.execute(opts = {}, &block)
9697

9798
# @!visibility private
9899
def work # :nodoc:
99-
success, val, reason = SafeTaskExecutor.new(@task).execute
100+
success, val, reason = SafeTaskExecutor.new(@task).execute(*@args)
100101
complete(success, val, reason)
101102
end
102103
end

lib/concurrent/options_parser.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ def get_executor_from(opts = {})
2323
end
2424
end
2525

26+
def get_arguments_from(opts = {})
27+
[*opts.fetch(:args, [])]
28+
end
29+
2630
# Get the requested `Executor` based on the values set in the options hash.
2731
#
2832
# @param [Hash] opts the options defining the requested executor

lib/concurrent/promise.rb

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -172,29 +172,34 @@ class Promise
172172

173173
# Initialize a new Promise with the provided options.
174174
#
175-
# @param [Hash] opts the options used to define the behavior at update and deref
175+
# @!macro [attach] promise_init_options
176176
#
177-
# @option opts [Promise] :parent the parent `Promise` when building a chain/tree
178-
# @option opts [Proc] :on_fulfill fulfillment handler
179-
# @option opts [Proc] :on_reject rejection handler
177+
# @param [Hash] opts the options used to define the behavior at update and deref
180178
#
181-
# @option opts [Boolean] :operation (false) when `true` will execute the future on the global
182-
# operation pool (for long-running operations), when `false` will execute the future on the
183-
# global task pool (for short-running tasks)
184-
# @option opts [object] :executor when provided will run all operations on
185-
# this executor rather than the global thread pool (overrides :operation)
179+
# @option opts [Promise] :parent the parent `Promise` when building a chain/tree
180+
# @option opts [Proc] :on_fulfill fulfillment handler
181+
# @option opts [Proc] :on_reject rejection handler
186182
#
187-
# @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
188-
# @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
189-
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
190-
# returning the value returned from the proc
183+
# @option opts [Boolean] :operation (false) when `true` will execute the future on the global
184+
# operation pool (for long-running operations), when `false` will execute the future on the
185+
# global task pool (for short-running tasks)
186+
# @option opts [object] :executor when provided will run all operations on
187+
# this executor rather than the global thread pool (overrides :operation)
188+
# @option opts [object, Array] :args zero or more arguments to be passed the task block on execution
189+
#
190+
# @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
191+
# @option opts [String] :freeze_on_deref (false) call `#freeze` before returning the data
192+
# @option opts [String] :copy_on_deref (nil) call the given `Proc` passing the internal value and
193+
# returning the value returned from the proc
191194
#
192195
# @see http://wiki.commonjs.org/wiki/Promises/A
193196
# @see http://promises-aplus.github.io/promises-spec/
194197
def initialize(opts = {}, &block)
195198
opts.delete_if { |k, v| v.nil? }
196199

197200
@executor = OptionsParser::get_executor_from(opts) || Concurrent.configuration.global_operation_pool
201+
@args = OptionsParser::get_arguments_from(opts)
202+
198203
@parent = opts.fetch(:parent) { nil }
199204
@on_fulfill = opts.fetch(:on_fulfill) { Proc.new { |result| result } }
200205
@on_reject = opts.fetch(:on_reject) { Proc.new { |reason| raise reason } }
@@ -219,7 +224,6 @@ def self.reject(reason, opts = {})
219224
end
220225

221226
# @return [Promise]
222-
# @since 0.5.0
223227
def execute
224228
if root?
225229
if compare_and_set_state(:pending, :unscheduled)
@@ -232,7 +236,18 @@ def execute
232236
self
233237
end
234238

235-
# @since 0.5.0
239+
# Create a new `Promise` object with the given block, execute it, and return the
240+
# `:pending` object.
241+
#
242+
# @!macro promise_init_options
243+
#
244+
# @return [Promise] the newly created `Promise` in the `:pending` state
245+
#
246+
# @raise [ArgumentError] if no block is given
247+
#
248+
# @example
249+
# promise = Concurrent::Promise.execute{ sleep(1); 42 }
250+
# promise.state #=> :pending
236251
def self.execute(opts = {}, &block)
237252
new(opts, &block).execute
238253
end
@@ -389,6 +404,7 @@ def self.aggregate(method, *promises)
389404
composite
390405
end
391406

407+
# @!visibility private
392408
def set_pending
393409
mutex.synchronize do
394410
@state = :pending
@@ -413,6 +429,7 @@ def on_reject(reason)
413429
nil
414430
end
415431

432+
# @!visibility private
416433
def notify_child(child)
417434
if_state(:fulfilled) { child.on_fulfill(apply_deref_options(@value)) }
418435
if_state(:rejected) { child.on_reject(@reason) }
@@ -421,7 +438,7 @@ def notify_child(child)
421438
# @!visibility private
422439
def realize(task)
423440
@executor.post do
424-
success, value, reason = SafeTaskExecutor.new(task).execute
441+
success, value, reason = SafeTaskExecutor.new(task).execute(*@args)
425442

426443
children_to_notify = mutex.synchronize do
427444
set_state!(success, value, reason)
@@ -432,11 +449,13 @@ def realize(task)
432449
end
433450
end
434451

452+
# @!visibility private
435453
def set_state!(success, value, reason)
436454
set_state(success, value, reason)
437455
event.set
438456
end
439457

458+
# @!visibility private
440459
def synchronized_set_state!(success, value, reason)
441460
mutex.lock
442461
set_state!(success, value, reason)

spec/concurrent/future_spec.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require_relative 'dereferenceable_shared'
33
require_relative 'obligation_shared'
44
require_relative 'observable_shared'
5+
require_relative 'thread_arguments_shared'
56

67
module Concurrent
78

@@ -18,6 +19,18 @@ module Concurrent
1819

1920
context 'behavior' do
2021

22+
# thread_arguments
23+
24+
def get_ivar_from_no_args
25+
Concurrent::Future.execute{|*args| args }
26+
end
27+
28+
def get_ivar_from_args(opts)
29+
Concurrent::Future.execute(opts){|*args| args }
30+
end
31+
32+
it_should_behave_like :thread_arguments
33+
2134
# obligation
2235

2336
let!(:fulfilled_value) { 10 }

spec/concurrent/options_parser_spec.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,50 @@ module Concurrent
99
let(:task_pool){ ImmediateExecutor.new }
1010
let(:operation_pool){ ImmediateExecutor.new }
1111

12+
context '#get_arguments_from' do
13+
14+
it 'returns an empty array when opts is not given' do
15+
args = OptionsParser::get_arguments_from
16+
expect(args).to be_a Array
17+
expect(args).to be_empty
18+
end
19+
20+
it 'returns an empty array when opts is an empty hash' do
21+
args = OptionsParser::get_arguments_from({})
22+
expect(args).to be_a Array
23+
expect(args).to be_empty
24+
end
25+
26+
it 'returns an empty array when there is no :args key' do
27+
args = OptionsParser::get_arguments_from(foo: 'bar')
28+
expect(args).to be_a Array
29+
expect(args).to be_empty
30+
end
31+
32+
it 'returns an empty array when the :args key has a nil value' do
33+
args = OptionsParser::get_arguments_from(args: nil)
34+
expect(args).to be_a Array
35+
expect(args).to be_empty
36+
end
37+
38+
it 'returns a one-element array when the :args key has a non-array value' do
39+
args = OptionsParser::get_arguments_from(args: 'foo')
40+
expect(args).to eq ['foo']
41+
end
42+
43+
it 'returns an array when when the :args key has an array value' do
44+
expected = [1, 2, 3, 4]
45+
args = OptionsParser::get_arguments_from(args: expected)
46+
expect(args).to eq expected
47+
end
48+
49+
it 'returns the given array when the :args key has a complex array value' do
50+
expected = [(1..10).to_a, (20..30).to_a, (100..110).to_a]
51+
args = OptionsParser::get_arguments_from(args: expected)
52+
expect(args).to eq expected
53+
end
54+
end
55+
1256
context '#get_executor_from' do
1357

1458
it 'returns the given :executor' do

spec/concurrent/promise_spec.rb

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'spec_helper'
22
require_relative 'obligation_shared'
3+
require_relative 'thread_arguments_shared'
34

45
module Concurrent
56

@@ -23,7 +24,24 @@ module Concurrent
2324
Promise.reject(rejected_reason, executor: executor)
2425
end
2526

26-
it_should_behave_like :obligation
27+
context 'behavior' do
28+
29+
# thread_arguments
30+
31+
def get_ivar_from_no_args
32+
Concurrent::Promise.execute{|*args| args }
33+
end
34+
35+
def get_ivar_from_args(opts)
36+
Concurrent::Promise.execute(opts){|*args| args }
37+
end
38+
39+
it_should_behave_like :thread_arguments
40+
41+
# obligation
42+
43+
it_should_behave_like :obligation
44+
end
2745

2846
it 'includes Dereferenceable' do
2947
promise = Promise.new{ nil }
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
require 'spec_helper'
2+
3+
shared_examples :thread_arguments do
4+
5+
it 'passes an empty array when opts is not given' do
6+
future = get_ivar_from_no_args
7+
expect(future.value).to eq []
8+
end
9+
10+
it 'passes an empty array when opts is an empty hash' do
11+
future = get_ivar_from_args({})
12+
expect(future.value).to eq []
13+
end
14+
15+
it 'passes an empty array when there is no :args key' do
16+
future = get_ivar_from_args(foo: 'bar')
17+
expect(future.value).to eq []
18+
end
19+
20+
it 'passes an empty array when the :args key has a nil value' do
21+
future = get_ivar_from_args(args: nil)
22+
expect(future.value).to eq []
23+
end
24+
25+
it 'passes a one-element array when the :args key has a non-array value' do
26+
future = get_ivar_from_args(args: 'foo')
27+
expect(future.value).to eq ['foo']
28+
end
29+
30+
it 'passes an array when when the :args key has an array value' do
31+
expected = [1, 2, 3, 4]
32+
future = get_ivar_from_args(args: expected)
33+
expect(future.value).to eq expected
34+
end
35+
36+
it 'passes the given array when the :args key has a complex array value' do
37+
expected = [(1..10).to_a, (20..30).to_a, (100..110).to_a]
38+
future = get_ivar_from_args(args: expected)
39+
expect(future.value).to eq expected
40+
end
41+
42+
it 'allows the given arguments array to be dereferenced' do
43+
expected = [1, 2, 3, 4]
44+
future = get_ivar_from_args(args: expected)
45+
expect(future.value).to eq expected
46+
end
47+
end

0 commit comments

Comments
 (0)