7
7
module Concurrent
8
8
9
9
# Lazy evaluation of a block yielding an immutable result. Useful for
10
- # expensive operations that may never be needed. `Delay` is a more
11
- # complex and feature-rich version of `LazyReference`. It is non-blocking,
10
+ # expensive operations that may never be needed. It may be non-blocking,
12
11
# supports the `Obligation` interface, and accepts the injection of
13
12
# custom executor upon which to execute the block. Processing of
14
13
# block will be deferred until the first time `#value` is called.
@@ -29,13 +28,26 @@ module Concurrent
29
28
# `Delay` includes the `Concurrent::Dereferenceable` mixin to support thread
30
29
# safety of the reference returned by `#value`.
31
30
#
31
+ # @!macro [attach] delay_note_regarding_blocking
32
+ # @note The default behavior of `Delay` is to block indefinitely when
33
+ # calling either `value` or `wait`, executing the delayed operation on
34
+ # the current thread. This makes the `timeout` value completely
35
+ # irrelevant. To enable non-blocking behavior, use the `executor`
36
+ # constructor option. This will cause the delayed operation to be
37
+ # execute on the given executor, allowing the call to timeout.
38
+ #
32
39
# Because of its simplicity `LazyReference` is much faster than `Delay`:
33
40
#
34
- # user system total real
35
- # Benchmarking Delay...
36
- # 0.730000 0.000000 0.730000 ( 0.738434)
37
- # Benchmarking LazyReference...
38
- # 0.040000 0.000000 0.040000 ( 0.042322)
41
+ # Rehearsal -------------------------------------------------------
42
+ # Delay#value 0.210000 0.000000 0.210000 ( 0.208207)
43
+ # Delay#value! 0.240000 0.000000 0.240000 ( 0.247136)
44
+ # LazyReference#value 0.160000 0.000000 0.160000 ( 0.158399)
45
+ # ---------------------------------------------- total: 0.610000sec
46
+ #
47
+ # user system total real
48
+ # Delay#value 0.200000 0.000000 0.200000 ( 0.203602)
49
+ # Delay#value! 0.250000 0.000000 0.250000 ( 0.252535)
50
+ # LazyReference#value 0.150000 0.000000 0.150000 ( 0.154053)
39
51
#
40
52
# @see Concurrent::Dereferenceable
41
53
# @see Concurrent::LazyReference
@@ -54,11 +66,67 @@ def initialize(opts = {}, &block)
54
66
raise ArgumentError . new ( 'no block given' ) unless block_given?
55
67
56
68
init_obligation
57
- @state = :pending
58
- @task = block
69
+
70
+ @state = :pending
71
+ @task = block
72
+ @computing = false
73
+
59
74
set_deref_options ( opts )
60
- @task_executor = get_executor_from ( opts ) || Concurrent ::GLOBAL_IMMEDIATE_EXECUTOR
61
- @computing = false
75
+
76
+ @task_executor = get_executor_from ( opts )
77
+ end
78
+
79
+ # Return the value this object represents after applying the options
80
+ # specified by the `#set_deref_options` method. If the delayed operation
81
+ # raised an exception this method will return nil. The execption object
82
+ # can be accessed via the `#reason` method.
83
+ #
84
+ # @param [Numeric] timeout the maximum number of seconds to wait
85
+ # @return [Object] the current value of the object
86
+ #
87
+ # @!macro delay_note_regarding_blocking
88
+ def value ( timeout = nil )
89
+ if @task_executor
90
+ super
91
+ else
92
+ # this function has been optimized for performance and
93
+ # should not be modified without running new benchmarks
94
+ mutex . synchronize do
95
+ execute = @computing = true unless @computing
96
+ if execute
97
+ begin
98
+ set_state ( true , @task . call , nil )
99
+ rescue => ex
100
+ set_state ( false , nil , ex )
101
+ end
102
+ end
103
+ end
104
+ if @do_nothing_on_deref
105
+ @value
106
+ else
107
+ apply_deref_options ( @value )
108
+ end
109
+ end
110
+ end
111
+
112
+ # Return the value this object represents after applying the options
113
+ # specified by the `#set_deref_options` method. If the delayed operation
114
+ # raised an exception, this method will raise that exception (even when)
115
+ # the operation has already been executed).
116
+ #
117
+ # @param [Numeric] timeout the maximum number of seconds to wait
118
+ # @return [Object] the current value of the object
119
+ # @raise [Exception] when `#rejected?` raises `#reason`
120
+ #
121
+ # @!macro delay_note_regarding_blocking
122
+ def value! ( timeout = nil )
123
+ if @task_executor
124
+ super
125
+ else
126
+ result = value
127
+ raise @reason if @reason
128
+ result
129
+ end
62
130
end
63
131
64
132
# Return the value this object represents after applying the options
@@ -67,10 +135,17 @@ def initialize(opts = {}, &block)
67
135
# @param [Integer] timeout (nil) the maximum number of seconds to wait for
68
136
# the value to be computed. When `nil` the caller will block indefinitely.
69
137
#
70
- # @return [Object] the current value of the object
138
+ # @return [Object] self
139
+ #
140
+ # @!macro delay_note_regarding_blocking
71
141
def wait ( timeout = nil )
72
- execute_task_once
73
- super ( timeout )
142
+ if @task_executor
143
+ execute_task_once
144
+ super ( timeout )
145
+ else
146
+ value
147
+ end
148
+ self
74
149
end
75
150
76
151
# Reconfigures the block returning the value if still `#incomplete?`
@@ -108,7 +183,7 @@ def execute_task_once # :nodoc:
108
183
reason = ex
109
184
end
110
185
mutex . lock
111
- set_state success , result , reason
186
+ set_state ( success , result , reason )
112
187
event . set
113
188
mutex . unlock
114
189
end
0 commit comments