concurrent-ruby 1.1.5

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.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +478 -0
  3. data/Gemfile +41 -0
  4. data/LICENSE.md +23 -0
  5. data/README.md +381 -0
  6. data/Rakefile +327 -0
  7. data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
  10. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
  11. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
  12. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +159 -0
  13. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +307 -0
  14. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
  15. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
  16. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
  17. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
  18. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
  19. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
  20. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
  21. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
  22. data/lib/concurrent-ruby.rb +1 -0
  23. data/lib/concurrent.rb +134 -0
  24. data/lib/concurrent/agent.rb +587 -0
  25. data/lib/concurrent/array.rb +66 -0
  26. data/lib/concurrent/async.rb +459 -0
  27. data/lib/concurrent/atom.rb +222 -0
  28. data/lib/concurrent/atomic/abstract_thread_local_var.rb +66 -0
  29. data/lib/concurrent/atomic/atomic_boolean.rb +126 -0
  30. data/lib/concurrent/atomic/atomic_fixnum.rb +143 -0
  31. data/lib/concurrent/atomic/atomic_markable_reference.rb +164 -0
  32. data/lib/concurrent/atomic/atomic_reference.rb +204 -0
  33. data/lib/concurrent/atomic/count_down_latch.rb +100 -0
  34. data/lib/concurrent/atomic/cyclic_barrier.rb +128 -0
  35. data/lib/concurrent/atomic/event.rb +109 -0
  36. data/lib/concurrent/atomic/java_count_down_latch.rb +42 -0
  37. data/lib/concurrent/atomic/java_thread_local_var.rb +37 -0
  38. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +62 -0
  39. data/lib/concurrent/atomic/mutex_atomic_fixnum.rb +75 -0
  40. data/lib/concurrent/atomic/mutex_count_down_latch.rb +44 -0
  41. data/lib/concurrent/atomic/mutex_semaphore.rb +115 -0
  42. data/lib/concurrent/atomic/read_write_lock.rb +254 -0
  43. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +379 -0
  44. data/lib/concurrent/atomic/ruby_thread_local_var.rb +161 -0
  45. data/lib/concurrent/atomic/semaphore.rb +145 -0
  46. data/lib/concurrent/atomic/thread_local_var.rb +104 -0
  47. data/lib/concurrent/atomic_reference/mutex_atomic.rb +56 -0
  48. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +28 -0
  49. data/lib/concurrent/atomics.rb +10 -0
  50. data/lib/concurrent/collection/copy_on_notify_observer_set.rb +107 -0
  51. data/lib/concurrent/collection/copy_on_write_observer_set.rb +111 -0
  52. data/lib/concurrent/collection/java_non_concurrent_priority_queue.rb +84 -0
  53. data/lib/concurrent/collection/lock_free_stack.rb +158 -0
  54. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +927 -0
  55. data/lib/concurrent/collection/map/mri_map_backend.rb +66 -0
  56. data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +140 -0
  57. data/lib/concurrent/collection/map/synchronized_map_backend.rb +82 -0
  58. data/lib/concurrent/collection/non_concurrent_priority_queue.rb +143 -0
  59. data/lib/concurrent/collection/ruby_non_concurrent_priority_queue.rb +150 -0
  60. data/lib/concurrent/concern/deprecation.rb +34 -0
  61. data/lib/concurrent/concern/dereferenceable.rb +73 -0
  62. data/lib/concurrent/concern/logging.rb +32 -0
  63. data/lib/concurrent/concern/obligation.rb +220 -0
  64. data/lib/concurrent/concern/observable.rb +110 -0
  65. data/lib/concurrent/concurrent_ruby.jar +0 -0
  66. data/lib/concurrent/configuration.rb +184 -0
  67. data/lib/concurrent/constants.rb +8 -0
  68. data/lib/concurrent/dataflow.rb +81 -0
  69. data/lib/concurrent/delay.rb +199 -0
  70. data/lib/concurrent/errors.rb +69 -0
  71. data/lib/concurrent/exchanger.rb +352 -0
  72. data/lib/concurrent/executor/abstract_executor_service.rb +134 -0
  73. data/lib/concurrent/executor/cached_thread_pool.rb +62 -0
  74. data/lib/concurrent/executor/executor_service.rb +185 -0
  75. data/lib/concurrent/executor/fixed_thread_pool.rb +206 -0
  76. data/lib/concurrent/executor/immediate_executor.rb +66 -0
  77. data/lib/concurrent/executor/indirect_immediate_executor.rb +44 -0
  78. data/lib/concurrent/executor/java_executor_service.rb +91 -0
  79. data/lib/concurrent/executor/java_single_thread_executor.rb +29 -0
  80. data/lib/concurrent/executor/java_thread_pool_executor.rb +123 -0
  81. data/lib/concurrent/executor/ruby_executor_service.rb +78 -0
  82. data/lib/concurrent/executor/ruby_single_thread_executor.rb +22 -0
  83. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +362 -0
  84. data/lib/concurrent/executor/safe_task_executor.rb +35 -0
  85. data/lib/concurrent/executor/serial_executor_service.rb +34 -0
  86. data/lib/concurrent/executor/serialized_execution.rb +107 -0
  87. data/lib/concurrent/executor/serialized_execution_delegator.rb +28 -0
  88. data/lib/concurrent/executor/simple_executor_service.rb +100 -0
  89. data/lib/concurrent/executor/single_thread_executor.rb +56 -0
  90. data/lib/concurrent/executor/thread_pool_executor.rb +87 -0
  91. data/lib/concurrent/executor/timer_set.rb +173 -0
  92. data/lib/concurrent/executors.rb +20 -0
  93. data/lib/concurrent/future.rb +141 -0
  94. data/lib/concurrent/hash.rb +59 -0
  95. data/lib/concurrent/immutable_struct.rb +93 -0
  96. data/lib/concurrent/ivar.rb +207 -0
  97. data/lib/concurrent/map.rb +337 -0
  98. data/lib/concurrent/maybe.rb +229 -0
  99. data/lib/concurrent/mutable_struct.rb +229 -0
  100. data/lib/concurrent/mvar.rb +242 -0
  101. data/lib/concurrent/options.rb +42 -0
  102. data/lib/concurrent/promise.rb +579 -0
  103. data/lib/concurrent/promises.rb +2167 -0
  104. data/lib/concurrent/re_include.rb +58 -0
  105. data/lib/concurrent/scheduled_task.rb +318 -0
  106. data/lib/concurrent/set.rb +66 -0
  107. data/lib/concurrent/settable_struct.rb +129 -0
  108. data/lib/concurrent/synchronization.rb +30 -0
  109. data/lib/concurrent/synchronization/abstract_lockable_object.rb +98 -0
  110. data/lib/concurrent/synchronization/abstract_object.rb +24 -0
  111. data/lib/concurrent/synchronization/abstract_struct.rb +160 -0
  112. data/lib/concurrent/synchronization/condition.rb +60 -0
  113. data/lib/concurrent/synchronization/jruby_lockable_object.rb +13 -0
  114. data/lib/concurrent/synchronization/jruby_object.rb +45 -0
  115. data/lib/concurrent/synchronization/lock.rb +36 -0
  116. data/lib/concurrent/synchronization/lockable_object.rb +74 -0
  117. data/lib/concurrent/synchronization/mri_object.rb +44 -0
  118. data/lib/concurrent/synchronization/mutex_lockable_object.rb +76 -0
  119. data/lib/concurrent/synchronization/object.rb +183 -0
  120. data/lib/concurrent/synchronization/rbx_lockable_object.rb +65 -0
  121. data/lib/concurrent/synchronization/rbx_object.rb +49 -0
  122. data/lib/concurrent/synchronization/truffleruby_object.rb +47 -0
  123. data/lib/concurrent/synchronization/volatile.rb +36 -0
  124. data/lib/concurrent/thread_safe/synchronized_delegator.rb +50 -0
  125. data/lib/concurrent/thread_safe/util.rb +16 -0
  126. data/lib/concurrent/thread_safe/util/adder.rb +74 -0
  127. data/lib/concurrent/thread_safe/util/cheap_lockable.rb +118 -0
  128. data/lib/concurrent/thread_safe/util/data_structures.rb +63 -0
  129. data/lib/concurrent/thread_safe/util/power_of_two_tuple.rb +38 -0
  130. data/lib/concurrent/thread_safe/util/striped64.rb +246 -0
  131. data/lib/concurrent/thread_safe/util/volatile.rb +75 -0
  132. data/lib/concurrent/thread_safe/util/xor_shift_random.rb +50 -0
  133. data/lib/concurrent/timer_task.rb +334 -0
  134. data/lib/concurrent/tuple.rb +86 -0
  135. data/lib/concurrent/tvar.rb +258 -0
  136. data/lib/concurrent/utility/at_exit.rb +97 -0
  137. data/lib/concurrent/utility/engine.rb +56 -0
  138. data/lib/concurrent/utility/monotonic_time.rb +58 -0
  139. data/lib/concurrent/utility/native_extension_loader.rb +79 -0
  140. data/lib/concurrent/utility/native_integer.rb +53 -0
  141. data/lib/concurrent/utility/processor_counter.rb +158 -0
  142. data/lib/concurrent/version.rb +3 -0
  143. metadata +193 -0
@@ -0,0 +1,184 @@
1
+ require 'thread'
2
+ require 'concurrent/delay'
3
+ require 'concurrent/errors'
4
+ require 'concurrent/atomic/atomic_reference'
5
+ require 'concurrent/concern/logging'
6
+ require 'concurrent/executor/immediate_executor'
7
+ require 'concurrent/executor/cached_thread_pool'
8
+ require 'concurrent/utility/at_exit'
9
+ require 'concurrent/utility/processor_counter'
10
+
11
+ module Concurrent
12
+ extend Concern::Logging
13
+
14
+ autoload :Options, 'concurrent/options'
15
+ autoload :TimerSet, 'concurrent/executor/timer_set'
16
+ autoload :ThreadPoolExecutor, 'concurrent/executor/thread_pool_executor'
17
+
18
+ # @return [Logger] Logger with provided level and output.
19
+ def self.create_simple_logger(level = Logger::FATAL, output = $stderr)
20
+ # TODO (pitr-ch 24-Dec-2016): figure out why it had to be replaced, stdlogger was deadlocking
21
+ lambda do |severity, progname, message = nil, &block|
22
+ return false if severity < level
23
+
24
+ message = block ? block.call : message
25
+ formatted_message = case message
26
+ when String
27
+ message
28
+ when Exception
29
+ format "%s (%s)\n%s",
30
+ message.message, message.class, (message.backtrace || []).join("\n")
31
+ else
32
+ message.inspect
33
+ end
34
+
35
+ output.print format "[%s] %5s -- %s: %s\n",
36
+ Time.now.strftime('%Y-%m-%d %H:%M:%S.%L'),
37
+ Logger::SEV_LABEL[severity],
38
+ progname,
39
+ formatted_message
40
+ true
41
+ end
42
+ end
43
+
44
+ # Use logger created by #create_simple_logger to log concurrent-ruby messages.
45
+ def self.use_simple_logger(level = Logger::FATAL, output = $stderr)
46
+ Concurrent.global_logger = create_simple_logger level, output
47
+ end
48
+
49
+ # @return [Logger] Logger with provided level and output.
50
+ # @deprecated
51
+ def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr)
52
+ logger = Logger.new(output)
53
+ logger.level = level
54
+ logger.formatter = lambda do |severity, datetime, progname, msg|
55
+ formatted_message = case msg
56
+ when String
57
+ msg
58
+ when Exception
59
+ format "%s (%s)\n%s",
60
+ msg.message, msg.class, (msg.backtrace || []).join("\n")
61
+ else
62
+ msg.inspect
63
+ end
64
+ format "[%s] %5s -- %s: %s\n",
65
+ datetime.strftime('%Y-%m-%d %H:%M:%S.%L'),
66
+ severity,
67
+ progname,
68
+ formatted_message
69
+ end
70
+
71
+ lambda do |loglevel, progname, message = nil, &block|
72
+ logger.add loglevel, message, progname, &block
73
+ end
74
+ end
75
+
76
+ # Use logger created by #create_stdlib_logger to log concurrent-ruby messages.
77
+ # @deprecated
78
+ def self.use_stdlib_logger(level = Logger::FATAL, output = $stderr)
79
+ Concurrent.global_logger = create_stdlib_logger level, output
80
+ end
81
+
82
+ # TODO (pitr-ch 27-Dec-2016): remove deadlocking stdlib_logger methods
83
+
84
+ # Suppresses all output when used for logging.
85
+ NULL_LOGGER = lambda { |level, progname, message = nil, &block| }
86
+
87
+ # @!visibility private
88
+ GLOBAL_LOGGER = AtomicReference.new(create_simple_logger(Logger::WARN))
89
+ private_constant :GLOBAL_LOGGER
90
+
91
+ def self.global_logger
92
+ GLOBAL_LOGGER.value
93
+ end
94
+
95
+ def self.global_logger=(value)
96
+ GLOBAL_LOGGER.value = value
97
+ end
98
+
99
+ # @!visibility private
100
+ GLOBAL_FAST_EXECUTOR = Delay.new { Concurrent.new_fast_executor(auto_terminate: true) }
101
+ private_constant :GLOBAL_FAST_EXECUTOR
102
+
103
+ # @!visibility private
104
+ GLOBAL_IO_EXECUTOR = Delay.new { Concurrent.new_io_executor(auto_terminate: true) }
105
+ private_constant :GLOBAL_IO_EXECUTOR
106
+
107
+ # @!visibility private
108
+ GLOBAL_TIMER_SET = Delay.new { TimerSet.new(auto_terminate: true) }
109
+ private_constant :GLOBAL_TIMER_SET
110
+
111
+ # @!visibility private
112
+ GLOBAL_IMMEDIATE_EXECUTOR = ImmediateExecutor.new
113
+ private_constant :GLOBAL_IMMEDIATE_EXECUTOR
114
+
115
+ # Disables AtExit handlers including pool auto-termination handlers.
116
+ # When disabled it will be the application programmer's responsibility
117
+ # to ensure that the handlers are shutdown properly prior to application
118
+ # exit by calling {AtExit.run} method.
119
+ #
120
+ # @note this option should be needed only because of `at_exit` ordering
121
+ # issues which may arise when running some of the testing frameworks.
122
+ # E.g. Minitest's test-suite runs itself in `at_exit` callback which
123
+ # executes after the pools are already terminated. Then auto termination
124
+ # needs to be disabled and called manually after test-suite ends.
125
+ # @note This method should *never* be called
126
+ # from within a gem. It should *only* be used from within the main
127
+ # application and even then it should be used only when necessary.
128
+ # @see AtExit
129
+ def self.disable_at_exit_handlers!
130
+ AtExit.enabled = false
131
+ end
132
+
133
+ # Global thread pool optimized for short, fast *operations*.
134
+ #
135
+ # @return [ThreadPoolExecutor] the thread pool
136
+ def self.global_fast_executor
137
+ GLOBAL_FAST_EXECUTOR.value
138
+ end
139
+
140
+ # Global thread pool optimized for long, blocking (IO) *tasks*.
141
+ #
142
+ # @return [ThreadPoolExecutor] the thread pool
143
+ def self.global_io_executor
144
+ GLOBAL_IO_EXECUTOR.value
145
+ end
146
+
147
+ def self.global_immediate_executor
148
+ GLOBAL_IMMEDIATE_EXECUTOR
149
+ end
150
+
151
+ # Global thread pool user for global *timers*.
152
+ #
153
+ # @return [Concurrent::TimerSet] the thread pool
154
+ def self.global_timer_set
155
+ GLOBAL_TIMER_SET.value
156
+ end
157
+
158
+ # General access point to global executors.
159
+ # @param [Symbol, Executor] executor_identifier symbols:
160
+ # - :fast - {Concurrent.global_fast_executor}
161
+ # - :io - {Concurrent.global_io_executor}
162
+ # - :immediate - {Concurrent.global_immediate_executor}
163
+ # @return [Executor]
164
+ def self.executor(executor_identifier)
165
+ Options.executor(executor_identifier)
166
+ end
167
+
168
+ def self.new_fast_executor(opts = {})
169
+ FixedThreadPool.new(
170
+ [2, Concurrent.processor_count].max,
171
+ auto_terminate: opts.fetch(:auto_terminate, true),
172
+ idletime: 60, # 1 minute
173
+ max_queue: 0, # unlimited
174
+ fallback_policy: :abort # shouldn't matter -- 0 max queue
175
+ )
176
+ end
177
+
178
+ def self.new_io_executor(opts = {})
179
+ CachedThreadPool.new(
180
+ auto_terminate: opts.fetch(:auto_terminate, true),
181
+ fallback_policy: :abort # shouldn't matter -- 0 max queue
182
+ )
183
+ end
184
+ end
@@ -0,0 +1,8 @@
1
+ module Concurrent
2
+
3
+ # Various classes within allows for +nil+ values to be stored,
4
+ # so a special +NULL+ token is required to indicate the "nil-ness".
5
+ # @!visibility private
6
+ NULL = ::Object.new
7
+
8
+ end
@@ -0,0 +1,81 @@
1
+ require 'concurrent/future'
2
+ require 'concurrent/atomic/atomic_fixnum'
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 when all of its data dependencies are available.
22
+ # {include:file:docs-source/dataflow.md}
23
+ #
24
+ # @param [Future] inputs zero or more `Future` operations that this dataflow depends upon
25
+ #
26
+ # @yield The operation to perform once all the dependencies are met
27
+ # @yieldparam [Future] inputs each of the `Future` inputs to the dataflow
28
+ # @yieldreturn [Object] the result of the block operation
29
+ #
30
+ # @return [Object] the result of all the operations
31
+ #
32
+ # @raise [ArgumentError] if no block is given
33
+ # @raise [ArgumentError] if any of the inputs are not `IVar`s
34
+ def dataflow(*inputs, &block)
35
+ dataflow_with(Concurrent.global_io_executor, *inputs, &block)
36
+ end
37
+ module_function :dataflow
38
+
39
+ def dataflow_with(executor, *inputs, &block)
40
+ call_dataflow(:value, executor, *inputs, &block)
41
+ end
42
+ module_function :dataflow_with
43
+
44
+ def dataflow!(*inputs, &block)
45
+ dataflow_with!(Concurrent.global_io_executor, *inputs, &block)
46
+ end
47
+ module_function :dataflow!
48
+
49
+ def dataflow_with!(executor, *inputs, &block)
50
+ call_dataflow(:value!, executor, *inputs, &block)
51
+ end
52
+ module_function :dataflow_with!
53
+
54
+ private
55
+
56
+ def call_dataflow(method, executor, *inputs, &block)
57
+ raise ArgumentError.new('an executor must be provided') if executor.nil?
58
+ raise ArgumentError.new('no block given') unless block_given?
59
+ unless inputs.all? { |input| input.is_a? IVar }
60
+ raise ArgumentError.new("Not all dependencies are IVars.\nDependencies: #{ inputs.inspect }")
61
+ end
62
+
63
+ result = Future.new(executor: executor) do
64
+ values = inputs.map { |input| input.send(method) }
65
+ block.call(*values)
66
+ end
67
+
68
+ if inputs.empty?
69
+ result.execute
70
+ else
71
+ counter = DependencyCounter.new(inputs.size) { result.execute }
72
+
73
+ inputs.each do |input|
74
+ input.add_observer counter
75
+ end
76
+ end
77
+
78
+ result
79
+ end
80
+ module_function :call_dataflow
81
+ end
@@ -0,0 +1,199 @@
1
+ require 'thread'
2
+ require 'concurrent/concern/obligation'
3
+ require 'concurrent/executor/immediate_executor'
4
+ require 'concurrent/synchronization'
5
+
6
+ module Concurrent
7
+
8
+ # This file has circular require issues. It must be autoloaded here.
9
+ autoload :Options, 'concurrent/options'
10
+
11
+ # Lazy evaluation of a block yielding an immutable result. Useful for
12
+ # expensive operations that may never be needed. It may be non-blocking,
13
+ # supports the `Concern::Obligation` interface, and accepts the injection of
14
+ # custom executor upon which to execute the block. Processing of
15
+ # block will be deferred until the first time `#value` is called.
16
+ # At that time the caller can choose to return immediately and let
17
+ # the block execute asynchronously, block indefinitely, or block
18
+ # with a timeout.
19
+ #
20
+ # When a `Delay` is created its state is set to `pending`. The value and
21
+ # reason are both `nil`. The first time the `#value` method is called the
22
+ # enclosed opration will be run and the calling thread will block. Other
23
+ # threads attempting to call `#value` will block as well. Once the operation
24
+ # is complete the *value* will be set to the result of the operation or the
25
+ # *reason* will be set to the raised exception, as appropriate. All threads
26
+ # blocked on `#value` will return. Subsequent calls to `#value` will immediately
27
+ # return the cached value. The operation will only be run once. This means that
28
+ # any side effects created by the operation will only happen once as well.
29
+ #
30
+ # `Delay` includes the `Concurrent::Concern::Dereferenceable` mixin to support thread
31
+ # safety of the reference returned by `#value`.
32
+ #
33
+ # @!macro copy_options
34
+ #
35
+ # @!macro delay_note_regarding_blocking
36
+ # @note The default behavior of `Delay` is to block indefinitely when
37
+ # calling either `value` or `wait`, executing the delayed operation on
38
+ # the current thread. This makes the `timeout` value completely
39
+ # irrelevant. To enable non-blocking behavior, use the `executor`
40
+ # constructor option. This will cause the delayed operation to be
41
+ # execute on the given executor, allowing the call to timeout.
42
+ #
43
+ # @see Concurrent::Concern::Dereferenceable
44
+ class Delay < Synchronization::LockableObject
45
+ include Concern::Obligation
46
+
47
+ # NOTE: Because the global thread pools are lazy-loaded with these objects
48
+ # there is a performance hit every time we post a new task to one of these
49
+ # thread pools. Subsequently it is critical that `Delay` perform as fast
50
+ # as possible post-completion. This class has been highly optimized using
51
+ # the benchmark script `examples/lazy_and_delay.rb`. Do NOT attempt to
52
+ # DRY-up this class or perform other refactoring with running the
53
+ # benchmarks and ensuring that performance is not negatively impacted.
54
+
55
+ # Create a new `Delay` in the `:pending` state.
56
+ #
57
+ # @!macro executor_and_deref_options
58
+ #
59
+ # @yield the delayed operation to perform
60
+ #
61
+ # @raise [ArgumentError] if no block is given
62
+ def initialize(opts = {}, &block)
63
+ raise ArgumentError.new('no block given') unless block_given?
64
+ super(&nil)
65
+ synchronize { ns_initialize(opts, &block) }
66
+ end
67
+
68
+ # Return the value this object represents after applying the options
69
+ # specified by the `#set_deref_options` method. If the delayed operation
70
+ # raised an exception this method will return nil. The execption object
71
+ # can be accessed via the `#reason` method.
72
+ #
73
+ # @param [Numeric] timeout the maximum number of seconds to wait
74
+ # @return [Object] the current value of the object
75
+ #
76
+ # @!macro delay_note_regarding_blocking
77
+ def value(timeout = nil)
78
+ if @executor # TODO (pitr 12-Sep-2015): broken unsafe read?
79
+ super
80
+ else
81
+ # this function has been optimized for performance and
82
+ # should not be modified without running new benchmarks
83
+ synchronize do
84
+ execute = @evaluation_started = true unless @evaluation_started
85
+ if execute
86
+ begin
87
+ set_state(true, @task.call, nil)
88
+ rescue => ex
89
+ set_state(false, nil, ex)
90
+ end
91
+ elsif incomplete?
92
+ raise IllegalOperationError, 'Recursive call to #value during evaluation of the Delay'
93
+ end
94
+ end
95
+ if @do_nothing_on_deref
96
+ @value
97
+ else
98
+ apply_deref_options(@value)
99
+ end
100
+ end
101
+ end
102
+
103
+ # Return the value this object represents after applying the options
104
+ # specified by the `#set_deref_options` method. If the delayed operation
105
+ # raised an exception, this method will raise that exception (even when)
106
+ # the operation has already been executed).
107
+ #
108
+ # @param [Numeric] timeout the maximum number of seconds to wait
109
+ # @return [Object] the current value of the object
110
+ # @raise [Exception] when `#rejected?` raises `#reason`
111
+ #
112
+ # @!macro delay_note_regarding_blocking
113
+ def value!(timeout = nil)
114
+ if @executor
115
+ super
116
+ else
117
+ result = value
118
+ raise @reason if @reason
119
+ result
120
+ end
121
+ end
122
+
123
+ # Return the value this object represents after applying the options
124
+ # specified by the `#set_deref_options` method.
125
+ #
126
+ # @param [Integer] timeout (nil) the maximum number of seconds to wait for
127
+ # the value to be computed. When `nil` the caller will block indefinitely.
128
+ #
129
+ # @return [Object] self
130
+ #
131
+ # @!macro delay_note_regarding_blocking
132
+ def wait(timeout = nil)
133
+ if @executor
134
+ execute_task_once
135
+ super(timeout)
136
+ else
137
+ value
138
+ end
139
+ self
140
+ end
141
+
142
+ # Reconfigures the block returning the value if still `#incomplete?`
143
+ #
144
+ # @yield the delayed operation to perform
145
+ # @return [true, false] if success
146
+ def reconfigure(&block)
147
+ synchronize do
148
+ raise ArgumentError.new('no block given') unless block_given?
149
+ unless @evaluation_started
150
+ @task = block
151
+ true
152
+ else
153
+ false
154
+ end
155
+ end
156
+ end
157
+
158
+ protected
159
+
160
+ def ns_initialize(opts, &block)
161
+ init_obligation
162
+ set_deref_options(opts)
163
+ @executor = opts[:executor]
164
+
165
+ @task = block
166
+ @state = :pending
167
+ @evaluation_started = false
168
+ end
169
+
170
+ private
171
+
172
+ # @!visibility private
173
+ def execute_task_once # :nodoc:
174
+ # this function has been optimized for performance and
175
+ # should not be modified without running new benchmarks
176
+ execute = task = nil
177
+ synchronize do
178
+ execute = @evaluation_started = true unless @evaluation_started
179
+ task = @task
180
+ end
181
+
182
+ if execute
183
+ executor = Options.executor_from_options(executor: @executor)
184
+ executor.post do
185
+ begin
186
+ result = task.call
187
+ success = true
188
+ rescue => ex
189
+ reason = ex
190
+ end
191
+ synchronize do
192
+ set_state(success, result, reason)
193
+ event.set
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end