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.
- 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
|