farce 0.4.1-java → 0.5.0-java
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/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 +19 -2
|
@@ -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)
|
data/lib/farce/internal.rb
CHANGED
|
@@ -77,18 +77,12 @@ module Farce
|
|
|
77
77
|
Clock.now + timeout
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
def remaining_timeout(deadline)
|
|
81
|
-
return unless deadline
|
|
82
|
-
remaining = deadline - Clock.now
|
|
83
|
-
remaining.negative? ? 0 : remaining
|
|
84
|
-
end
|
|
85
|
-
|
|
86
80
|
# Repeat with one timeout budget. Even a zero timeout permits the first check.
|
|
87
81
|
def with_timeout(timeout)
|
|
88
82
|
raise LocalJumpError, "no block given" unless block_given?
|
|
89
83
|
deadline = timeout_deadline(timeout)
|
|
90
84
|
loop do
|
|
91
|
-
yield
|
|
85
|
+
yield Clock.remaining(deadline), deadline
|
|
92
86
|
return if deadline && Clock.now >= deadline
|
|
93
87
|
end
|
|
94
88
|
end
|
|
@@ -98,10 +92,16 @@ module Farce
|
|
|
98
92
|
with_timeout(timeout) do |_, deadline|
|
|
99
93
|
current = keys.empty? ? receiver.value : receiver[*keys]
|
|
100
94
|
return current if yield(current)
|
|
101
|
-
receiver.wait_until_changed(*keys, current, timeout:
|
|
95
|
+
receiver.wait_until_changed(*keys, current, timeout: Clock.remaining(deadline)) { return }
|
|
102
96
|
end
|
|
103
97
|
end
|
|
104
98
|
|
|
99
|
+
# An unchanged observation must permit waiting even when its equality is non-reflexive.
|
|
100
|
+
def value_changed?(current, expected, identity)
|
|
101
|
+
return false if BasicObject.instance_method(:equal?).bind_call(current, expected)
|
|
102
|
+
identity || !(current == expected) # rubocop:disable Style/InverseMethods -- != can be overridden independently.
|
|
103
|
+
end
|
|
104
|
+
|
|
105
105
|
def garbage_collectable?(object)
|
|
106
106
|
case object
|
|
107
107
|
when Integer, Float, Complex, Rational, Symbol, true, false, nil then false
|
data/lib/farce/lazy.rb
CHANGED
|
@@ -59,6 +59,9 @@ module Farce
|
|
|
59
59
|
# # Will only compute the expensive attribute once, and return the same value to all Ractors.
|
|
60
60
|
# 5.times { Ractor.new(object) { it.expensive_attribute } }
|
|
61
61
|
# ```
|
|
62
|
+
#
|
|
63
|
+
# @see Farce::Memoize
|
|
64
|
+
# @see Farce::LazyRef
|
|
62
65
|
class Lazy < Farce::Abstract::Lazy
|
|
63
66
|
include Shareable::Tracked
|
|
64
67
|
|
data/lib/farce/lazy_ref.rb
CHANGED
|
@@ -22,6 +22,9 @@ module Farce
|
|
|
22
22
|
# # initialized.value # => 0
|
|
23
23
|
# # lazy_ref[:key] = :value
|
|
24
24
|
# # initialized.value # => 1
|
|
25
|
+
# #
|
|
26
|
+
# # @see Farce::Memoize
|
|
27
|
+
# # @see Farce::Lazy
|
|
25
28
|
# class LazyRef < Farce::Reference
|
|
26
29
|
# # @!macro modes
|
|
27
30
|
# # @overload initialize(factory, mode: :smart)
|
data/lib/farce/local/lease.rb
CHANGED
|
@@ -34,10 +34,10 @@ module Farce
|
|
|
34
34
|
# (see Farce::Abstract::Lease#checkout)
|
|
35
35
|
def checkout(timeout: nil, &block)
|
|
36
36
|
deadline = timeout_deadline(timeout)
|
|
37
|
-
lease = scoped_lease(timeout:
|
|
37
|
+
lease = scoped_lease(timeout: Clock.remaining(deadline))
|
|
38
38
|
raise TimeoutError, "lease checkout timed out" unless lease
|
|
39
39
|
|
|
40
|
-
timeout =
|
|
40
|
+
timeout = Clock.remaining(deadline)
|
|
41
41
|
block ? lease.checkout(timeout:, &block) : lease.checkout(timeout:)
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -65,13 +65,6 @@ module Farce
|
|
|
65
65
|
end
|
|
66
66
|
Clock.now + timeout
|
|
67
67
|
end
|
|
68
|
-
|
|
69
|
-
def remaining_timeout(deadline)
|
|
70
|
-
return unless deadline
|
|
71
|
-
|
|
72
|
-
remaining = deadline - Clock.now
|
|
73
|
-
remaining.positive? ? remaining : 0
|
|
74
|
-
end
|
|
75
68
|
end
|
|
76
69
|
end
|
|
77
70
|
end
|
|
@@ -35,10 +35,10 @@ module Farce
|
|
|
35
35
|
|
|
36
36
|
def checkout_canonical(key, missing_key:, timeout: nil, &)
|
|
37
37
|
deadline = timeout_deadline(timeout)
|
|
38
|
-
map = scoped_lease_map(timeout:
|
|
38
|
+
map = scoped_lease_map(timeout: Clock.remaining(deadline))
|
|
39
39
|
raise TimeoutError, "lease checkout timed out" unless map
|
|
40
40
|
|
|
41
|
-
map.checkout(key, timeout:
|
|
41
|
+
map.checkout(key, timeout: Clock.remaining(deadline), receiver: self, missing_key:, &)
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def try_checkout_canonical(key, missing_key:, &)
|
|
@@ -68,13 +68,6 @@ module Farce
|
|
|
68
68
|
end
|
|
69
69
|
Clock.now + timeout
|
|
70
70
|
end
|
|
71
|
-
|
|
72
|
-
def remaining_timeout(deadline)
|
|
73
|
-
return unless deadline
|
|
74
|
-
|
|
75
|
-
remaining = deadline - Clock.now
|
|
76
|
-
remaining.positive? ? remaining : 0
|
|
77
|
-
end
|
|
78
71
|
end
|
|
79
72
|
end
|
|
80
73
|
end
|
data/lib/farce/local/pool.rb
CHANGED
|
@@ -7,7 +7,8 @@ module Farce
|
|
|
7
7
|
# A shareable handle with an independent thread pool in each scope.
|
|
8
8
|
#
|
|
9
9
|
# Each pool uses the scaling settings and fiber scheduler configured at
|
|
10
|
-
# construction.
|
|
10
|
+
# construction. Changing settings tunes the current scope's pool. New scopes
|
|
11
|
+
# use the constructor settings. Tasks always keep their block and argument references. Only
|
|
11
12
|
# local and smart modes are accepted when `auto_local` is false. A scheduler
|
|
12
13
|
# constructor must be convertible to a shareable proc for use in each Ractor.
|
|
13
14
|
# {#close} drains only the current scope's pool. {#close_all} drains every pool
|
|
@@ -31,14 +32,45 @@ module Farce
|
|
|
31
32
|
grow_after: 0.005, shrink_after: 30, capacity: 1024, scope: :ractor,
|
|
32
33
|
backend: Internal::FROZEN_CONFIG.io_backend, &constructor)
|
|
33
34
|
initialize_configuration(min_size:, max_size:, max_inflight:, grow_after:, shrink_after:, backend:)
|
|
34
|
-
@
|
|
35
|
-
@
|
|
36
|
-
@
|
|
35
|
+
@capacity = capacity && Integer(capacity)
|
|
36
|
+
@constructor = prepare_constructor(constructor || Internal::FROZEN_CONFIG.fiber_scheduler_constructor)
|
|
37
|
+
@pools = Strict::Map.new
|
|
38
|
+
@closed_all = Internal::Flag.new(false)
|
|
37
39
|
@initialization_key = Object.new.freeze
|
|
38
40
|
super(scope:, min_size: @min_size, max_size: @max_size, max_inflight: @max_inflight,
|
|
39
41
|
grow_after: @grow_after, shrink_after: @shrink_after, capacity:, backend:)
|
|
40
42
|
end
|
|
41
43
|
|
|
44
|
+
# Settings changes affect only the current scope. New scopes use constructor defaults.
|
|
45
|
+
# @api private
|
|
46
|
+
def settings = (pool = current_pool) ? pool.settings : @settings.value
|
|
47
|
+
|
|
48
|
+
def capacity = (pool = current_pool) ? pool.capacity : @capacity
|
|
49
|
+
|
|
50
|
+
def min_size=(value)
|
|
51
|
+
scoped_value.min_size = value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def max_size=(value)
|
|
55
|
+
scoped_value.max_size = value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def max_inflight=(value)
|
|
59
|
+
scoped_value.max_inflight = value
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def grow_after=(value)
|
|
63
|
+
scoped_value.grow_after = value
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def shrink_after=(value)
|
|
67
|
+
scoped_value.shrink_after = value
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def capacity=(value)
|
|
71
|
+
scoped_value.capacity = value
|
|
72
|
+
end
|
|
73
|
+
|
|
42
74
|
# Stop accepting work in this scope and drain its queued tasks.
|
|
43
75
|
# @return [self]
|
|
44
76
|
def close
|
|
@@ -74,7 +106,8 @@ module Farce
|
|
|
74
106
|
def closed? = @closed_all.value || super
|
|
75
107
|
|
|
76
108
|
Internal.delegate(self, :scoped_value, :drained?, :admission_generation, :wait_for_admission,
|
|
77
|
-
:admission_changed, :task_taken, :retire_worker, :
|
|
109
|
+
:admission_changed, :task_taken, :retire_worker, :wake_retiring_workers,
|
|
110
|
+
:worker_retired, :worker_stopped, :scale_if_needed)
|
|
78
111
|
|
|
79
112
|
private
|
|
80
113
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Local
|
|
7
|
+
# Local variant of {Farce::Publisher}.
|
|
8
|
+
class Publisher < Abstract::Publisher
|
|
9
|
+
include Shareable::Unfreezable
|
|
10
|
+
|
|
11
|
+
# The scope with independent contents.
|
|
12
|
+
# @return [Symbol] The configured scope name.
|
|
13
|
+
attr_reader :scope
|
|
14
|
+
|
|
15
|
+
# @overload initialize(*subscribers)
|
|
16
|
+
# (see Farce::Abstract#initialize)
|
|
17
|
+
def initialize(*, scope: :ractor)
|
|
18
|
+
raise ArgumentError, "Invalid scope: #{scope.inspect}" unless Farce::SCOPES.include?(scope)
|
|
19
|
+
@scope = scope
|
|
20
|
+
super(*)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def map = Internal::Storage.store_if_absent(self, scope:) { Unshared::Map.new }
|
|
26
|
+
|
|
27
|
+
def default_mode = :local
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|