farce 0.4.1-aarch64-linux-gnu → 0.6.0-aarch64-linux-gnu
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 +98 -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 +32 -0
- data/docs/variants.md +9 -0
- data/ext/farce/atom.c +23 -7
- data/ext/farce/containers.h +3 -0
- data/ext/farce/counter.c +4 -6
- data/ext/farce/depend +3 -1
- data/ext/farce/extconf.rb +19 -14
- data/ext/farce/farce.c +110 -3
- data/ext/farce/map.c +15 -7
- data/ext/farce/priority_queue.c +46 -1
- data/ext/farce/queue.c +141 -7
- data/ext/farce/trie.c +140 -72
- data/ext/farce/trie_search.h +114 -0
- data/ext/farce/vector.c +53 -21
- data/ext/farce/weak_map.c +16 -11
- data/ext/farce/weak_references.h +23 -0
- data/ext/java/org/farce/FiberScheduler.java +8 -6
- data/ext/java/org/farce/PriorityQueue.java +18 -2
- data/lib/farce/abstract/atom.rb +5 -1
- data/lib/farce/abstract/concurrent_map.rb +122 -0
- data/lib/farce/abstract/counter.rb +36 -1
- data/lib/farce/abstract/executor.rb +30 -7
- data/lib/farce/abstract/flag.rb +12 -0
- data/lib/farce/abstract/molecule.rb +14 -14
- 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 +15 -2
- data/lib/farce/clock.rb +20 -7
- 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 +5 -15
- data/lib/farce/deduper.rb +12 -4
- data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
- data/lib/farce/engine/jruby/map.rb +8 -6
- data/lib/farce/engine/jruby/signal.rb +2 -2
- data/lib/farce/engine/jvm/counter.rb +0 -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/3.4/farce.so +0 -0
- data/lib/farce/engine/ruby/4.0/farce.so +0 -0
- 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_cache_mirror.rb +56 -0
- data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
- data/lib/farce/engine/ruby.rb +5 -1
- 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/trie/ignore_case.rb +136 -0
- data/lib/farce/engine/shared/trie.rb +14 -1
- data/lib/farce/engine/shared/trie_builder.rb +19 -8
- data/lib/farce/engine/shared/unshared_atom.rb +1 -1
- data/lib/farce/engine/shared/unshared_vector.rb +11 -8
- data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
- data/lib/farce/engine/shared/vector.rb +2 -0
- 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/shared.rb +2 -0
- data/lib/farce/engine/truffleruby/map.rb +8 -6
- data/lib/farce/engine/truffleruby/native/counter.rb +0 -2
- data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
- data/lib/farce/engine/truffleruby/signal.rb +1 -1
- data/lib/farce/engine/truffleruby.rb +9 -0
- data/lib/farce/extension.rb +5 -3
- data/lib/farce/integrations/concurrent.rb +16 -3
- data/lib/farce/integrations/sorted_set.rb +1 -0
- data/lib/farce/internal/blocking_priority_queue.rb +4 -9
- data/lib/farce/internal/fake_ractor.rb +1 -1
- data/lib/farce/internal/flag_waiting.rb +1 -1
- data/lib/farce/internal/freeze.rb +1 -5
- 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 +17 -9
- 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 +19 -8
- data/lib/farce/lazy.rb +3 -0
- data/lib/farce/lazy_ref.rb +3 -0
- data/lib/farce/local/counter.rb +4 -10
- 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/map.rb +8 -1
- data/lib/farce/memoize.rb +319 -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/shareable.rb +3 -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/strict/weak_cache.rb +199 -0
- 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 +20 -16
- data/lib/farce/version.rb +1 -1
- data/lib/farce/walker/definitions.rb +31 -2
- data/lib/farce/walker/modification.rb +28 -14
- data/lib/farce/walker.rb +9 -1
- data/lib/farce/weak_atom.rb +5 -2
- data/lib/farce/weak_key_map.rb +1 -1
- data/lib/farce/weak_map.rb +1 -1
- data/lib/farce/weak_value_map.rb +1 -1
- data/lib/farce.rb +3 -1
- metadata +23 -1
data/lib/farce/engine/shared.rb
CHANGED
|
@@ -54,6 +54,8 @@ module Farce
|
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
def native_weak_maps? = true
|
|
58
|
+
|
|
57
59
|
# define_method supplies the receiver. Rebinding first is unnecessary without
|
|
58
60
|
# native Ractor isolation and can change the method context used by super.
|
|
59
61
|
def prepare_method_definition(&definition) = definition
|
|
@@ -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
|
|
@@ -39,6 +39,15 @@ module Farce
|
|
|
39
39
|
|
|
40
40
|
def native_ractors? = false
|
|
41
41
|
|
|
42
|
+
# Range#initialize rejects open endpoints on TruffleRuby, while Range.new accepts them.
|
|
43
|
+
def initialize_range(target, first, last, exclude_end)
|
|
44
|
+
if first.nil? || last.nil?
|
|
45
|
+
target.__send__(:initialize_copy, target.class.new(first, last, exclude_end))
|
|
46
|
+
else
|
|
47
|
+
target.__send__(:initialize, first, last, exclude_end)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
42
51
|
# @api private
|
|
43
52
|
# TruffleRuby probes this additional hook before using _dump.
|
|
44
53
|
def marshal_protocol_method?(name)
|
data/lib/farce/extension.rb
CHANGED
|
@@ -100,13 +100,15 @@ module Farce
|
|
|
100
100
|
# @yield the block containing method definitions
|
|
101
101
|
# @yieldreceiver [Module, Refinement] the module/refinement used for extending the target class.
|
|
102
102
|
# @return [self] the extension instance
|
|
103
|
-
|
|
103
|
+
# rubocop:disable-next Naming/BlockForwarding, Style/ArgumentsForwarding
|
|
104
|
+
def define(*targets, &block)
|
|
105
|
+
# JRuby rejects repeated anonymous block forwarding inside a block.
|
|
104
106
|
@lock.synchronize do
|
|
105
107
|
check_state!
|
|
106
108
|
targets.each do |target|
|
|
107
|
-
refine(target, &)
|
|
109
|
+
refine(target, &block)
|
|
108
110
|
mixin = @definitions.store_if_absent(target) { Mixin.new(self, target) }
|
|
109
|
-
mixin.module_eval(&)
|
|
111
|
+
mixin.module_eval(&block)
|
|
110
112
|
mixin.install if installed?
|
|
111
113
|
end
|
|
112
114
|
end
|
|
@@ -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
|
|
@@ -8,11 +8,7 @@ module Farce
|
|
|
8
8
|
module Freeze
|
|
9
9
|
# Structurally freeze a Ruby facade, then validate and publish its object graph.
|
|
10
10
|
# This deliberately bypasses an override that controls logical frozen state.
|
|
11
|
-
def self.publish(object)
|
|
12
|
-
Object.instance_method(:freeze).bind_call(object)
|
|
13
|
-
::Ractor.make_shareable(object) if Internal.native_ractors?
|
|
14
|
-
object
|
|
15
|
-
end
|
|
11
|
+
def self.publish(object) = Internal.publish_shareable(object)
|
|
16
12
|
|
|
17
13
|
def self.check(object)
|
|
18
14
|
return object unless object.frozen?
|
|
@@ -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
|
|
@@ -22,9 +22,7 @@ module Farce
|
|
|
22
22
|
compare_keys_by_identity: compare_by_identity, compare_values_by_identity: compare_by_identity)
|
|
23
23
|
initial_mapping = convert_entries(initial_mapping)
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
valid_comparison = identity.bind_call(compare_values_by_identity, true) ||
|
|
27
|
-
identity.bind_call(compare_values_by_identity, false)
|
|
25
|
+
valid_comparison = true.equal?(compare_values_by_identity) || false.equal?(compare_values_by_identity)
|
|
28
26
|
|
|
29
27
|
raise ArgumentError, "compare_values_by_identity must be a boolean" unless valid_comparison
|
|
30
28
|
@compare_values_by_identity = compare_values_by_identity
|
|
@@ -33,9 +31,11 @@ module Farce
|
|
|
33
31
|
@map = new_map(compare_by_identity:, compare_keys_by_identity:, compare_values_by_identity: true)
|
|
34
32
|
KeyNormalizer.prepare_concurrent(@map)
|
|
35
33
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
unless nil.equal?(normalize_keys)
|
|
35
|
+
restoring = KeyNormalizer.restoration?(normalize_keys)
|
|
36
|
+
normalizer = KeyNormalizer.build(normalize_keys, shareable: true)
|
|
37
|
+
KeyNormalizer.install_concurrent(self, normalizer) unless restoring
|
|
38
|
+
end
|
|
39
39
|
|
|
40
40
|
initial_mapping&.each { |key, value| self[key] = value }
|
|
41
41
|
KeyNormalizer.install_concurrent(self, normalizer) if restoring
|
|
@@ -143,18 +143,26 @@ 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
|
-
|
|
151
|
-
|
|
157
|
+
# JRuby rejects repeated anonymous block forwarding inside a block.
|
|
158
|
+
while true
|
|
159
|
+
current = @map.get_prepared(prepared_key, timeout: Clock.remaining(deadline)) { TIMED_OUT }
|
|
152
160
|
return unwrap_result(current, &) if TIMED_OUT.equal?(current)
|
|
153
161
|
return unwrap_value(current) unless values_equal?(nil.equal?(current) ? NIL_VALUE : current, expected)
|
|
154
162
|
result = @map.wait_until_changed_prepared(
|
|
155
163
|
prepared_key,
|
|
156
164
|
current,
|
|
157
|
-
timeout:
|
|
165
|
+
timeout: Clock.remaining(deadline),
|
|
158
166
|
) { TIMED_OUT }
|
|
159
167
|
return unwrap_result(result, &) if TIMED_OUT.equal?(result)
|
|
160
168
|
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
|
|
@@ -113,9 +113,20 @@ module Farce
|
|
|
113
113
|
def storage_thread(thread) = thread
|
|
114
114
|
def prepare_mutable_numeric(_) = nil
|
|
115
115
|
def prepare_map_access(_klass, _kind) = nil
|
|
116
|
+
def prepare_shareable_class(_base) = nil
|
|
116
117
|
def finalize_engine = nil
|
|
117
118
|
def with_scheduler_thread_group = yield(ThreadGroup.new)
|
|
118
119
|
|
|
120
|
+
def initialize_range(target, first, last, exclude_end)
|
|
121
|
+
target.__send__(:initialize, first, last, exclude_end)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def publish_shareable(object)
|
|
125
|
+
Object.instance_method(:freeze).bind_call(object)
|
|
126
|
+
::Ractor.make_shareable(object) if native_ractors?
|
|
127
|
+
object
|
|
128
|
+
end
|
|
129
|
+
|
|
119
130
|
def delegate(from, to, *methods)
|
|
120
131
|
methods.each do |method|
|
|
121
132
|
if to.is_a?(Module)
|
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/counter.rb
CHANGED
|
@@ -50,19 +50,13 @@ module Farce
|
|
|
50
50
|
|
|
51
51
|
# Increment the counter atomically.
|
|
52
52
|
# @param by [Numeric, String, #to_int] the amount, converted to an Integer
|
|
53
|
-
# @return [
|
|
54
|
-
def increment(by = 1)
|
|
55
|
-
internal_counter.increment(by)
|
|
56
|
-
self
|
|
57
|
-
end
|
|
53
|
+
# @return [Integer] the value produced by this atomic update
|
|
54
|
+
def increment(by = 1) = internal_counter.increment(by)
|
|
58
55
|
|
|
59
56
|
# Decrement the counter atomically.
|
|
60
57
|
# @param by [Numeric, String, #to_int] the amount, converted to an Integer
|
|
61
|
-
# @return [
|
|
62
|
-
def decrement(by = 1)
|
|
63
|
-
internal_counter.decrement(by)
|
|
64
|
-
self
|
|
65
|
-
end
|
|
58
|
+
# @return [Integer] the value produced by this atomic update
|
|
59
|
+
def decrement(by = 1) = internal_counter.decrement(by)
|
|
66
60
|
|
|
67
61
|
alias subtract decrement
|
|
68
62
|
alias remove decrement
|