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.
- checksums.yaml +4 -4
- data/README.md +31 -33
- data/lib/concurrent.rb +11 -3
- data/lib/concurrent/actor.rb +29 -29
- data/lib/concurrent/agent.rb +98 -16
- data/lib/concurrent/atomic.rb +125 -0
- data/lib/concurrent/channel.rb +36 -1
- data/lib/concurrent/condition.rb +67 -0
- data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
- data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
- data/lib/concurrent/count_down_latch.rb +60 -0
- data/lib/concurrent/dataflow.rb +85 -0
- data/lib/concurrent/dereferenceable.rb +69 -31
- data/lib/concurrent/event.rb +27 -21
- data/lib/concurrent/future.rb +103 -43
- data/lib/concurrent/ivar.rb +78 -0
- data/lib/concurrent/mvar.rb +154 -0
- data/lib/concurrent/obligation.rb +94 -9
- data/lib/concurrent/postable.rb +11 -9
- data/lib/concurrent/promise.rb +101 -127
- data/lib/concurrent/safe_task_executor.rb +28 -0
- data/lib/concurrent/scheduled_task.rb +60 -54
- data/lib/concurrent/stoppable.rb +2 -2
- data/lib/concurrent/supervisor.rb +36 -29
- data/lib/concurrent/thread_local_var.rb +117 -0
- data/lib/concurrent/timer_task.rb +28 -30
- data/lib/concurrent/utilities.rb +1 -1
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/agent_spec.rb +121 -230
- data/spec/concurrent/atomic_spec.rb +201 -0
- data/spec/concurrent/condition_spec.rb +171 -0
- data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
- data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
- data/spec/concurrent/count_down_latch_spec.rb +125 -0
- data/spec/concurrent/dataflow_spec.rb +160 -0
- data/spec/concurrent/dereferenceable_shared.rb +145 -0
- data/spec/concurrent/event_spec.rb +44 -9
- data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
- data/spec/concurrent/future_spec.rb +184 -69
- data/spec/concurrent/ivar_spec.rb +192 -0
- data/spec/concurrent/mvar_spec.rb +380 -0
- data/spec/concurrent/obligation_spec.rb +193 -0
- data/spec/concurrent/observer_set_shared.rb +233 -0
- data/spec/concurrent/postable_shared.rb +3 -7
- data/spec/concurrent/promise_spec.rb +270 -192
- data/spec/concurrent/safe_task_executor_spec.rb +58 -0
- data/spec/concurrent/scheduled_task_spec.rb +142 -38
- data/spec/concurrent/thread_local_var_spec.rb +113 -0
- data/spec/concurrent/thread_pool_shared.rb +2 -3
- data/spec/concurrent/timer_task_spec.rb +31 -1
- data/spec/spec_helper.rb +2 -3
- data/spec/support/functions.rb +4 -0
- data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
- metadata +50 -30
- data/lib/concurrent/contract.rb +0 -21
- data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
- data/md/actor.md +0 -404
- data/md/agent.md +0 -142
- data/md/channel.md +0 -40
- data/md/dereferenceable.md +0 -49
- data/md/future.md +0 -125
- data/md/obligation.md +0 -32
- data/md/promise.md +0 -217
- data/md/scheduled_task.md +0 -156
- data/md/supervisor.md +0 -246
- data/md/thread_pool.md +0 -225
- data/md/timer_task.md +0 -191
- data/spec/concurrent/contract_spec.rb +0 -34
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'concurrent/atomic'
|
2
|
+
require 'concurrent/future'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
|
6
|
+
# @!visibility private
|
7
|
+
class DependencyCounter # :nodoc:
|
8
|
+
|
9
|
+
def initialize(count, &block)
|
10
|
+
@counter = AtomicFixnum.new(count)
|
11
|
+
@block = block
|
12
|
+
end
|
13
|
+
|
14
|
+
def update(time, value, reason)
|
15
|
+
if @counter.decrement == 0
|
16
|
+
@block.call
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Dataflow allows you to create a task that will be scheduled then all of its
|
22
|
+
# data dependencies are available. Data dependencies are +Future+ values. The
|
23
|
+
# dataflow task itself is also a +Future+ value, so you can build up a graph of
|
24
|
+
# these tasks, each of which is run when all the data and other tasks it depends
|
25
|
+
# on are available or completed.
|
26
|
+
#
|
27
|
+
# Our syntax is somewhat related to that of Akka's +flow+ and Habanero Java's
|
28
|
+
# +DataDrivenFuture+. However unlike Akka we don't schedule a task at all until
|
29
|
+
# it is ready to run, and unlike Habanero Java we pass the data values into the
|
30
|
+
# task instead of dereferencing them again in the task.
|
31
|
+
#
|
32
|
+
# The theory of dataflow goes back to the 80s. In the terminology of the literature,
|
33
|
+
# our implementation is coarse-grained, in that each task can be many instructions,
|
34
|
+
# and dynamic in that you can create more tasks within other tasks.
|
35
|
+
#
|
36
|
+
# @example Parallel Fibonacci calculator
|
37
|
+
# def fib(n)
|
38
|
+
# if n < 2
|
39
|
+
# Concurrent::dataflow { n }
|
40
|
+
# else
|
41
|
+
# n1 = fib(n - 1)
|
42
|
+
# n2 = fib(n - 2)
|
43
|
+
# Concurrent::dataflow(n1, n2) { |v1, v2| v1 + v2 }
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# f = fib(14) #=> #<Concurrent::Future:0x000001019a26d8 ...
|
48
|
+
#
|
49
|
+
# # wait up to 1 second for the answer...
|
50
|
+
# f.value(1) #=> 377
|
51
|
+
#
|
52
|
+
# @param [Future] inputs zero or more +Future+ operations that this dataflow depends upon
|
53
|
+
#
|
54
|
+
# @yield The operation to perform once all the dependencies are met
|
55
|
+
# @yieldparam [Future] inputs each of the +Future+ inputs to the dataflow
|
56
|
+
# @yieldreturn [Object] the result of the block operation
|
57
|
+
#
|
58
|
+
# @return [Object] the result of all the operations
|
59
|
+
#
|
60
|
+
# @raise [ArgumentError] if no block is given
|
61
|
+
# @raise [ArgumentError] if any of the inputs are not +IVar+s
|
62
|
+
def dataflow(*inputs, &block)
|
63
|
+
raise ArgumentError.new('no block given') unless block_given?
|
64
|
+
raise ArgumentError.new('not all dependencies are IVars') unless inputs.all? { |input| input.is_a? IVar }
|
65
|
+
|
66
|
+
result = Future.new do
|
67
|
+
values = inputs.map { |input| input.value }
|
68
|
+
block.call(*values)
|
69
|
+
end
|
70
|
+
|
71
|
+
if inputs.empty?
|
72
|
+
result.execute
|
73
|
+
else
|
74
|
+
counter = DependencyCounter.new(inputs.size) { result.execute }
|
75
|
+
|
76
|
+
inputs.each do |input|
|
77
|
+
input.add_observer counter
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
result
|
82
|
+
end
|
83
|
+
|
84
|
+
module_function :dataflow
|
85
|
+
end
|
@@ -1,24 +1,76 @@
|
|
1
1
|
module Concurrent
|
2
2
|
|
3
3
|
# Object references in Ruby are mutable. This can lead to serious problems when
|
4
|
-
# the
|
5
|
-
# case unless the value is a
|
6
|
-
# Most classes in this library that expose a
|
4
|
+
# the +#value+ of a concurrent object is a mutable reference. Which is always the
|
5
|
+
# case unless the value is a +Fixnum+, +Symbol+, or similar "primitive" data type.
|
6
|
+
# Most classes in this library that expose a +#value+ getter method do so using
|
7
7
|
# this mixin module.
|
8
8
|
module Dereferenceable
|
9
9
|
|
10
|
+
# Return the value this object represents after applying the options specified
|
11
|
+
# by the +#set_deref_options+ method.
|
12
|
+
#
|
13
|
+
# When multiple deref options are set the order of operations is strictly defined.
|
14
|
+
# The order of deref operations is:
|
15
|
+
# * +:copy_on_deref+
|
16
|
+
# * +:dup_on_deref+
|
17
|
+
# * +:freeze_on_deref+
|
18
|
+
#
|
19
|
+
# Because of this ordering there is no need to +#freeze+ an object created by a
|
20
|
+
# provided +:copy_on_deref+ block. Simply set +:freeze_on_deref+ to +true+.
|
21
|
+
# Setting both +:dup_on_deref+ to +true+ and +:freeze_on_deref+ to +true is
|
22
|
+
# as close to the behavior of a "pure" functional language (like Erlang, Clojure,
|
23
|
+
# or Haskell) as we are likely to get in Ruby.
|
24
|
+
#
|
25
|
+
# This method is thread-safe and synchronized with the internal +#mutex+.
|
26
|
+
#
|
27
|
+
# @return [Object] the current value of the object
|
28
|
+
def value
|
29
|
+
mutex.synchronize do
|
30
|
+
apply_deref_options(@value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias_method :deref, :value
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
# A mutex lock used for synchronizing thread-safe operations. Methods defined
|
38
|
+
# by +Dereferenceable+ are synchronized using the +Mutex+ returned from this
|
39
|
+
# method. Operations performed by the including class that operate on the
|
40
|
+
# +@value+ instance variable should be locked with this +Mutex+.
|
41
|
+
#
|
42
|
+
# @return [Mutex] the synchronization object
|
43
|
+
#
|
44
|
+
# @!visibility public
|
45
|
+
def mutex
|
46
|
+
@mutex
|
47
|
+
end
|
48
|
+
|
49
|
+
# Initializes the internal +Mutex+.
|
50
|
+
#
|
51
|
+
# @note This method *must* be called from within the constructor of the including class.
|
52
|
+
#
|
53
|
+
# @see #mutex
|
54
|
+
#
|
55
|
+
# @!visibility public
|
56
|
+
def init_mutex
|
57
|
+
@mutex = Mutex.new
|
58
|
+
end
|
59
|
+
|
10
60
|
# Set the options which define the operations #value performs before
|
11
61
|
# returning data to the caller (dereferencing).
|
12
62
|
#
|
13
|
-
# @note
|
63
|
+
# @note Most classes that include this module will call +#set_deref_options+
|
14
64
|
# from within the constructor, thus allowing these options to be set at
|
15
65
|
# object creation.
|
16
66
|
#
|
17
67
|
# @param [Hash] opts the options defining dereference behavior.
|
18
|
-
# @option opts [String] :dup_on_deref
|
19
|
-
# @option opts [String] :freeze_on_deref
|
20
|
-
# @option opts [String] :copy_on_deref
|
21
|
-
# returning the value returned from the proc
|
68
|
+
# @option opts [String] :dup_on_deref (false) call +#dup+ before returning the data
|
69
|
+
# @option opts [String] :freeze_on_deref (false) call +#freeze+ before returning the data
|
70
|
+
# @option opts [String] :copy_on_deref (nil) call the given +Proc+ passing the internal value and
|
71
|
+
# returning the value returned from the proc
|
72
|
+
#
|
73
|
+
# @!visibility public
|
22
74
|
def set_deref_options(opts = {})
|
23
75
|
mutex.synchronize do
|
24
76
|
@dup_on_deref = opts[:dup_on_deref] || opts[:dup]
|
@@ -28,29 +80,15 @@ module Concurrent
|
|
28
80
|
end
|
29
81
|
end
|
30
82
|
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
return
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
value = value.freeze if @freeze_on_deref
|
41
|
-
value
|
42
|
-
end
|
43
|
-
end
|
44
|
-
alias_method :deref, :value
|
45
|
-
|
46
|
-
protected
|
47
|
-
|
48
|
-
def mutex # :nodoc:
|
49
|
-
@mutex
|
50
|
-
end
|
51
|
-
|
52
|
-
def init_mutex
|
53
|
-
@mutex = Mutex.new
|
83
|
+
# @!visibility private
|
84
|
+
def apply_deref_options(value) # :nodoc:
|
85
|
+
return nil if value.nil?
|
86
|
+
return value if @do_nothing_on_deref
|
87
|
+
value = value
|
88
|
+
value = @copy_on_deref.call(value) if @copy_on_deref
|
89
|
+
value = value.dup if @dup_on_deref
|
90
|
+
value = value.freeze if @freeze_on_deref
|
91
|
+
value
|
54
92
|
end
|
55
93
|
end
|
56
94
|
end
|
data/lib/concurrent/event.rb
CHANGED
@@ -1,41 +1,42 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'concurrent/utilities'
|
3
|
+
require 'concurrent/condition'
|
3
4
|
|
4
5
|
module Concurrent
|
5
6
|
|
6
7
|
# Old school kernel-style event reminiscent of Win32 programming in C++.
|
7
8
|
#
|
8
|
-
# When an
|
9
|
-
#
|
10
|
-
# thread wants to alert all blocking threads it calls the
|
11
|
-
# will then wake up all listeners. Once an
|
12
|
-
# New threads calling
|
13
|
-
#
|
9
|
+
# When an +Event+ is created it is in the +unset+ state. Threads can choose to
|
10
|
+
# +#wait+ on the event, blocking until released by another thread. When one
|
11
|
+
# thread wants to alert all blocking threads it calls the +#set+ method which
|
12
|
+
# will then wake up all listeners. Once an +Event+ has been set it remains set.
|
13
|
+
# New threads calling +#wait+ will return immediately. An +Event+ may be
|
14
|
+
# +#reset+ at any time once it has been set.
|
14
15
|
#
|
15
16
|
# @see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682655.aspx
|
16
17
|
class Event
|
17
18
|
|
18
|
-
# Creates a new
|
19
|
-
#
|
19
|
+
# Creates a new +Event+ in the unset state. Threads calling +#wait+ on the
|
20
|
+
# +Event+ will block.
|
20
21
|
def initialize
|
21
22
|
@set = false
|
22
23
|
@mutex = Mutex.new
|
23
|
-
@condition =
|
24
|
+
@condition = Condition.new
|
24
25
|
end
|
25
26
|
|
26
27
|
# Is the object in the set state?
|
27
28
|
#
|
28
|
-
# @return [Boolean] indicating whether or not the
|
29
|
+
# @return [Boolean] indicating whether or not the +Event+ has been set
|
29
30
|
def set?
|
30
31
|
@mutex.synchronize do
|
31
32
|
@set
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
35
|
-
# Trigger the event, setting the state to
|
36
|
-
# waiting on the event. Has no effect if the
|
36
|
+
# Trigger the event, setting the state to +set+ and releasing all threads
|
37
|
+
# waiting on the event. Has no effect if the +Event+ has already been set.
|
37
38
|
#
|
38
|
-
# @return [Boolean] should always return
|
39
|
+
# @return [Boolean] should always return +true+
|
39
40
|
def set
|
40
41
|
@mutex.synchronize do
|
41
42
|
return true if @set
|
@@ -46,10 +47,10 @@ module Concurrent
|
|
46
47
|
true
|
47
48
|
end
|
48
49
|
|
49
|
-
# Reset a previously set event back to the
|
50
|
-
# Has no effect if the
|
50
|
+
# Reset a previously set event back to the +unset+ state.
|
51
|
+
# Has no effect if the +Event+ has not yet been set.
|
51
52
|
#
|
52
|
-
# @return [Boolean] should always return
|
53
|
+
# @return [Boolean] should always return +true+
|
53
54
|
def reset
|
54
55
|
@mutex.synchronize do
|
55
56
|
@set = false
|
@@ -58,15 +59,20 @@ module Concurrent
|
|
58
59
|
true
|
59
60
|
end
|
60
61
|
|
61
|
-
# Wait a given number of seconds for the
|
62
|
-
# thread. Will wait forever when no
|
63
|
-
# immediately if the
|
62
|
+
# Wait a given number of seconds for the +Event+ to be set by another
|
63
|
+
# thread. Will wait forever when no +timeout+ value is given. Returns
|
64
|
+
# immediately if the +Event+ has already been set.
|
64
65
|
#
|
65
|
-
# @return [Boolean] true if the
|
66
|
+
# @return [Boolean] true if the +Event+ was set before timeout else false
|
66
67
|
def wait(timeout = nil)
|
67
68
|
@mutex.synchronize do
|
68
69
|
return true if @set
|
69
|
-
|
70
|
+
|
71
|
+
remaining = Condition::Result.new(timeout)
|
72
|
+
while !@set && remaining.can_wait?
|
73
|
+
remaining = @condition.wait(@mutex, remaining.remaining_time)
|
74
|
+
end
|
75
|
+
|
70
76
|
@set
|
71
77
|
end
|
72
78
|
end
|
data/lib/concurrent/future.rb
CHANGED
@@ -1,62 +1,122 @@
|
|
1
1
|
require 'thread'
|
2
|
-
require 'observer'
|
3
2
|
|
4
|
-
require 'concurrent/global_thread_pool'
|
5
3
|
require 'concurrent/obligation'
|
4
|
+
require 'concurrent/global_thread_pool'
|
5
|
+
require 'concurrent/safe_task_executor'
|
6
6
|
|
7
7
|
module Concurrent
|
8
8
|
|
9
|
-
|
9
|
+
# A +Future+ represents a promise to complete an action at some time in the future.
|
10
|
+
# The action is atomic and permanent. The idea behind a future is to send an operation
|
11
|
+
# for asynchronous completion, do other stuff, then return and retrieve the result
|
12
|
+
# of the async operation at a later time.
|
13
|
+
#
|
14
|
+
# A +Future+ has four possible states: *:unscheduled*, *:pending*, *:rejected*, or *:fulfilled*.
|
15
|
+
# When a +Future+ is created its state is set to *:unscheduled*. Once the +#execute+ method is
|
16
|
+
# called the state becomes *:pending* and will remain in that state until processing is
|
17
|
+
# complete. A completed +Future+ is either *:rejected*, indicating that an exception was
|
18
|
+
# thrown during processing, or *:fulfilled*, indicating success. If a +Future+ is *:fulfilled*
|
19
|
+
# its +value+ will be updated to reflect the result of the operation. If *:rejected* the
|
20
|
+
# +reason+ will be updated with a reference to the thrown exception. The predicate methods
|
21
|
+
# +#unscheduled?+, +#pending?+, +#rejected?+, and +fulfilled?+ can be called at any time to
|
22
|
+
# obtain the state of the +Future+, as can the +#state+ method, which returns a symbol.
|
23
|
+
#
|
24
|
+
# Retrieving the value of a +Future+ is done through the +#value+ (alias: +#deref+) method.
|
25
|
+
# Obtaining the value of a +Future+ is a potentially blocking operation. When a +Future+ is
|
26
|
+
# *:rejected* a call to +#value+ will return +nil+ immediately. When a +Future+ is
|
27
|
+
# *:fulfilled* a call to +#value+ will immediately return the current value. When a
|
28
|
+
# +Future+ is *:pending* a call to +#value+ will block until the +Future+ is either
|
29
|
+
# *:rejected* or *:fulfilled*. A *timeout* value can be passed to +#value+ to limit how
|
30
|
+
# long the call will block. If +nil+ the call will block indefinitely. If +0+ the call will
|
31
|
+
# not block. Any other integer or float value will indicate the maximum number of seconds to block.
|
32
|
+
#
|
33
|
+
# The +Future+ class also includes the behavior of the Ruby standard library +Observable+ module,
|
34
|
+
# but does so in a thread-safe way. On fulfillment or rejection all observers will be notified
|
35
|
+
# according to the normal +Observable+ behavior. The observer callback function will be called
|
36
|
+
# with three parameters: the +Time+ of fulfillment/rejection, the final +value+, and the final
|
37
|
+
# +reason+. Observers added after fulfillment/rejection will still be notified as normal.
|
38
|
+
#
|
39
|
+
# @see http://ruby-doc.org/stdlib-2.1.1/libdoc/observer/rdoc/Observable.html Ruby Observable module
|
40
|
+
# @see http://clojuredocs.org/clojure_core/clojure.core/future Clojure's future function
|
41
|
+
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html java.util.concurrent.Future
|
42
|
+
class Future < IVar
|
10
43
|
include Obligation
|
11
|
-
include Observable
|
12
44
|
include UsesGlobalThreadPool
|
13
45
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
46
|
+
# Create a new +Future+ in the +:unscheduled+ state.
|
47
|
+
#
|
48
|
+
# @yield the asynchronous operation to perform
|
49
|
+
#
|
50
|
+
# @param [Hash] opts the options to create a message with
|
51
|
+
# @option opts [String] :dup_on_deref (false) call +#dup+ before returning the data
|
52
|
+
# @option opts [String] :freeze_on_deref (false) call +#freeze+ before returning the data
|
53
|
+
# @option opts [String] :copy_on_deref (nil) call the given +Proc+ passing the internal value and
|
54
|
+
# returning the value returned from the proc
|
55
|
+
#
|
56
|
+
# @raise [ArgumentError] if no block is given
|
57
|
+
def initialize(opts = {}, &block)
|
58
|
+
raise ArgumentError.new('no block given') unless block_given?
|
59
|
+
super(IVar::NO_VALUE, opts)
|
60
|
+
@state = :unscheduled
|
61
|
+
@task = block
|
25
62
|
end
|
26
63
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
64
|
+
# Execute an +:unscheduled+ +Future+. Immediately sets the state to +:pending+ and
|
65
|
+
# passes the block to a new thread/thread pool for eventual execution.
|
66
|
+
# Does nothing if the +Future+ is in any state other than +:unscheduled+.
|
67
|
+
#
|
68
|
+
# @return [Future] a reference to +self+
|
69
|
+
#
|
70
|
+
# @example Instance and execute in separate steps
|
71
|
+
# future = Concurrent::Future.new{ sleep(1); 42 }
|
72
|
+
# future.state #=> :unscheduled
|
73
|
+
# future.execute
|
74
|
+
# future.state #=> :pending
|
75
|
+
#
|
76
|
+
# @example Instance and execute in one line
|
77
|
+
# future = Concurrent::Future.new{ sleep(1); 42 }.execute
|
78
|
+
# future.state #=> :pending
|
79
|
+
#
|
80
|
+
# @since 0.5.0
|
81
|
+
def execute
|
82
|
+
if compare_and_set_state(:pending, :unscheduled)
|
83
|
+
Future.thread_pool.post { work }
|
84
|
+
self
|
37
85
|
end
|
38
|
-
return func
|
39
86
|
end
|
40
87
|
|
88
|
+
# Create a new +Future+ object with the given block, execute it, and return the
|
89
|
+
# +:pending+ object.
|
90
|
+
#
|
91
|
+
# @yield the asynchronous operation to perform
|
92
|
+
#
|
93
|
+
# @option opts [String] :dup_on_deref (false) call +#dup+ before returning the data
|
94
|
+
# @option opts [String] :freeze_on_deref (false) call +#freeze+ before returning the data
|
95
|
+
# @option opts [String] :copy_on_deref (nil) call the given +Proc+ passing the internal value and
|
96
|
+
# returning the value returned from the proc
|
97
|
+
#
|
98
|
+
# @return [Future] the newly created +Future+ in the +:pending+ state
|
99
|
+
#
|
100
|
+
# @raise [ArgumentError] if no block is given
|
101
|
+
#
|
102
|
+
# @example
|
103
|
+
# future = Concurrent::Future.execute{ sleep(1); 42 }
|
104
|
+
# future.state #=> :pending
|
105
|
+
#
|
106
|
+
# @since 0.5.0
|
107
|
+
def self.execute(opts = {}, &block)
|
108
|
+
return Future.new(opts, &block).execute
|
109
|
+
end
|
110
|
+
|
111
|
+
protected :set, :fail, :complete
|
112
|
+
|
41
113
|
private
|
42
114
|
|
43
|
-
#
|
44
|
-
def work
|
45
|
-
|
46
|
-
|
47
|
-
@state = :fulfilled
|
48
|
-
rescue Exception => ex
|
49
|
-
@reason = ex
|
50
|
-
@state = :rejected
|
51
|
-
ensure
|
52
|
-
val = self.value
|
53
|
-
mutex.synchronize do
|
54
|
-
event.set
|
55
|
-
changed
|
56
|
-
notify_observers(Time.now, val, @reason)
|
57
|
-
delete_observers
|
58
|
-
end
|
59
|
-
end
|
115
|
+
# @!visibility private
|
116
|
+
def work # :nodoc:
|
117
|
+
success, val, reason = SafeTaskExecutor.new(@task).execute
|
118
|
+
complete(success, val, reason)
|
60
119
|
end
|
120
|
+
|
61
121
|
end
|
62
122
|
end
|