farce 0.4.1 → 0.5.0
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/CHANGELOG.md +80 -2
- data/README.md +3 -1
- data/docs/benchmarks.md +37 -21
- data/docs/compatibility.md +13 -0
- data/docs/data-structures.md +37 -0
- data/docs/integrations.md +1 -0
- data/docs/introduction.md +19 -0
- data/docs/modes.md +2 -0
- data/docs/scheduling.md +35 -3
- data/docs/scopes.md +3 -1
- data/docs/shims.md +53 -0
- data/docs/utilities.md +31 -0
- data/docs/variants.md +9 -0
- data/ext/farce/farce.c +9 -0
- data/ext/farce/map.c +14 -3
- data/ext/farce/priority_queue.c +46 -1
- data/ext/farce/queue.c +141 -7
- data/ext/farce/vector.c +3 -3
- data/ext/farce/weak_map.c +12 -3
- data/ext/java/org/farce/PriorityQueue.java +18 -2
- data/lib/farce/abstract/concurrent_map.rb +122 -0
- data/lib/farce/abstract/counter.rb +35 -1
- data/lib/farce/abstract/executor.rb +30 -7
- data/lib/farce/abstract/flag.rb +12 -0
- data/lib/farce/abstract/pool.rb +158 -47
- data/lib/farce/abstract/priority_queue.rb +1 -1
- data/lib/farce/abstract/publisher.rb +189 -0
- data/lib/farce/abstract/queue.rb +29 -3
- data/lib/farce/abstract/scheduler.rb +41 -14
- data/lib/farce/abstract/timer_queue.rb +3 -3
- data/lib/farce/abstract/vector.rb +3 -1
- data/lib/farce/atom.rb +9 -2
- data/lib/farce/clock.rb +12 -0
- data/lib/farce/concurrent/count_down_latch.rb +37 -0
- data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
- data/lib/farce/concurrent/event.rb +60 -0
- data/lib/farce/concurrent/fiber_local_var.rb +31 -0
- data/lib/farce/concurrent/local_var.rb +65 -0
- data/lib/farce/concurrent/thread_local_var.rb +28 -0
- data/lib/farce/concurrent/tuple.rb +74 -0
- data/lib/farce/concurrent.rb +50 -0
- data/lib/farce/config.rb +112 -2
- data/lib/farce/counter.rb +0 -16
- data/lib/farce/engine/jruby/map.rb +8 -6
- data/lib/farce/engine/jruby/signal.rb +2 -2
- data/lib/farce/engine/jvm/farce.jar +0 -0
- data/lib/farce/engine/jvm/priority_queue.rb +14 -4
- data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
- data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
- data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
- data/lib/farce/engine/shared/exchanger.rb +2 -2
- data/lib/farce/engine/shared/queue.rb +43 -5
- data/lib/farce/engine/shared/ractor_selector.rb +1 -1
- data/lib/farce/engine/shared/strict_map.rb +5 -0
- data/lib/farce/engine/shared/unshared_atom.rb +1 -1
- data/lib/farce/engine/shared/unshared_vector.rb +8 -7
- data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
- data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
- data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
- data/lib/farce/engine/truffleruby/map.rb +8 -6
- data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
- data/lib/farce/engine/truffleruby/signal.rb +1 -1
- data/lib/farce/integrations/concurrent.rb +16 -3
- data/lib/farce/internal/blocking_priority_queue.rb +4 -9
- data/lib/farce/internal/flag_waiting.rb +1 -1
- data/lib/farce/internal/lease_initialization.rb +1 -1
- data/lib/farce/internal/lease_pool_state.rb +1 -1
- data/lib/farce/internal/lease_state.rb +2 -2
- data/lib/farce/internal/local_executor_methods.rb +3 -3
- data/lib/farce/internal/map_value_modes.rb +9 -2
- data/lib/farce/internal/pool_settings.rb +9 -0
- data/lib/farce/internal/pool_worker.rb +34 -21
- data/lib/farce/internal/reservation_waiting.rb +1 -1
- data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
- data/lib/farce/internal/weak_map_value_modes.rb +5 -0
- data/lib/farce/internal.rb +8 -8
- data/lib/farce/lazy.rb +3 -0
- data/lib/farce/lazy_ref.rb +3 -0
- data/lib/farce/local/lease.rb +2 -9
- data/lib/farce/local/lease_map.rb +2 -9
- data/lib/farce/local/pool.rb +38 -5
- data/lib/farce/local/publisher.rb +30 -0
- data/lib/farce/local/scheduler.rb +18 -5
- data/lib/farce/local/vector.rb +3 -2
- data/lib/farce/memoize.rb +314 -0
- data/lib/farce/pool.rb +5 -0
- data/lib/farce/priority_queue.rb +1 -1
- data/lib/farce/publisher.rb +57 -0
- data/lib/farce/signal.rb +1 -1
- data/lib/farce/strict/publisher.rb +31 -0
- data/lib/farce/strict/vector.rb +3 -2
- data/lib/farce/subscription/definitions.rb +27 -0
- data/lib/farce/subscription.rb +121 -0
- data/lib/farce/transaction/map.rb +35 -1
- data/lib/farce/unshared/pool.rb +1 -1
- data/lib/farce/unshared/publisher.rb +25 -0
- data/lib/farce/unshared/scheduler.rb +3 -3
- data/lib/farce/unshared/vector.rb +3 -2
- data/lib/farce/vector.rb +12 -9
- data/lib/farce/version.rb +1 -1
- data/lib/farce/walker/definitions.rb +13 -2
- data/lib/farce/weak_atom.rb +5 -2
- data/lib/farce.rb +3 -1
- metadata +18 -1
|
@@ -9,17 +9,56 @@ module Farce
|
|
|
9
9
|
include Freeze::Unfreezable
|
|
10
10
|
|
|
11
11
|
NIL_VALUE = Object.new.freeze
|
|
12
|
-
attr_reader :capacity
|
|
13
12
|
|
|
14
13
|
def initialize(capacity: 1024, track_age: false)
|
|
15
14
|
@queue = capacity ? Thread::SizedQueue.new(capacity) : Thread::Queue.new
|
|
16
|
-
@capacity = capacity
|
|
17
15
|
@signal = Signal.new
|
|
18
16
|
@tracking = track_age ? { lock: Mutex.new, timestamps: [], generation: 0 } : nil
|
|
19
|
-
@lifecycle = { lock: Mutex.new, hard_closed: false }
|
|
17
|
+
@lifecycle = { lock: Mutex.new, hard_closed: false, token: nil, generation: -1 }
|
|
20
18
|
Freeze.publish(self)
|
|
21
19
|
end
|
|
22
20
|
|
|
21
|
+
def capacity = @queue.is_a?(Thread::SizedQueue) ? @queue.max : nil
|
|
22
|
+
|
|
23
|
+
def capacity=(value)
|
|
24
|
+
value = Integer(value) unless value.nil?
|
|
25
|
+
raise ArgumentError, "capacity must be positive or nil" if value && !value.positive?
|
|
26
|
+
if value.nil? != capacity.nil?
|
|
27
|
+
raise ArgumentError, "cannot switch between bounded and unbounded queues on this Ruby engine"
|
|
28
|
+
end
|
|
29
|
+
@queue.max = value if value
|
|
30
|
+
broadcast
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def interrupt(token, generation)
|
|
35
|
+
@lifecycle[:lock].synchronize do
|
|
36
|
+
return self if generation <= @lifecycle[:generation]
|
|
37
|
+
@lifecycle[:token] = token
|
|
38
|
+
@lifecycle[:generation] = generation
|
|
39
|
+
end
|
|
40
|
+
broadcast
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def pop_interruptible(token, timeout)
|
|
45
|
+
timeout_at = timeout_at(timeout)
|
|
46
|
+
loop do
|
|
47
|
+
return false unless @lifecycle[:lock].synchronize { @lifecycle[:token].equal?(token) }
|
|
48
|
+
begin
|
|
49
|
+
result = tracked_pop
|
|
50
|
+
raise ThreadError if nil.equal?(result) && @queue.closed?
|
|
51
|
+
broadcast
|
|
52
|
+
return NIL_VALUE.equal?(result) ? nil : result
|
|
53
|
+
rescue ThreadError
|
|
54
|
+
ready = wait(remaining_timeout(timeout_at)) do
|
|
55
|
+
!@lifecycle[:lock].synchronize { @lifecycle[:token].equal?(token) } || size.positive?
|
|
56
|
+
end
|
|
57
|
+
return block_given? ? yield : nil unless ready
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
23
62
|
def pop(timeout: nil)
|
|
24
63
|
timeout_at = timeout_at(timeout)
|
|
25
64
|
# Observing an empty queue is sufficient for a nonblocking miss. Avoid
|
|
@@ -223,8 +262,7 @@ module Farce
|
|
|
223
262
|
def remaining_timeout(timeout_at)
|
|
224
263
|
return unless timeout_at
|
|
225
264
|
return 0 if timeout_at.zero?
|
|
226
|
-
|
|
227
|
-
timeout.positive? ? timeout : 0
|
|
265
|
+
Clock.remaining(timeout_at)
|
|
228
266
|
end
|
|
229
267
|
|
|
230
268
|
def broadcast = @signal.broadcast
|
|
@@ -61,7 +61,7 @@ module Farce
|
|
|
61
61
|
result = port.select_receive
|
|
62
62
|
return result if result
|
|
63
63
|
end
|
|
64
|
-
remaining =
|
|
64
|
+
remaining = Clock.remaining(deadline)
|
|
65
65
|
return if remaining && !remaining.positive?
|
|
66
66
|
return unless ReservationWaiting.wait(wakeup, generation, deadline)
|
|
67
67
|
end
|
|
@@ -67,6 +67,11 @@ module Farce
|
|
|
67
67
|
super(check_key(key), **) { |value| check_value(yield(value)) }
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
+
def modify(key, **)
|
|
71
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
72
|
+
super(check_key(key), **) { |present, value| check_value(yield(present, value)) }
|
|
73
|
+
end
|
|
74
|
+
|
|
70
75
|
def upsert(key, initial, **)
|
|
71
76
|
raise LocalJumpError, "no block given" unless block_given?
|
|
72
77
|
super(check_key(key), check_value(initial), **) { |value| check_value(yield(value)) }
|
|
@@ -129,7 +129,7 @@ module Farce
|
|
|
129
129
|
|
|
130
130
|
loop do
|
|
131
131
|
current, version = @mutex.synchronize { [@value, @version] }
|
|
132
|
-
return current
|
|
132
|
+
return current if Internal.value_changed?(current, expected, compare_by_identity?)
|
|
133
133
|
|
|
134
134
|
result = @mutex.synchronize do
|
|
135
135
|
next unless @version == version
|
|
@@ -274,12 +274,12 @@ module Farce
|
|
|
274
274
|
finish_update if completed
|
|
275
275
|
end
|
|
276
276
|
|
|
277
|
-
def wait_until_changed(index, expected, timeout: nil)
|
|
278
|
-
wait_for_value(convert_index(index), expected, timeout_deadline(timeout), non_nil: false)
|
|
277
|
+
def wait_until_changed(index, expected, timeout: nil, &fallback)
|
|
278
|
+
wait_for_value(convert_index(index), expected, timeout_deadline(timeout), fallback, non_nil: false)
|
|
279
279
|
end
|
|
280
280
|
|
|
281
281
|
def wait_until_non_nil(index, timeout: nil)
|
|
282
|
-
wait_for_value(convert_index(index), nil, timeout_deadline(timeout), non_nil: true)
|
|
282
|
+
wait_for_value(convert_index(index), nil, timeout_deadline(timeout), nil, non_nil: true)
|
|
283
283
|
end
|
|
284
284
|
|
|
285
285
|
def compare_by_identity? = @compare_by_identity
|
|
@@ -341,20 +341,21 @@ module Farce
|
|
|
341
341
|
raise FrozenError.new("can't modify frozen #{self.class}", receiver: self)
|
|
342
342
|
end
|
|
343
343
|
|
|
344
|
-
def wait_for_value(index, expected, deadline, non_nil:)
|
|
344
|
+
def wait_for_value(index, expected, deadline, fallback, non_nil:)
|
|
345
345
|
while true
|
|
346
346
|
generation = @signal.generation
|
|
347
347
|
current = @mutex.synchronize { @values[index] }
|
|
348
|
-
ready = non_nil ? !current.nil? :
|
|
348
|
+
ready = non_nil ? !current.nil? :
|
|
349
|
+
Internal.value_changed?(current, expected, compare_by_identity?)
|
|
349
350
|
return current if ready
|
|
350
|
-
return unless wait_for_signal(generation, deadline)
|
|
351
|
+
return fallback&.call unless wait_for_signal(generation, deadline)
|
|
351
352
|
end
|
|
352
353
|
end
|
|
353
354
|
|
|
354
355
|
def wait_for_signal(generation, deadline)
|
|
355
356
|
return @signal.wait(generation) unless deadline
|
|
356
357
|
|
|
357
|
-
remaining =
|
|
358
|
+
remaining = Clock.remaining(deadline)
|
|
358
359
|
return false unless remaining.positive?
|
|
359
360
|
!@signal.wait(generation, timeout: remaining).nil?
|
|
360
361
|
end
|
|
@@ -145,10 +145,11 @@ module Farce
|
|
|
145
145
|
# Yield presence and value under the entry reservation.
|
|
146
146
|
# MAP_KEEP and MAP_DELETE are control results. Other results replace the value.
|
|
147
147
|
# Return whether a change committed.
|
|
148
|
-
def modify(key
|
|
148
|
+
def modify(key, timeout: nil)
|
|
149
149
|
raise LocalJumpError, "no block given" unless block_given?
|
|
150
150
|
check_frozen!
|
|
151
|
-
|
|
151
|
+
deadline = timeout_deadline(timeout)
|
|
152
|
+
completed, result = with_entry(key, deadline, create: true, mutating: true) do |present, current, entry, index|
|
|
152
153
|
value = yield(present, current)
|
|
153
154
|
check_frozen!
|
|
154
155
|
if MAP_KEEP.equal?(value)
|
|
@@ -161,7 +162,7 @@ module Farce
|
|
|
161
162
|
entry.store(value)
|
|
162
163
|
end
|
|
163
164
|
end
|
|
164
|
-
!!result
|
|
165
|
+
completed && !!result
|
|
165
166
|
end
|
|
166
167
|
|
|
167
168
|
def wait_until_changed(key, expected, timeout: nil, &fallback)
|
|
@@ -352,7 +353,7 @@ module Farce
|
|
|
352
353
|
return fallback&.call if timed_out == :timed_out
|
|
353
354
|
|
|
354
355
|
unless entry
|
|
355
|
-
return nil
|
|
356
|
+
return nil if Internal.value_changed?(nil, expected, compare_values_by_identity?)
|
|
356
357
|
return fallback&.call unless signal_changed?(@index.change_signal, observed, deadline)
|
|
357
358
|
next
|
|
358
359
|
end
|
|
@@ -373,13 +374,14 @@ module Farce
|
|
|
373
374
|
# lookup object. Otherwise GC could orphan the entry's change signal.
|
|
374
375
|
key = stored_key
|
|
375
376
|
current = nil unless present
|
|
376
|
-
return current if non_nil ? !current.nil? :
|
|
377
|
+
return current if non_nil ? !current.nil? :
|
|
378
|
+
Internal.value_changed?(current, expected, compare_values_by_identity?)
|
|
377
379
|
return fallback&.call unless entry.wait_for_change?(changed, deadline)
|
|
378
380
|
end
|
|
379
381
|
end
|
|
380
382
|
|
|
381
383
|
def signal_changed?(signal, observed, deadline)
|
|
382
|
-
timeout =
|
|
384
|
+
timeout = Clock.remaining(deadline)
|
|
383
385
|
return false if timeout && !timeout.positive?
|
|
384
386
|
|
|
385
387
|
timed_out = Object.new
|
|
@@ -146,7 +146,7 @@ module Farce
|
|
|
146
146
|
def with_value(timeout)
|
|
147
147
|
deadline = timeout_deadline(timeout)
|
|
148
148
|
result = TIMED_OUT
|
|
149
|
-
@lock.update(timeout:
|
|
149
|
+
@lock.update(timeout: Clock.remaining(deadline)) do
|
|
150
150
|
result = yield
|
|
151
151
|
nil
|
|
152
152
|
end
|
|
@@ -158,11 +158,12 @@ module Farce
|
|
|
158
158
|
loop do
|
|
159
159
|
generation = @changes.value
|
|
160
160
|
current = read_value
|
|
161
|
-
return current if non_nil ? !current.nil? :
|
|
161
|
+
return current if non_nil ? !current.nil? :
|
|
162
|
+
Internal.value_changed?(current, expected, compare_by_identity?)
|
|
162
163
|
# Detect recursive waits without waiting for another owner's update.
|
|
163
164
|
@lock.get(timeout: 0)
|
|
164
|
-
return fallback&.call if
|
|
165
|
-
@changes.wait_until_changed(generation, timeout:
|
|
165
|
+
return fallback&.call if Clock.remaining(deadline)&.zero?
|
|
166
|
+
@changes.wait_until_changed(generation, timeout: Clock.remaining(deadline))
|
|
166
167
|
end
|
|
167
168
|
end
|
|
168
169
|
|
|
@@ -177,12 +178,6 @@ module Farce
|
|
|
177
178
|
raise ArgumentError, "timeout must be finite and non-negative" unless timeout.finite? && !timeout.negative?
|
|
178
179
|
Clock.now + timeout
|
|
179
180
|
end
|
|
180
|
-
|
|
181
|
-
def remaining_timeout(deadline)
|
|
182
|
-
return unless deadline
|
|
183
|
-
remaining = deadline - Clock.now
|
|
184
|
-
remaining.negative? ? 0 : remaining
|
|
185
|
-
end
|
|
186
181
|
end
|
|
187
182
|
private_constant :WeakAtomBase
|
|
188
183
|
end
|
|
@@ -28,7 +28,8 @@ module Farce
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def eql?(other)
|
|
31
|
-
result = other.is_a?(Key) &&
|
|
31
|
+
result = other.is_a?(Key) &&
|
|
32
|
+
(BASIC_OBJECT_EQUAL_METHOD.bind_call(value, other.value) || value.eql?(other.value))
|
|
32
33
|
owner = mutation_owner || (other.mutation_owner if other.is_a?(Key))
|
|
33
34
|
Internal::Freeze.check(owner) if owner
|
|
34
35
|
result
|
|
@@ -214,11 +215,11 @@ module Farce
|
|
|
214
215
|
# Yield presence and value under the key reservation.
|
|
215
216
|
# MAP_KEEP and MAP_DELETE are control results. Other results replace the value.
|
|
216
217
|
# Return whether a change committed.
|
|
217
|
-
def modify(key
|
|
218
|
+
def modify(key, timeout: nil)
|
|
218
219
|
raise LocalJumpError, "no block given" unless block_given?
|
|
219
220
|
check_frozen!
|
|
220
221
|
wrapped = wrap_key(key)
|
|
221
|
-
|
|
222
|
+
completed, result = with_mutating_key_operation(wrapped, timeout_deadline(timeout)) do |reservation|
|
|
222
223
|
present, current = native_operation { [concurrent_key?(wrapped), concurrent_get(wrapped)] }
|
|
223
224
|
value = yield(present, current)
|
|
224
225
|
next false if MAP_KEEP.equal?(value)
|
|
@@ -231,7 +232,7 @@ module Farce
|
|
|
231
232
|
changed!
|
|
232
233
|
end
|
|
233
234
|
end
|
|
234
|
-
!!result
|
|
235
|
+
completed && !!result
|
|
235
236
|
end
|
|
236
237
|
|
|
237
238
|
def upsert(key, initial_value, timeout: nil)
|
|
@@ -557,7 +558,8 @@ module Farce
|
|
|
557
558
|
native_operation { concurrent_get(wrapped) }
|
|
558
559
|
end
|
|
559
560
|
return fallback&.call unless completed
|
|
560
|
-
ready = non_nil ? !current.nil? :
|
|
561
|
+
ready = non_nil ? !current.nil? :
|
|
562
|
+
Internal.value_changed?(current, expected, compare_values_by_identity?)
|
|
561
563
|
return current if ready
|
|
562
564
|
return fallback&.call unless wait_for_signal(@change_signal, generation, deadline)
|
|
563
565
|
end
|
|
@@ -566,7 +568,7 @@ module Farce
|
|
|
566
568
|
def wait_for_signal(signal, generation, deadline)
|
|
567
569
|
return signal.wait(generation) unless deadline
|
|
568
570
|
|
|
569
|
-
remaining =
|
|
571
|
+
remaining = Clock.remaining(deadline)
|
|
570
572
|
return false unless remaining.positive?
|
|
571
573
|
!signal.wait(generation, timeout: remaining).nil?
|
|
572
574
|
end
|
|
@@ -23,8 +23,8 @@ module Farce
|
|
|
23
23
|
private_constant :Bucket, :TrackedItem
|
|
24
24
|
|
|
25
25
|
class State
|
|
26
|
-
attr_accessor :size, :sealed, :closed, :generation, :operation_owner
|
|
27
|
-
attr_reader :buckets, :
|
|
26
|
+
attr_accessor :size, :sealed, :closed, :generation, :operation_owner, :capacity
|
|
27
|
+
attr_reader :buckets, :signal, :lock, :tracked_items, :track_age
|
|
28
28
|
|
|
29
29
|
def initialize(capacity:, signal:, lock:, track_age:)
|
|
30
30
|
@buckets = []
|
|
@@ -139,7 +139,18 @@ module Farce
|
|
|
139
139
|
with_queue_operation(state) { state.size.zero? } # rubocop:disable Style/ZeroLengthPredicate
|
|
140
140
|
end
|
|
141
141
|
|
|
142
|
-
def capacity
|
|
142
|
+
def capacity
|
|
143
|
+
state = initialized_state
|
|
144
|
+
with_queue_operation(state) { state.capacity }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def capacity=(value)
|
|
148
|
+
state = initialized_state
|
|
149
|
+
value = normalize_capacity(value)
|
|
150
|
+
with_queue_operation(state) do
|
|
151
|
+
notify_and_commit(state) { state.capacity = value }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
143
154
|
|
|
144
155
|
def clear
|
|
145
156
|
state = initialized_state
|
|
@@ -262,7 +273,10 @@ module Farce
|
|
|
262
273
|
|
|
263
274
|
def normalize_capacity(capacity)
|
|
264
275
|
return nil if capacity.nil?
|
|
265
|
-
|
|
276
|
+
unless primitive_is_a?(capacity, Integer)
|
|
277
|
+
raise TypeError, "capacity must be an Integer or nil" unless capacity.respond_to?(:to_int)
|
|
278
|
+
capacity = capacity.to_int
|
|
279
|
+
end
|
|
266
280
|
raise TypeError, "capacity must be an Integer or nil" unless primitive_is_a?(capacity, Integer)
|
|
267
281
|
raise ArgumentError, "capacity must be positive or nil" unless capacity.positive?
|
|
268
282
|
capacity
|
|
@@ -43,7 +43,7 @@ module Farce
|
|
|
43
43
|
current = generation
|
|
44
44
|
break current unless current == observed
|
|
45
45
|
|
|
46
|
-
remaining =
|
|
46
|
+
remaining = Clock.remaining(deadline)
|
|
47
47
|
break TIMED_OUT if remaining && !remaining.positive?
|
|
48
48
|
@num_waiting.set(@num_waiting.get + 1) unless waiting
|
|
49
49
|
waiting = true
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
# warn_indent: true
|
|
4
4
|
|
|
5
5
|
# @!group concurrent-ruby Integration
|
|
6
|
-
require "farce"
|
|
7
|
-
require "concurrent/map"
|
|
8
|
-
require "concurrent/tvar"
|
|
6
|
+
require "farce" unless defined?(Farce::Abstract)
|
|
7
|
+
require "concurrent/map" unless defined?(Concurrent::Map)
|
|
8
|
+
require "concurrent/tvar" unless defined?(Concurrent::TVar)
|
|
9
|
+
require "concurrent/atomic/atomic_reference" unless defined?(Concurrent::AtomicReference)
|
|
10
|
+
require "concurrent/atomic/atomic_fixnum" unless defined?(Concurrent::AtomicFixnum)
|
|
11
|
+
require "concurrent/atomic/atomic_boolean" unless defined?(Concurrent::AtomicBoolean)
|
|
12
|
+
require "concurrent/atomic/event" unless defined?(Concurrent::Event)
|
|
13
|
+
require "concurrent/atomic/count_down_latch" unless defined?(Concurrent::CountDownLatch)
|
|
9
14
|
|
|
10
15
|
module Farce
|
|
11
16
|
module Integrations
|
|
@@ -109,4 +114,12 @@ module Farce
|
|
|
109
114
|
Internal::Converter.define(::Concurrent::Map, :Map) do |instance, value|
|
|
110
115
|
value.each { instance[_1] = convert(_2) }
|
|
111
116
|
end
|
|
117
|
+
|
|
118
|
+
Farce.on_main do
|
|
119
|
+
Subscription.define(::Concurrent::AtomicReference) { subscriber.value = it }
|
|
120
|
+
Subscription.define(::Concurrent::AtomicFixnum) { |_| subscriber.increment }
|
|
121
|
+
Subscription.define(::Concurrent::AtomicBoolean) { |_| subscriber.make_true }
|
|
122
|
+
Subscription.define(::Concurrent::Event) { |_| subscriber.set }
|
|
123
|
+
Subscription.define(::Concurrent::CountDownLatch) { |_| subscriber.count_down }
|
|
124
|
+
end
|
|
112
125
|
end
|
|
@@ -32,7 +32,8 @@ module Farce
|
|
|
32
32
|
|
|
33
33
|
wait(timeout) do
|
|
34
34
|
raise_unwritable if sealed?
|
|
35
|
-
|
|
35
|
+
limit = capacity
|
|
36
|
+
!limit || size < limit
|
|
36
37
|
end
|
|
37
38
|
end
|
|
38
39
|
|
|
@@ -76,7 +77,7 @@ module Farce
|
|
|
76
77
|
while true
|
|
77
78
|
return true if internal_queue.push(priority, value)
|
|
78
79
|
raise ThreadError, "queue full" if non_block
|
|
79
|
-
return false unless wait_push(timeout:
|
|
80
|
+
return false unless wait_push(timeout: Clock.remaining(deadline))
|
|
80
81
|
end
|
|
81
82
|
end
|
|
82
83
|
|
|
@@ -98,7 +99,7 @@ module Farce
|
|
|
98
99
|
generation = internal_signal.generation
|
|
99
100
|
raise ::Farce::ClosedQueueError, "queue is closed" if closed?
|
|
100
101
|
return true if yield
|
|
101
|
-
return false unless internal_signal.wait(generation, timeout:
|
|
102
|
+
return false unless internal_signal.wait(generation, timeout: Clock.remaining(deadline))
|
|
102
103
|
end
|
|
103
104
|
end
|
|
104
105
|
|
|
@@ -111,12 +112,6 @@ module Farce
|
|
|
111
112
|
def timeout_at(timeout)
|
|
112
113
|
Clock.timeout(normalize_timeout(timeout)) unless timeout.nil?
|
|
113
114
|
end
|
|
114
|
-
|
|
115
|
-
def remaining_timeout(timeout_at)
|
|
116
|
-
return unless timeout_at
|
|
117
|
-
timeout = timeout_at - Clock.now
|
|
118
|
-
timeout.positive? ? timeout : 0
|
|
119
|
-
end
|
|
120
115
|
end
|
|
121
116
|
end
|
|
122
117
|
end
|
|
@@ -21,7 +21,7 @@ module Farce
|
|
|
21
21
|
observed = signal.generation
|
|
22
22
|
current = value
|
|
23
23
|
return current unless expected == current
|
|
24
|
-
break unless signal.wait(observed, timeout:
|
|
24
|
+
break unless signal.wait(observed, timeout: Clock.remaining(deadline))
|
|
25
25
|
end
|
|
26
26
|
yield if block_given?
|
|
27
27
|
end
|
|
@@ -31,7 +31,7 @@ module Farce
|
|
|
31
31
|
reject_recursive_initialization!
|
|
32
32
|
return unless wait
|
|
33
33
|
|
|
34
|
-
remaining =
|
|
34
|
+
remaining = Clock.remaining(deadline)
|
|
35
35
|
return if remaining && !remaining.positive?
|
|
36
36
|
reject_unscheduled_fiber_wait!
|
|
37
37
|
@signal.wait(observed, timeout: wait_interval(remaining))
|
|
@@ -108,7 +108,7 @@ module Farce
|
|
|
108
108
|
return create_resource(factory) if reserve_creation
|
|
109
109
|
return unless wait
|
|
110
110
|
|
|
111
|
-
remaining =
|
|
111
|
+
remaining = Clock.remaining(deadline)
|
|
112
112
|
raise TimeoutError, "lease pool checkout timed out" if remaining && !remaining.positive?
|
|
113
113
|
interval = LeaseWaiting.wait_interval(remaining)
|
|
114
114
|
changed = Thread.handle_interrupt(Exception => :immediate) do
|
|
@@ -156,7 +156,7 @@ module Farce
|
|
|
156
156
|
resource = try_acquire(kind)
|
|
157
157
|
return resource if resource
|
|
158
158
|
|
|
159
|
-
remaining =
|
|
159
|
+
remaining = Clock.remaining(deadline)
|
|
160
160
|
raise TimeoutError, "lease checkout timed out" if remaining && !remaining.positive?
|
|
161
161
|
reject_unscheduled_fiber_wait!
|
|
162
162
|
interval = wait_interval(remaining)
|
|
@@ -180,7 +180,7 @@ module Farce
|
|
|
180
180
|
outcome = try_acquire_block(block)
|
|
181
181
|
return outcome.first if outcome
|
|
182
182
|
|
|
183
|
-
remaining =
|
|
183
|
+
remaining = Clock.remaining(deadline)
|
|
184
184
|
raise TimeoutError, "lease checkout timed out" if remaining && !remaining.positive?
|
|
185
185
|
reject_unscheduled_fiber_wait!
|
|
186
186
|
interval = wait_interval(remaining)
|
|
@@ -16,10 +16,10 @@ module Farce
|
|
|
16
16
|
# Run a task directly, preserving its block, arguments, and calling fiber.
|
|
17
17
|
# `mode` and `auto_local` follow the same validation as {#schedule}.
|
|
18
18
|
# @param (see Abstract::Executor#execute)
|
|
19
|
-
# @return [self]
|
|
20
|
-
def execute(*, mode: :smart, auto_local: true, &)
|
|
19
|
+
# @return [self, nil]
|
|
20
|
+
def execute(*, mode: :smart, auto_local: true, timeout: nil, &)
|
|
21
21
|
validate_local_mode(mode, auto_local)
|
|
22
|
-
super(*, mode: :local, auto_local: true, &)
|
|
22
|
+
super(*, mode: :local, auto_local: true, timeout:, &)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
private
|
|
@@ -143,18 +143,25 @@ module Farce
|
|
|
143
143
|
end
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
+
# (see Abstract::ConcurrentMap#wait_until)
|
|
147
|
+
def wait_until(key, timeout: nil)
|
|
148
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
149
|
+
result = Internal.wait_until(@map, key, timeout:) { |current| yield(unwrap_value(current)) }
|
|
150
|
+
unwrap_value(result)
|
|
151
|
+
end
|
|
152
|
+
|
|
146
153
|
def wait_until_changed(key, expected, timeout: nil, &)
|
|
147
154
|
prepared_key = @map.normalize_external_key(key)
|
|
148
155
|
expected = wrap_comparison(expected)
|
|
149
156
|
deadline = Internal.timeout_deadline(timeout)
|
|
150
157
|
loop do
|
|
151
|
-
current = @map.get_prepared(prepared_key, timeout:
|
|
158
|
+
current = @map.get_prepared(prepared_key, timeout: Clock.remaining(deadline)) { TIMED_OUT }
|
|
152
159
|
return unwrap_result(current, &) if TIMED_OUT.equal?(current)
|
|
153
160
|
return unwrap_value(current) unless values_equal?(nil.equal?(current) ? NIL_VALUE : current, expected)
|
|
154
161
|
result = @map.wait_until_changed_prepared(
|
|
155
162
|
prepared_key,
|
|
156
163
|
current,
|
|
157
|
-
timeout:
|
|
164
|
+
timeout: Clock.remaining(deadline),
|
|
158
165
|
) { TIMED_OUT }
|
|
159
166
|
return unwrap_result(result, &) if TIMED_OUT.equal?(result)
|
|
160
167
|
end
|
|
@@ -9,46 +9,59 @@ module Farce
|
|
|
9
9
|
include Shareable::Unfreezable
|
|
10
10
|
|
|
11
11
|
def initialize(pool)
|
|
12
|
-
@pool
|
|
13
|
-
@inflight
|
|
14
|
-
@pressure = pool.min_size < pool.max_size
|
|
12
|
+
@pool = pool
|
|
13
|
+
@inflight = Counter.new
|
|
15
14
|
@registered = Atom.new(true)
|
|
16
15
|
super()
|
|
17
16
|
end
|
|
18
17
|
|
|
19
|
-
def
|
|
20
|
-
|
|
21
|
-
def wait_for_admission(scheduler, runnable_count)
|
|
22
|
-
return false if @pool.drained?
|
|
23
|
-
limit = @pool.max_inflight
|
|
24
|
-
return true unless limit
|
|
18
|
+
def settings = @pool.settings
|
|
19
|
+
def idle_timeout = settings.shrink_after
|
|
25
20
|
|
|
21
|
+
def wait_for_admission(scheduler, runnable_count, settings = self.settings)
|
|
26
22
|
loop do
|
|
27
23
|
return false if @pool.drained?
|
|
28
|
-
return
|
|
29
|
-
|
|
24
|
+
return settings if @pool.closing?
|
|
25
|
+
count = @pool.size
|
|
26
|
+
limit = settings.max_inflight
|
|
27
|
+
return false if count > settings.max_size && retire?(excess: true)
|
|
28
|
+
# Busy workers keep queued dependencies runnable until they become idle.
|
|
29
|
+
return settings if !limit || count >= settings.max_size || @inflight.value < limit
|
|
30
|
+
return settings if runnable_count && scheduler.public_send(runnable_count).zero?
|
|
30
31
|
|
|
32
|
+
# Read the generation before rechecking state to avoid a lost configuration wakeup.
|
|
31
33
|
generation = @pool.admission_generation
|
|
32
|
-
|
|
34
|
+
settings = self.settings
|
|
35
|
+
count = @pool.size
|
|
36
|
+
limit = settings.max_inflight
|
|
37
|
+
next if @pool.closing? || !limit || count >= settings.max_size || @inflight.value < limit
|
|
33
38
|
@pool.wait_for_admission(generation)
|
|
39
|
+
settings = self.settings
|
|
34
40
|
end
|
|
35
41
|
end
|
|
36
42
|
|
|
37
|
-
def task_started
|
|
38
|
-
@inflight
|
|
39
|
-
@pool.task_taken
|
|
43
|
+
def task_started(settings = self.settings)
|
|
44
|
+
@inflight.increment
|
|
45
|
+
@pool.task_taken(settings)
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
def task_finished
|
|
43
|
-
return unless @inflight
|
|
44
49
|
@inflight.decrement
|
|
45
|
-
|
|
50
|
+
settings = self.settings
|
|
51
|
+
count = @pool.size
|
|
52
|
+
if count > settings.max_size
|
|
53
|
+
@inflight.value.zero? ? @pool.wake_retiring_workers : @pool.admission_changed
|
|
54
|
+
elsif settings.max_inflight && count < settings.max_size
|
|
55
|
+
@pool.admission_changed
|
|
56
|
+
end
|
|
46
57
|
end
|
|
47
58
|
|
|
48
|
-
def retire?
|
|
49
|
-
return false unless @inflight
|
|
50
|
-
return false unless @pool.retire_worker
|
|
51
|
-
@registered.compare_and_set(true, false)
|
|
59
|
+
def retire?(excess: false, expected: nil)
|
|
60
|
+
return false unless @inflight.value.zero?
|
|
61
|
+
return false unless @pool.retire_worker(excess:, expected:)
|
|
62
|
+
retired = @registered.compare_and_set(true, false)
|
|
63
|
+
@pool.worker_retired if retired
|
|
64
|
+
retired
|
|
52
65
|
end
|
|
53
66
|
|
|
54
67
|
def stopped(error, replace: true) # rubocop:disable Naming/PredicateMethod
|
|
@@ -9,7 +9,7 @@ module Farce
|
|
|
9
9
|
module ReservationWaiting
|
|
10
10
|
def self.wait(signal, generation, deadline)
|
|
11
11
|
while true
|
|
12
|
-
remaining =
|
|
12
|
+
remaining = Clock.remaining(deadline)
|
|
13
13
|
return false if remaining && !remaining.positive?
|
|
14
14
|
|
|
15
15
|
# LeaseWaiting supplies the full remaining timeout on default engines
|
|
@@ -146,8 +146,8 @@ module Farce
|
|
|
146
146
|
service_policy
|
|
147
147
|
break if idle?
|
|
148
148
|
deadline = @timers.peek_priority
|
|
149
|
-
timeout =
|
|
150
|
-
timeout = 0 if
|
|
149
|
+
timeout = Clock.remaining(deadline)
|
|
150
|
+
timeout = 0 if ready? || !@mailbox.empty?
|
|
151
151
|
dispatch(timeout, @timers.empty? ? @dispatch_budget : 256)
|
|
152
152
|
end
|
|
153
153
|
|
|
@@ -24,6 +24,11 @@ module Farce
|
|
|
24
24
|
super
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def replace_if_exists(key, replacement, mode: nil, **)
|
|
28
|
+
@manager.validate_mode!(mode)
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
27
32
|
def store_if_absent(key, mode: nil, **)
|
|
28
33
|
raise LocalJumpError, "no block given" unless block_given?
|
|
29
34
|
@manager.validate_mode!(mode)
|