concurrent-ruby-edge 0.1.0.pre2
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +284 -0
- data/lib/concurrent-edge.rb +11 -0
- data/lib/concurrent/actor.rb +98 -0
- data/lib/concurrent/actor/behaviour.rb +143 -0
- data/lib/concurrent/actor/behaviour/abstract.rb +51 -0
- data/lib/concurrent/actor/behaviour/awaits.rb +21 -0
- data/lib/concurrent/actor/behaviour/buffer.rb +56 -0
- data/lib/concurrent/actor/behaviour/errors_on_unknown_message.rb +12 -0
- data/lib/concurrent/actor/behaviour/executes_context.rb +17 -0
- data/lib/concurrent/actor/behaviour/linking.rb +83 -0
- data/lib/concurrent/actor/behaviour/pausing.rb +123 -0
- data/lib/concurrent/actor/behaviour/removes_child.rb +16 -0
- data/lib/concurrent/actor/behaviour/sets_results.rb +37 -0
- data/lib/concurrent/actor/behaviour/supervising.rb +39 -0
- data/lib/concurrent/actor/behaviour/terminates_children.rb +14 -0
- data/lib/concurrent/actor/behaviour/termination.rb +74 -0
- data/lib/concurrent/actor/context.rb +167 -0
- data/lib/concurrent/actor/core.rb +220 -0
- data/lib/concurrent/actor/default_dead_letter_handler.rb +9 -0
- data/lib/concurrent/actor/envelope.rb +41 -0
- data/lib/concurrent/actor/errors.rb +27 -0
- data/lib/concurrent/actor/internal_delegations.rb +59 -0
- data/lib/concurrent/actor/public_delegations.rb +40 -0
- data/lib/concurrent/actor/reference.rb +106 -0
- data/lib/concurrent/actor/root.rb +37 -0
- data/lib/concurrent/actor/type_check.rb +48 -0
- data/lib/concurrent/actor/utils.rb +10 -0
- data/lib/concurrent/actor/utils/ad_hoc.rb +27 -0
- data/lib/concurrent/actor/utils/balancer.rb +43 -0
- data/lib/concurrent/actor/utils/broadcast.rb +52 -0
- data/lib/concurrent/actor/utils/pool.rb +54 -0
- data/lib/concurrent/agent.rb +289 -0
- data/lib/concurrent/channel.rb +6 -0
- data/lib/concurrent/channel/blocking_ring_buffer.rb +82 -0
- data/lib/concurrent/channel/buffered_channel.rb +87 -0
- data/lib/concurrent/channel/channel.rb +19 -0
- data/lib/concurrent/channel/ring_buffer.rb +65 -0
- data/lib/concurrent/channel/unbuffered_channel.rb +39 -0
- data/lib/concurrent/channel/waitable_list.rb +48 -0
- data/lib/concurrent/edge/atomic_markable_reference.rb +184 -0
- data/lib/concurrent/edge/future.rb +1226 -0
- data/lib/concurrent/edge/lock_free_stack.rb +85 -0
- metadata +110 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
module Actor
|
5
|
+
module Utils
|
6
|
+
|
7
|
+
# Allows to build pub/sub easily.
|
8
|
+
# @example news
|
9
|
+
# news_channel = Concurrent::Actor::Utils::Broadcast.spawn :news
|
10
|
+
#
|
11
|
+
# 2.times do |i|
|
12
|
+
# Concurrent::Actor::Utils::AdHoc.spawn "listener-#{i}" do
|
13
|
+
# news_channel << :subscribe
|
14
|
+
# -> message { puts message }
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# news_channel << 'Ruby rocks!'
|
19
|
+
# # prints: 'Ruby rocks!' twice
|
20
|
+
class Broadcast < RestartingContext
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@receivers = Set.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_message(message)
|
27
|
+
case message
|
28
|
+
when :subscribe
|
29
|
+
if envelope.sender.is_a? Reference
|
30
|
+
@receivers.add envelope.sender
|
31
|
+
true
|
32
|
+
else
|
33
|
+
false
|
34
|
+
end
|
35
|
+
when :unsubscribe
|
36
|
+
!!@receivers.delete(envelope.sender)
|
37
|
+
when :subscribed?
|
38
|
+
@receivers.include? envelope.sender
|
39
|
+
else
|
40
|
+
filtered_receivers.each { |r| r << message }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# override to define different behaviour, filtering etc
|
45
|
+
def filtered_receivers
|
46
|
+
@receivers
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'concurrent/actor/utils/balancer'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
module Actor
|
5
|
+
module Utils
|
6
|
+
|
7
|
+
# Allows to create a pool of workers and distribute work between them
|
8
|
+
# @param [Integer] size number of workers
|
9
|
+
# @yield [balancer, index] a block spawning an worker instance. called +size+ times.
|
10
|
+
# The worker should be descendant of AbstractWorker and supervised, see example.
|
11
|
+
# @yieldparam [Balancer] balancer to pass to the worker
|
12
|
+
# @yieldparam [Integer] index of the worker, usually used in its name
|
13
|
+
# @yieldreturn [Reference] the reference of newly created worker
|
14
|
+
# @example
|
15
|
+
# class Worker < Concurrent::Actor::Utils::AbstractWorker
|
16
|
+
# def work(message)
|
17
|
+
# p message * 5
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# pool = Concurrent::Actor::Utils::Pool.spawn! 'pool', 5 do |balancer, index|
|
22
|
+
# Worker.spawn name: "worker-#{index}", supervise: true, args: [balancer]
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# pool << 'asd' << 2
|
26
|
+
# # prints:
|
27
|
+
# # "asdasdasdasdasd"
|
28
|
+
# # 10
|
29
|
+
class Pool < RestartingContext
|
30
|
+
def initialize(size, &worker_initializer)
|
31
|
+
@balancer = Balancer.spawn name: :balancer, supervise: true
|
32
|
+
@workers = Array.new(size, &worker_initializer)
|
33
|
+
@workers.each do |worker|
|
34
|
+
Type! worker, Reference
|
35
|
+
@balancer << [:subscribe, worker]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def on_message(message)
|
40
|
+
command, *rest = message
|
41
|
+
return if [:restarted, :reset, :resumed, :terminated].include? command # ignore events from supervised actors
|
42
|
+
|
43
|
+
envelope_to_redirect = if envelope.future
|
44
|
+
envelope
|
45
|
+
else
|
46
|
+
Envelope.new(envelope.message, Concurrent.future, envelope.sender, envelope.address)
|
47
|
+
end
|
48
|
+
envelope_to_redirect.future.on_completion! { @balancer << :subscribe } # TODO check safety of @balancer reading
|
49
|
+
redirect @balancer, envelope_to_redirect
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'concurrent/collection/copy_on_write_observer_set'
|
3
|
+
require 'concurrent/concern/dereferenceable'
|
4
|
+
require 'concurrent/concern/observable'
|
5
|
+
require 'concurrent/concern/logging'
|
6
|
+
require 'concurrent/executor/executor'
|
7
|
+
require 'concurrent/concern/deprecation'
|
8
|
+
|
9
|
+
module Concurrent
|
10
|
+
|
11
|
+
# `Agent`s are inspired by [Clojure's](http://clojure.org/) [agent](http://clojure.org/agents) function. An `Agent` is a single atomic value that represents an identity. The current value of the `Agent` can be requested at any time (`deref`). Each `Agent` has a work queue and operates on the global thread pool (see below). Consumers can `post` code blocks to the `Agent`. The code block (function) will receive the current value of the `Agent` as its sole parameter. The return value of the block will become the new value of the `Agent`. `Agent`s support two error handling modes: fail and continue. A good example of an `Agent` is a shared incrementing counter, such as the score in a video game.
|
12
|
+
#
|
13
|
+
# An `Agent` must be initialize with an initial value. This value is always accessible via the `value` (or `deref`) methods. Code blocks sent to the `Agent` will be processed in the order received. As each block is processed the current value is updated with the result from the block. This update is an atomic operation so a `deref` will never block and will always return the current value.
|
14
|
+
#
|
15
|
+
# When an `Agent` is created it may be given an optional `validate` block and zero or more `rescue` blocks. When a new value is calculated the value will be checked against the validator, if present. If the validator returns `true` the new value will be accepted. If it returns `false` it will be rejected. If a block raises an exception during execution the list of `rescue` blocks will be seacrhed in order until one matching the current exception is found. That `rescue` block will then be called an passed the exception object. If no matching `rescue` block is found, or none were configured, then the exception will be suppressed.
|
16
|
+
#
|
17
|
+
# `Agent`s also implement Ruby's [Observable](http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html). Code that observes an `Agent` will receive a callback with the new value any time the value is changed.
|
18
|
+
#
|
19
|
+
# @!macro copy_options
|
20
|
+
#
|
21
|
+
# @example Simple Example
|
22
|
+
#
|
23
|
+
# require 'concurrent'
|
24
|
+
#
|
25
|
+
# score = Concurrent::Agent.new(10)
|
26
|
+
# score.value #=> 10
|
27
|
+
#
|
28
|
+
# score << proc{|current| current + 100 }
|
29
|
+
# sleep(0.1)
|
30
|
+
# score.value #=> 110
|
31
|
+
#
|
32
|
+
# score << proc{|current| current * 2 }
|
33
|
+
# sleep(0.1)
|
34
|
+
# score.value #=> 220
|
35
|
+
#
|
36
|
+
# score << proc{|current| current - 50 }
|
37
|
+
# sleep(0.1)
|
38
|
+
# score.value #=> 170
|
39
|
+
#
|
40
|
+
# @example With Validation and Error Handling
|
41
|
+
#
|
42
|
+
# score = Concurrent::Agent.new(0).validate{|value| value <= 1024 }.
|
43
|
+
# rescue(NoMethodError){|ex| puts "Bam!" }.
|
44
|
+
# rescue(ArgumentError){|ex| puts "Pow!" }.
|
45
|
+
# rescue{|ex| puts "Boom!" }
|
46
|
+
# score.value #=> 0
|
47
|
+
#
|
48
|
+
# score << proc{|current| current + 2048 }
|
49
|
+
# sleep(0.1)
|
50
|
+
# score.value #=> 0
|
51
|
+
#
|
52
|
+
# score << proc{|current| raise ArgumentError }
|
53
|
+
# sleep(0.1)
|
54
|
+
# #=> puts "Pow!"
|
55
|
+
# score.value #=> 0
|
56
|
+
#
|
57
|
+
# score << proc{|current| current + 100 }
|
58
|
+
# sleep(0.1)
|
59
|
+
# score.value #=> 100
|
60
|
+
#
|
61
|
+
# @example With Observation
|
62
|
+
#
|
63
|
+
# bingo = Class.new{
|
64
|
+
# def update(time, score)
|
65
|
+
# puts "Bingo! [score: #{score}, time: #{time}]" if score >= 100
|
66
|
+
# end
|
67
|
+
# }.new
|
68
|
+
#
|
69
|
+
# score = Concurrent::Agent.new(0)
|
70
|
+
# score.add_observer(bingo)
|
71
|
+
#
|
72
|
+
# score << proc{|current| sleep(0.1); current += 30 }
|
73
|
+
# score << proc{|current| sleep(0.1); current += 30 }
|
74
|
+
# score << proc{|current| sleep(0.1); current += 30 }
|
75
|
+
# score << proc{|current| sleep(0.1); current += 30 }
|
76
|
+
#
|
77
|
+
# sleep(1)
|
78
|
+
# #=> Bingo! [score: 120, time: 2013-07-22 21:26:08 -0400]
|
79
|
+
#
|
80
|
+
# @!attribute [r] timeout
|
81
|
+
# @return [Fixnum] the maximum number of seconds before an update is cancelled
|
82
|
+
#
|
83
|
+
# @!macro edge_warning
|
84
|
+
class Agent
|
85
|
+
include Concern::Dereferenceable
|
86
|
+
include Concern::Observable
|
87
|
+
include Concern::Logging
|
88
|
+
include Concern::Deprecation
|
89
|
+
|
90
|
+
attr_reader :timeout, :io_executor, :fast_executor
|
91
|
+
|
92
|
+
# Initialize a new Agent with the given initial value and provided options.
|
93
|
+
#
|
94
|
+
# @param [Object] initial the initial value
|
95
|
+
#
|
96
|
+
# @!macro [attach] executor_and_deref_options
|
97
|
+
#
|
98
|
+
# @param [Hash] opts the options used to define the behavior at update and deref
|
99
|
+
# and to specify the executor on which to perform actions
|
100
|
+
# @option opts [Executor] :executor when set use the given `Executor` instance.
|
101
|
+
# Three special values are also supported: `:task` returns the global task pool,
|
102
|
+
# `:operation` returns the global operation pool, and `:immediate` returns a new
|
103
|
+
# `ImmediateExecutor` object.
|
104
|
+
# @!macro deref_options
|
105
|
+
def initialize(initial, opts = {})
|
106
|
+
@value = initial
|
107
|
+
@rescuers = []
|
108
|
+
@validator = Proc.new { |result| true }
|
109
|
+
self.observers = Collection::CopyOnWriteObserverSet.new
|
110
|
+
@serialized_execution = SerializedExecution.new
|
111
|
+
@io_executor = Executor.executor_from_options(opts) || Concurrent.global_io_executor
|
112
|
+
@fast_executor = Executor.executor_from_options(opts) || Concurrent.global_fast_executor
|
113
|
+
init_mutex
|
114
|
+
set_deref_options(opts)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Specifies a block fast to be performed when an update fast raises
|
118
|
+
# an exception. Rescue blocks will be checked in order they were added. The first
|
119
|
+
# block for which the raised exception "is-a" subclass of the given `clazz` will
|
120
|
+
# be called. If no `clazz` is given the block will match any caught exception.
|
121
|
+
# This behavior is intended to be identical to Ruby's `begin/rescue/end` behavior.
|
122
|
+
# Any number of rescue handlers can be added. If no rescue handlers are added then
|
123
|
+
# caught exceptions will be suppressed.
|
124
|
+
#
|
125
|
+
# @param [Exception] clazz the class of exception to catch
|
126
|
+
# @yield the block to be called when a matching exception is caught
|
127
|
+
# @yieldparam [StandardError] ex the caught exception
|
128
|
+
#
|
129
|
+
# @example
|
130
|
+
# score = Concurrent::Agent.new(0).
|
131
|
+
# rescue(NoMethodError){|ex| puts "Bam!" }.
|
132
|
+
# rescue(ArgumentError){|ex| puts "Pow!" }.
|
133
|
+
# rescue{|ex| puts "Boom!" }
|
134
|
+
#
|
135
|
+
# score << proc{|current| raise ArgumentError }
|
136
|
+
# sleep(0.1)
|
137
|
+
# #=> puts "Pow!"
|
138
|
+
def rescue(clazz = StandardError, &block)
|
139
|
+
unless block.nil?
|
140
|
+
mutex.synchronize do
|
141
|
+
@rescuers << Rescuer.new(clazz, block)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
alias_method :catch, :rescue
|
148
|
+
alias_method :on_error, :rescue
|
149
|
+
|
150
|
+
# A block fast to be performed after every update to validate if the new
|
151
|
+
# value is valid. If the new value is not valid then the current value is not
|
152
|
+
# updated. If no validator is provided then all updates are considered valid.
|
153
|
+
#
|
154
|
+
# @yield the block to be called after every update fast to determine if
|
155
|
+
# the result is valid
|
156
|
+
# @yieldparam [Object] value the result of the last update fast
|
157
|
+
# @yieldreturn [Boolean] true if the value is valid else false
|
158
|
+
def validate(&block)
|
159
|
+
|
160
|
+
unless block.nil?
|
161
|
+
begin
|
162
|
+
mutex.lock
|
163
|
+
@validator = block
|
164
|
+
ensure
|
165
|
+
mutex.unlock
|
166
|
+
end
|
167
|
+
end
|
168
|
+
self
|
169
|
+
end
|
170
|
+
|
171
|
+
alias_method :validates, :validate
|
172
|
+
alias_method :validate_with, :validate
|
173
|
+
alias_method :validates_with, :validate
|
174
|
+
|
175
|
+
# Update the current value with the result of the given block fast,
|
176
|
+
# block should not do blocking calls, use #post_off for blocking calls
|
177
|
+
#
|
178
|
+
# @yield the fast to be performed with the current value in order to calculate
|
179
|
+
# the new value
|
180
|
+
# @yieldparam [Object] value the current value
|
181
|
+
# @yieldreturn [Object] the new value
|
182
|
+
# @return [true, nil] nil when no block is given
|
183
|
+
def post(&block)
|
184
|
+
post_on(@fast_executor, &block)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Update the current value with the result of the given block fast,
|
188
|
+
# block can do blocking calls
|
189
|
+
#
|
190
|
+
# @param [Fixnum, nil] timeout [DEPRECATED] maximum number of seconds before an update is cancelled
|
191
|
+
#
|
192
|
+
# @yield the fast to be performed with the current value in order to calculate
|
193
|
+
# the new value
|
194
|
+
# @yieldparam [Object] value the current value
|
195
|
+
# @yieldreturn [Object] the new value
|
196
|
+
# @return [true, nil] nil when no block is given
|
197
|
+
def post_off(timeout = nil, &block)
|
198
|
+
task = if timeout
|
199
|
+
deprecated 'post_off with option timeout options is deprecated and will be removed'
|
200
|
+
lambda do |value|
|
201
|
+
future = Future.execute do
|
202
|
+
block.call(value)
|
203
|
+
end
|
204
|
+
if future.wait(timeout)
|
205
|
+
future.value!
|
206
|
+
else
|
207
|
+
raise Concurrent::TimeoutError
|
208
|
+
end
|
209
|
+
end
|
210
|
+
else
|
211
|
+
block
|
212
|
+
end
|
213
|
+
post_on(@io_executor, &task)
|
214
|
+
end
|
215
|
+
|
216
|
+
# Update the current value with the result of the given block fast,
|
217
|
+
# block should not do blocking calls, use #post_off for blocking calls
|
218
|
+
#
|
219
|
+
# @yield the fast to be performed with the current value in order to calculate
|
220
|
+
# the new value
|
221
|
+
# @yieldparam [Object] value the current value
|
222
|
+
# @yieldreturn [Object] the new value
|
223
|
+
def <<(block)
|
224
|
+
post(&block)
|
225
|
+
self
|
226
|
+
end
|
227
|
+
|
228
|
+
# Waits/blocks until all the updates sent before this call are done.
|
229
|
+
#
|
230
|
+
# @param [Numeric] timeout the maximum time in second to wait.
|
231
|
+
# @return [Boolean] false on timeout, true otherwise
|
232
|
+
def await(timeout = nil)
|
233
|
+
done = Event.new
|
234
|
+
post { |val| done.set; val }
|
235
|
+
done.wait timeout
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
def post_on(executor, &block)
|
241
|
+
return nil if block.nil?
|
242
|
+
@serialized_execution.post(executor) { work(&block) }
|
243
|
+
true
|
244
|
+
end
|
245
|
+
|
246
|
+
# @!visibility private
|
247
|
+
Rescuer = Struct.new(:clazz, :block) # :nodoc:
|
248
|
+
|
249
|
+
# @!visibility private
|
250
|
+
def try_rescue(ex) # :nodoc:
|
251
|
+
rescuer = mutex.synchronize do
|
252
|
+
@rescuers.find { |r| ex.is_a?(r.clazz) }
|
253
|
+
end
|
254
|
+
rescuer.block.call(ex) if rescuer
|
255
|
+
rescue Exception => ex
|
256
|
+
# suppress
|
257
|
+
log DEBUG, ex
|
258
|
+
end
|
259
|
+
|
260
|
+
# @!visibility private
|
261
|
+
def work(&handler) # :nodoc:
|
262
|
+
validator, value = mutex.synchronize { [@validator, @value] }
|
263
|
+
|
264
|
+
begin
|
265
|
+
result = handler.call(value)
|
266
|
+
valid = validator.call(result)
|
267
|
+
rescue Exception => ex
|
268
|
+
exception = ex
|
269
|
+
end
|
270
|
+
|
271
|
+
begin
|
272
|
+
mutex.lock
|
273
|
+
should_notify = if !exception && valid
|
274
|
+
@value = result
|
275
|
+
true
|
276
|
+
end
|
277
|
+
ensure
|
278
|
+
mutex.unlock
|
279
|
+
end
|
280
|
+
|
281
|
+
if should_notify
|
282
|
+
time = Time.now
|
283
|
+
observers.notify_observers { [time, self.value] }
|
284
|
+
end
|
285
|
+
|
286
|
+
try_rescue(exception)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
require 'concurrent/channel/blocking_ring_buffer'
|
2
|
+
require 'concurrent/channel/buffered_channel'
|
3
|
+
require 'concurrent/channel/channel'
|
4
|
+
require 'concurrent/channel/ring_buffer'
|
5
|
+
require 'concurrent/channel/unbuffered_channel'
|
6
|
+
require 'concurrent/channel/waitable_list'
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'concurrent/synchronization'
|
2
|
+
|
3
|
+
module Concurrent
|
4
|
+
module Channel
|
5
|
+
|
6
|
+
# @api Channel
|
7
|
+
# @!macro edge_warning
|
8
|
+
class BlockingRingBuffer < Synchronization::Object
|
9
|
+
|
10
|
+
def initialize(capacity)
|
11
|
+
super()
|
12
|
+
synchronize { ns_initialize capacity}
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Integer] the capacity of the buffer
|
16
|
+
def capacity
|
17
|
+
synchronize { @buffer.capacity }
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Integer] the number of elements currently in the buffer
|
21
|
+
def count
|
22
|
+
synchronize { @buffer.count }
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Boolean] true if buffer is empty, false otherwise
|
26
|
+
def empty?
|
27
|
+
synchronize { @buffer.empty? }
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Boolean] true if buffer is full, false otherwise
|
31
|
+
def full?
|
32
|
+
synchronize { @buffer.full? }
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [Object] value the value to be inserted
|
36
|
+
# @return [Boolean] true if value has been inserted, false otherwise
|
37
|
+
def put(value)
|
38
|
+
synchronize do
|
39
|
+
wait_while_full
|
40
|
+
@buffer.offer(value)
|
41
|
+
ns_signal
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Object] the first available value and removes it from the buffer.
|
47
|
+
# If buffer is empty it blocks until an element is available
|
48
|
+
def take
|
49
|
+
synchronize do
|
50
|
+
wait_while_empty
|
51
|
+
result = @buffer.poll
|
52
|
+
ns_signal
|
53
|
+
result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Object] the first available value and without removing it from
|
58
|
+
# the buffer. If buffer is empty returns nil
|
59
|
+
def peek
|
60
|
+
synchronize { @buffer.peek }
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def ns_initialize(capacity)
|
66
|
+
@buffer = RingBuffer.new(capacity)
|
67
|
+
@first = @last = 0
|
68
|
+
@count = 0
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def wait_while_full
|
74
|
+
ns_wait_until { !@buffer.full? }
|
75
|
+
end
|
76
|
+
|
77
|
+
def wait_while_empty
|
78
|
+
ns_wait_until { !@buffer.empty? }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|