farce 0.4.1-java → 0.6.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 +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/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/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 +22 -2
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
module Internal
|
|
7
|
+
class PortableTrie
|
|
8
|
+
# @api private
|
|
9
|
+
module IgnoreCase
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def each_result(input, peek:, deduplicate:, &)
|
|
13
|
+
raise TypeError, "input must be a String" unless input.is_a?(String)
|
|
14
|
+
input = String.new(input)
|
|
15
|
+
validate_encoding!(input, "input")
|
|
16
|
+
|
|
17
|
+
input_length = input.length
|
|
18
|
+
static_input = input.downcase
|
|
19
|
+
if static_input.length != input_length
|
|
20
|
+
return each_expanding_result(input, static_input, peek:, deduplicate:, &)
|
|
21
|
+
end
|
|
22
|
+
seen = {} if deduplicate
|
|
23
|
+
stack = [[@root, 0, EMPTY_CAPTURES, EMPTY_NAMED_CAPTURES, 0, 0]]
|
|
24
|
+
|
|
25
|
+
until stack.empty?
|
|
26
|
+
node, position, captures, named, phase, index = stack.pop
|
|
27
|
+
if phase.zero?
|
|
28
|
+
child = nil
|
|
29
|
+
stride = node.stride
|
|
30
|
+
if node.fast_static
|
|
31
|
+
key = static_input[position, stride]
|
|
32
|
+
child = node.fast_static[key] if key
|
|
33
|
+
elsif position < input_length && !node.static.empty?
|
|
34
|
+
child = node.static[static_input[position]]
|
|
35
|
+
end
|
|
36
|
+
if child
|
|
37
|
+
# Retain only frames with alternatives or a terminal result.
|
|
38
|
+
stack << [node, position, captures, named, 1, 0] unless node.dynamic.empty? && node.tokens.empty?
|
|
39
|
+
stack << [child, position + stride, captures, named, 0, 0]
|
|
40
|
+
next
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
if index < node.dynamic.length
|
|
44
|
+
regexp, child = node.dynamic[index]
|
|
45
|
+
if index + 1 < node.dynamic.length || !node.tokens.empty?
|
|
46
|
+
stack << [node, position, captures, named, 1, index + 1]
|
|
47
|
+
end
|
|
48
|
+
if (matched = regexp.match(input, position))
|
|
49
|
+
stack << [
|
|
50
|
+
child,
|
|
51
|
+
matched.end(0),
|
|
52
|
+
captures + matched.captures,
|
|
53
|
+
merge_named_captures(named, matched.named_captures),
|
|
54
|
+
0,
|
|
55
|
+
0
|
|
56
|
+
]
|
|
57
|
+
end
|
|
58
|
+
elsif (peek || position == input_length) && !node.tokens.empty?
|
|
59
|
+
node.tokens.each do |token_id|
|
|
60
|
+
next if deduplicate && seen.key?(token_id)
|
|
61
|
+
|
|
62
|
+
seen[token_id] = true if deduplicate
|
|
63
|
+
yield(
|
|
64
|
+
@tokens.fetch(token_id),
|
|
65
|
+
captures.map { |value| value&.dup },
|
|
66
|
+
copy_named_captures(named),
|
|
67
|
+
input[position..] || +""
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def each_expanding_result(input, static_input, peek:, deduplicate:)
|
|
75
|
+
to_original = [0]
|
|
76
|
+
from_original = [0]
|
|
77
|
+
input.each_char.with_index do |character, index|
|
|
78
|
+
count = character.downcase.length
|
|
79
|
+
# An internal lowercase boundary cannot start a regexp or end a result.
|
|
80
|
+
(count - 1).times { to_original << nil }
|
|
81
|
+
to_original << (index + 1)
|
|
82
|
+
from_original << (to_original.length - 1)
|
|
83
|
+
end
|
|
84
|
+
input_length = static_input.length
|
|
85
|
+
seen = {} if deduplicate
|
|
86
|
+
stack = [[@root, 0, EMPTY_CAPTURES, EMPTY_NAMED_CAPTURES, 0, 0]]
|
|
87
|
+
|
|
88
|
+
until stack.empty?
|
|
89
|
+
node, position, captures, named, phase, index = stack.pop
|
|
90
|
+
original_position = to_original[position]
|
|
91
|
+
case phase
|
|
92
|
+
when 0
|
|
93
|
+
stack << [node, position, captures, named, 1, 0]
|
|
94
|
+
if node.fast_static
|
|
95
|
+
key = static_input[position, node.stride]
|
|
96
|
+
if key && (child = node.fast_static[key])
|
|
97
|
+
stack << [child, position + node.stride, captures, named, 0, 0]
|
|
98
|
+
end
|
|
99
|
+
elsif position < input_length && (child = node.static[static_input[position]])
|
|
100
|
+
stack << [child, position + 1, captures, named, 0, 0]
|
|
101
|
+
end
|
|
102
|
+
when 1
|
|
103
|
+
if index < node.dynamic.length
|
|
104
|
+
regexp, child = node.dynamic[index]
|
|
105
|
+
stack << [node, position, captures, named, 1, index + 1]
|
|
106
|
+
if original_position && (matched = regexp.match(input, original_position))
|
|
107
|
+
stack << [
|
|
108
|
+
child,
|
|
109
|
+
from_original[matched.end(0)],
|
|
110
|
+
captures + matched.captures,
|
|
111
|
+
merge_named_captures(named, matched.named_captures),
|
|
112
|
+
0,
|
|
113
|
+
0
|
|
114
|
+
]
|
|
115
|
+
end
|
|
116
|
+
elsif original_position && (peek || position == input_length) && !node.tokens.empty?
|
|
117
|
+
node.tokens.each do |token_id|
|
|
118
|
+
next if deduplicate && seen.key?(token_id)
|
|
119
|
+
|
|
120
|
+
seen[token_id] = true if deduplicate
|
|
121
|
+
yield(
|
|
122
|
+
@tokens.fetch(token_id),
|
|
123
|
+
captures.map { |value| value&.dup },
|
|
124
|
+
copy_named_captures(named),
|
|
125
|
+
input[original_position..] || +""
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
private_constant :IgnoreCase
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -76,11 +76,16 @@ module Farce
|
|
|
76
76
|
end
|
|
77
77
|
private_constant :Node
|
|
78
78
|
|
|
79
|
-
def initialize(entries, tokens)
|
|
79
|
+
def initialize(entries, tokens, ignore_case: false)
|
|
80
80
|
validate_array!(entries, "entries")
|
|
81
81
|
validate_array!(tokens, "tokens")
|
|
82
82
|
validate_tokens!(tokens)
|
|
83
83
|
|
|
84
|
+
if ignore_case
|
|
85
|
+
@ignore_case = true
|
|
86
|
+
extend IgnoreCase
|
|
87
|
+
end
|
|
88
|
+
|
|
84
89
|
@tokens = Array.new(tokens).freeze
|
|
85
90
|
@root = Node.new
|
|
86
91
|
nodes = [@root]
|
|
@@ -103,6 +108,7 @@ module Farce
|
|
|
103
108
|
when String
|
|
104
109
|
literal = String.new(part)
|
|
105
110
|
validate_encoding!(literal, "edge")
|
|
111
|
+
literal.downcase! if ignore_case
|
|
106
112
|
literal.each_char do |character|
|
|
107
113
|
child = node.static[character]
|
|
108
114
|
unless child
|
|
@@ -171,6 +177,12 @@ module Farce
|
|
|
171
177
|
|
|
172
178
|
private
|
|
173
179
|
|
|
180
|
+
def initialize_dup(other)
|
|
181
|
+
super
|
|
182
|
+
# Unlike clone, dup does not retain the instance's matching module.
|
|
183
|
+
extend IgnoreCase if @ignore_case
|
|
184
|
+
end
|
|
185
|
+
|
|
174
186
|
def anchor(regexp)
|
|
175
187
|
compile_regexp("\\G(?:#{regexp.source})", regexp)
|
|
176
188
|
rescue RegexpError
|
|
@@ -314,4 +326,5 @@ module Farce
|
|
|
314
326
|
end
|
|
315
327
|
end
|
|
316
328
|
|
|
329
|
+
require "farce/engine/shared/trie/ignore_case"
|
|
317
330
|
require "farce/engine/shared/trie_builder"
|
|
@@ -17,6 +17,13 @@ module Farce
|
|
|
17
17
|
# terminal match. A regexp edge is atomic. Later edges cannot make a regexp
|
|
18
18
|
# choose a different endpoint.
|
|
19
19
|
#
|
|
20
|
+
# Pass `ignore_case: true` to {.build} or {Builder.new} to compare all static
|
|
21
|
+
# segments using Unicode downcasing. Regexp options do not change. Regexp
|
|
22
|
+
# matches, captures, and suffixes use the original input and retain its casing.
|
|
23
|
+
# This does not include every equivalence supported by `Regexp::IGNORECASE`.
|
|
24
|
+
# For example, `"SS"` matches `"ss"`, but `"ß"` does not match `"ss"`.
|
|
25
|
+
# Case-equivalent static entries share a branch and retain their token order.
|
|
26
|
+
#
|
|
20
27
|
# Tokens must already be Ractor-shareable. They are stored and returned
|
|
21
28
|
# unchanged. Equal but different token objects remain separate. Reusing the
|
|
22
29
|
# same token object produces at most one result per lookup.
|
|
@@ -94,20 +101,23 @@ module Farce
|
|
|
94
101
|
# Collects entries and publishes immutable {Trie} snapshots.
|
|
95
102
|
class Builder
|
|
96
103
|
# Create an empty builder.
|
|
97
|
-
|
|
104
|
+
# @param ignore_case [Boolean] compare all static segments without case
|
|
105
|
+
def initialize(ignore_case: false)
|
|
98
106
|
@entries = []
|
|
107
|
+
@ignore_case = !!ignore_case
|
|
99
108
|
end
|
|
100
109
|
|
|
101
110
|
# @api private
|
|
102
111
|
def marshal_dump
|
|
103
112
|
entries = @entries.map { |parts, token| [parts, Internal::MarshalSupport.value(token)] }
|
|
104
|
-
[1, entries, frozen
|
|
113
|
+
[1, entries, frozen?, @ignore_case]
|
|
105
114
|
end
|
|
106
115
|
|
|
107
116
|
# @api private
|
|
108
117
|
def marshal_load(data)
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
length = data.is_a?(Array) && data.length == 3 ? 2 : 3
|
|
119
|
+
entries, frozen, ignore_case = Internal::MarshalSupport.payload(data, length)
|
|
120
|
+
initialize(ignore_case:)
|
|
111
121
|
entries.each { |parts, token| add(parts, Internal::MarshalSupport.restore_value(token)) }
|
|
112
122
|
freeze if frozen
|
|
113
123
|
end
|
|
@@ -159,7 +169,7 @@ module Farce
|
|
|
159
169
|
[parts.dup.freeze, token_ids.fetch(token)].freeze
|
|
160
170
|
end.freeze
|
|
161
171
|
|
|
162
|
-
Trie.send(:build_entries, entries, tokens.freeze)
|
|
172
|
+
Trie.send(:build_entries, entries, tokens.freeze, ignore_case: @ignore_case)
|
|
163
173
|
end
|
|
164
174
|
|
|
165
175
|
private
|
|
@@ -180,17 +190,18 @@ module Farce
|
|
|
180
190
|
class << self
|
|
181
191
|
# Build an immutable trie.
|
|
182
192
|
#
|
|
193
|
+
# @param ignore_case [Boolean] compare all static segments without case
|
|
183
194
|
# @yieldparam builder [Builder]
|
|
184
195
|
# @return [Trie]
|
|
185
|
-
def build
|
|
186
|
-
builder = Builder.new
|
|
196
|
+
def build(ignore_case: false)
|
|
197
|
+
builder = Builder.new(ignore_case:)
|
|
187
198
|
yield builder if block_given?
|
|
188
199
|
builder.build
|
|
189
200
|
end
|
|
190
201
|
|
|
191
202
|
private
|
|
192
203
|
|
|
193
|
-
def build_entries(entries, tokens) = new(entries, tokens)
|
|
204
|
+
def build_entries(entries, tokens, ignore_case:) = new(entries, tokens, ignore_case:)
|
|
194
205
|
|
|
195
206
|
private :new
|
|
196
207
|
end
|
|
@@ -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
|
|
@@ -6,9 +6,11 @@ module Farce
|
|
|
6
6
|
# @!visibility private
|
|
7
7
|
module Internal # :nodoc: all
|
|
8
8
|
class UnsharedVector
|
|
9
|
+
def self.normalize_source(source) = source.is_a?(Integer) ? Array.new(source) : source
|
|
10
|
+
|
|
9
11
|
def initialize(source = nil, compare_by_identity: false)
|
|
10
12
|
validate_boolean(compare_by_identity, "compare_by_identity")
|
|
11
|
-
raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array)
|
|
13
|
+
raise TypeError, "source must be an Array" unless source.nil? || source.is_a?(Array) || source.is_a?(Integer)
|
|
12
14
|
|
|
13
15
|
@values = source ? Array.new(source) : []
|
|
14
16
|
@compare_by_identity = compare_by_identity
|
|
@@ -274,12 +276,12 @@ module Farce
|
|
|
274
276
|
finish_update if completed
|
|
275
277
|
end
|
|
276
278
|
|
|
277
|
-
def wait_until_changed(index, expected, timeout: nil)
|
|
278
|
-
wait_for_value(convert_index(index), expected, timeout_deadline(timeout), non_nil: false)
|
|
279
|
+
def wait_until_changed(index, expected, timeout: nil, &fallback)
|
|
280
|
+
wait_for_value(convert_index(index), expected, timeout_deadline(timeout), fallback, non_nil: false)
|
|
279
281
|
end
|
|
280
282
|
|
|
281
283
|
def wait_until_non_nil(index, timeout: nil)
|
|
282
|
-
wait_for_value(convert_index(index), nil, timeout_deadline(timeout), non_nil: true)
|
|
284
|
+
wait_for_value(convert_index(index), nil, timeout_deadline(timeout), nil, non_nil: true)
|
|
283
285
|
end
|
|
284
286
|
|
|
285
287
|
def compare_by_identity? = @compare_by_identity
|
|
@@ -341,20 +343,21 @@ module Farce
|
|
|
341
343
|
raise FrozenError.new("can't modify frozen #{self.class}", receiver: self)
|
|
342
344
|
end
|
|
343
345
|
|
|
344
|
-
def wait_for_value(index, expected, deadline, non_nil:)
|
|
346
|
+
def wait_for_value(index, expected, deadline, fallback, non_nil:)
|
|
345
347
|
while true
|
|
346
348
|
generation = @signal.generation
|
|
347
349
|
current = @mutex.synchronize { @values[index] }
|
|
348
|
-
ready = non_nil ? !current.nil? :
|
|
350
|
+
ready = non_nil ? !current.nil? :
|
|
351
|
+
Internal.value_changed?(current, expected, compare_by_identity?)
|
|
349
352
|
return current if ready
|
|
350
|
-
return unless wait_for_signal(generation, deadline)
|
|
353
|
+
return fallback&.call unless wait_for_signal(generation, deadline)
|
|
351
354
|
end
|
|
352
355
|
end
|
|
353
356
|
|
|
354
357
|
def wait_for_signal(generation, deadline)
|
|
355
358
|
return @signal.wait(generation) unless deadline
|
|
356
359
|
|
|
357
|
-
remaining =
|
|
360
|
+
remaining = Clock.remaining(deadline)
|
|
358
361
|
return false unless remaining.positive?
|
|
359
362
|
!@signal.wait(generation, timeout: remaining).nil?
|
|
360
363
|
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
|
|
@@ -6,6 +6,8 @@ module Farce
|
|
|
6
6
|
# @!visibility private
|
|
7
7
|
module Internal # :nodoc: all
|
|
8
8
|
class Vector < UnsharedVector
|
|
9
|
+
def self.new_for_modes(source) = new(source, compare_by_identity: true)
|
|
10
|
+
|
|
9
11
|
def initialize(source = nil, **)
|
|
10
12
|
source.each { check_value(it) } if source.is_a?(Array)
|
|
11
13
|
super
|
|
@@ -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
|
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
|