farce 0.1.0-x86_64-linux-gnu → 0.3.0-x86_64-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 +127 -1
- data/README.md +111 -17
- data/docs/gems/dry-types.md +8 -8
- data/docs/modes.md +75 -15
- data/docs/scopes.md +3 -1
- data/docs/variants.md +18 -0
- data/ext/farce/atom.c +37 -22
- data/ext/farce/bounded_map.c +17 -13
- data/ext/farce/containers.h +15 -0
- data/ext/farce/counter.c +13 -18
- data/ext/farce/exchanger.c +5 -1
- data/ext/farce/farce.c +1 -1
- data/ext/farce/flag.c +39 -3
- data/ext/farce/io.c +3 -3
- data/ext/farce/lock.c +17 -23
- data/ext/farce/map.c +88 -66
- data/ext/farce/priority_queue.c +55 -43
- data/ext/farce/queue.c +10 -4
- data/ext/farce/reactor.c +52 -25
- data/ext/farce/reactor.h +1 -1
- data/ext/farce/signal.c +5 -1
- data/ext/farce/transaction.c +20 -24
- data/ext/farce/tree_map.c +33 -18
- data/ext/farce/trie.c +81 -43
- data/ext/farce/unshareable.c +168 -19
- data/ext/farce/unshared_io_pool.h +21 -12
- data/ext/farce/unshared_signal.c +13 -2
- data/ext/farce/unshared_wait.h +3 -0
- data/ext/farce/vector.c +82 -18
- data/ext/farce/weak_map.c +75 -63
- data/lib/farce/_yard/macros.rb +18 -3
- data/lib/farce/abstract/executor.rb +130 -0
- data/lib/farce/abstract/flag.rb +73 -0
- data/lib/farce/abstract/pool.rb +334 -0
- data/lib/farce/abstract/queue.rb +4 -3
- data/lib/farce/abstract/scheduler.rb +539 -93
- data/lib/farce/abstract/set.rb +17 -5
- data/lib/farce/abstract/sorted_set.rb +2 -2
- data/lib/farce/abstract/timer_queue.rb +49 -10
- data/lib/farce/abstract/vector.rb +510 -28
- data/lib/farce/atom.rb +1 -1
- data/lib/farce/class_mirror.rb +20 -7
- data/lib/farce/config.rb +7 -1
- data/lib/farce/engine/jruby/signal.rb +7 -4
- data/lib/farce/engine/jvm/flag.rb +15 -3
- 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/require_hook.rb +18 -0
- data/lib/farce/engine/ruby.rb +7 -0
- data/lib/farce/engine/shared/main_scheduler.rb +2 -2
- data/lib/farce/engine/shared/parallel_scheduler.rb +1 -1
- data/lib/farce/engine/shared/proxy_owner.rb +1 -1
- data/lib/farce/engine/shared/unshared_atom.rb +9 -8
- data/lib/farce/engine/shared/unshared_vector.rb +31 -4
- data/lib/farce/engine/shared/vector.rb +8 -0
- data/lib/farce/engine/shared/weak_map/index.rb +14 -1
- data/lib/farce/engine/shared.rb +9 -0
- data/lib/farce/engine/truffleruby/native/flag.rb +15 -3
- data/lib/farce/envelope.rb +54 -7
- data/lib/farce/exchanger.rb +1 -1
- data/lib/farce/flag.rb +2 -0
- data/lib/farce/integrations/active_support.rb +1 -1
- data/lib/farce/integrations/bson.rb +1 -1
- data/lib/farce/integrations/cbor.rb +1 -1
- data/lib/farce/integrations/concurrent.rb +3 -3
- data/lib/farce/integrations/date.rb +8 -0
- data/lib/farce/integrations/dry_types.rb +7 -4
- data/lib/farce/integrations/json.rb +2 -2
- data/lib/farce/integrations/msgpack.rb +1 -1
- data/lib/farce/integrations/oj.rb +3 -4
- data/lib/farce/integrations/psych.rb +1 -1
- data/lib/farce/integrations/ractor_sharing.rb +1 -1
- data/lib/farce/integrations/sorted_set.rb +1 -1
- data/lib/farce/integrations/weakref.rb +9 -1
- data/lib/farce/integrations/yajl.rb +3 -3
- data/lib/farce/integrations.rb +33 -23
- data/lib/farce/internal/flag_waiting.rb +30 -0
- data/lib/farce/internal/freeze.rb +8 -3
- data/lib/farce/internal/lease_initialization.rb +1 -1
- data/lib/farce/internal/local_executor_methods.rb +33 -0
- data/lib/farce/internal/local_scheduled_task.rb +27 -0
- data/lib/farce/internal/map_value_modes.rb +1 -1
- data/lib/farce/internal/scheduled_task.rb +1 -1
- data/lib/farce/internal/scheduler_lifecycle.rb +1 -1
- data/lib/farce/internal/thread_pool_supervisor.rb +45 -0
- data/lib/farce/internal/unshared_pool_worker.rb +14 -0
- data/lib/farce/internal.rb +14 -0
- data/lib/farce/lazy.rb +4 -4
- data/lib/farce/lazy_ref.rb +3 -3
- data/lib/farce/lfu_map.rb +1 -1
- data/lib/farce/local/flag.rb +4 -0
- data/lib/farce/local/pool.rb +111 -0
- data/lib/farce/local/scheduler.rb +219 -0
- data/lib/farce/local/scoped.rb +1 -1
- data/lib/farce/lru_map.rb +1 -1
- data/lib/farce/map.rb +2 -2
- data/lib/farce/mode_manager/smart_rule.rb +138 -0
- data/lib/farce/mode_manager.rb +12 -3
- data/lib/farce/molecule.rb +2 -2
- data/lib/farce/pool.rb +2 -290
- data/lib/farce/port.rb +4 -4
- data/lib/farce/priority_queue.rb +1 -1
- data/lib/farce/proxy/register.rb +6 -0
- data/lib/farce/proxy/supervisor.rb +1 -1
- data/lib/farce/proxy.rb +9 -4
- data/lib/farce/queue.rb +1 -1
- data/lib/farce/scheduler.rb +1 -502
- data/lib/farce/set.rb +1 -1
- data/lib/farce/sorted_set.rb +1 -1
- data/lib/farce/task.rb +285 -0
- data/lib/farce/{thread_scheduler.rb → thread_executor.rb} +7 -7
- data/lib/farce/timer_queue.rb +20 -6
- data/lib/farce/transaction/vector.rb +43 -9
- data/lib/farce/transaction/wrapper.rb +14 -0
- data/lib/farce/tree_map.rb +1 -1
- data/lib/farce/unsafe/lfu_map.rb +2 -0
- data/lib/farce/unsafe/lru_map.rb +2 -0
- data/lib/farce/unsafe/tree_map.rb +2 -0
- data/lib/farce/unshareable.rb +8 -19
- data/lib/farce/unshared/atom.rb +3 -0
- data/lib/farce/unshared/lfu_map.rb +2 -0
- data/lib/farce/unshared/lru_map.rb +2 -0
- data/lib/farce/unshared/map.rb +3 -0
- data/lib/farce/unshared/pool.rb +44 -0
- data/lib/farce/unshared/scheduler.rb +89 -0
- data/lib/farce/unshared/set.rb +1 -0
- data/lib/farce/unshared/sorted_set.rb +1 -0
- data/lib/farce/unshared/tree_map.rb +5 -1
- data/lib/farce/unshared/vector.rb +3 -0
- data/lib/farce/unshared/weak_atom.rb +5 -1
- data/lib/farce/unshared/weak_key_map.rb +3 -0
- data/lib/farce/unshared/weak_map.rb +3 -0
- data/lib/farce/unshared/weak_set.rb +1 -0
- data/lib/farce/unshared/weak_value_map.rb +3 -0
- data/lib/farce/vector.rb +44 -19
- data/lib/farce/version.rb +1 -1
- data/lib/farce.rb +20 -25
- metadata +17 -2
data/lib/farce/config.rb
CHANGED
|
@@ -24,7 +24,7 @@ module Farce
|
|
|
24
24
|
class Config
|
|
25
25
|
# Automatically load integrations when their dependencies are loaded. Defaults to true.
|
|
26
26
|
# Can also be set via `FARCE_AUTOLOAD_INTEGRATIONS` (`true`/`false` or `1`/`0`).
|
|
27
|
-
# Set before requiring Farce to
|
|
27
|
+
# Set before requiring Farce to disable startup integration loading and require hooks.
|
|
28
28
|
# @return [Boolean]
|
|
29
29
|
attr_reader :autoload_integrations
|
|
30
30
|
|
|
@@ -63,6 +63,7 @@ module Farce
|
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# Enable or disable automatic integration loading. Already loaded integrations remain active.
|
|
66
|
+
# Enabling this option installs require hooks if they are not already installed.
|
|
66
67
|
# @param value [Boolean, String] true, false, or their environment variable representations.
|
|
67
68
|
# @raise [ArgumentError] If the value is not a supported boolean.
|
|
68
69
|
def autoload_integrations=(value)
|
|
@@ -71,6 +72,11 @@ module Farce
|
|
|
71
72
|
when false, "false", "0" then false
|
|
72
73
|
else raise ArgumentError, "autoload_integrations must be a boolean"
|
|
73
74
|
end
|
|
75
|
+
if @autoload_integrations && Farce.const_defined?(:Integrations, false) &&
|
|
76
|
+
!Farce.autoload?(:Integrations) && Farce.config.equal?(self)
|
|
77
|
+
Integrations.install_require_hooks
|
|
78
|
+
end
|
|
79
|
+
@autoload_integrations
|
|
74
80
|
end
|
|
75
81
|
|
|
76
82
|
# Default fiber scheduler for Pool and Scheduler.
|
|
@@ -48,10 +48,13 @@ module Farce
|
|
|
48
48
|
timeout = parse_timeout(timeout)
|
|
49
49
|
observed ||= generation
|
|
50
50
|
|
|
51
|
-
scheduler
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
# During shutdown JRuby can expose a scheduler to a blocking fiber.
|
|
52
|
+
unless Fiber.current.blocking?
|
|
53
|
+
scheduler = Fiber.current_scheduler if Fiber.respond_to?(:current_scheduler)
|
|
54
|
+
# JRuby may run a later scheduled fiber on a backing thread where this is nil.
|
|
55
|
+
scheduler ||= Fiber.scheduler if Fiber.respond_to?(:scheduler)
|
|
56
|
+
return wait_with_scheduler(scheduler, observed, timeout) { yield if block_given? } if scheduler
|
|
57
|
+
end
|
|
55
58
|
|
|
56
59
|
wait_with_phaser(observed, timeout) { yield if block_given? }
|
|
57
60
|
end
|
|
@@ -8,23 +8,30 @@ module Farce
|
|
|
8
8
|
# @!visibility private
|
|
9
9
|
module Internal # :nodoc: all
|
|
10
10
|
class Flag
|
|
11
|
+
include FlagWaiting
|
|
12
|
+
|
|
11
13
|
def initialize(value = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
12
14
|
raise "Flag is already initialized" if defined?(@reference)
|
|
13
15
|
check_frozen!
|
|
16
|
+
@signal = Signal.new
|
|
14
17
|
@reference = JVMContainers::AtomicBoolean.new(validate_boolean(value, "initial value"))
|
|
15
18
|
end
|
|
16
19
|
|
|
20
|
+
def change_signal = @signal
|
|
21
|
+
|
|
17
22
|
def value = @reference.get
|
|
18
23
|
|
|
19
24
|
def set # rubocop:disable Naming/PredicateMethod
|
|
20
25
|
check_frozen!
|
|
21
26
|
@reference.set(true)
|
|
27
|
+
@signal.broadcast
|
|
22
28
|
true
|
|
23
29
|
end
|
|
24
30
|
|
|
25
31
|
def unset # rubocop:disable Naming/PredicateMethod
|
|
26
32
|
check_frozen!
|
|
27
33
|
@reference.set(false)
|
|
34
|
+
@signal.broadcast
|
|
28
35
|
false
|
|
29
36
|
end
|
|
30
37
|
|
|
@@ -32,20 +39,21 @@ module Farce
|
|
|
32
39
|
check_frozen!
|
|
33
40
|
value = validate_boolean(value, "value")
|
|
34
41
|
@reference.set(value)
|
|
42
|
+
@signal.broadcast
|
|
35
43
|
value
|
|
36
44
|
end
|
|
37
45
|
|
|
38
46
|
def swap(value)
|
|
39
47
|
check_frozen!
|
|
40
48
|
value = validate_boolean(value, "value")
|
|
41
|
-
@reference.getAndSet(value)
|
|
49
|
+
@reference.getAndSet(value).tap { @signal.broadcast }
|
|
42
50
|
end
|
|
43
51
|
|
|
44
52
|
def compare_and_set(expected_value, replacement_value)
|
|
45
53
|
check_frozen!
|
|
46
54
|
expected_value = validate_boolean(expected_value, "expected value")
|
|
47
55
|
replacement_value = validate_boolean(replacement_value, "replacement value")
|
|
48
|
-
@reference.compareAndSet(expected_value, replacement_value)
|
|
56
|
+
@reference.compareAndSet(expected_value, replacement_value).tap { |changed| @signal.broadcast if changed }
|
|
49
57
|
end
|
|
50
58
|
|
|
51
59
|
def toggle
|
|
@@ -53,7 +61,10 @@ module Farce
|
|
|
53
61
|
loop do
|
|
54
62
|
current = @reference.get
|
|
55
63
|
replacement = !current
|
|
56
|
-
|
|
64
|
+
if @reference.compareAndSet(current, replacement)
|
|
65
|
+
@signal.broadcast
|
|
66
|
+
return replacement
|
|
67
|
+
end
|
|
57
68
|
end
|
|
58
69
|
end
|
|
59
70
|
|
|
@@ -72,6 +83,7 @@ module Farce
|
|
|
72
83
|
end
|
|
73
84
|
|
|
74
85
|
def initialize_copy(other)
|
|
86
|
+
@signal = Signal.new
|
|
75
87
|
@reference = JVMContainers::AtomicBoolean.new(other.value)
|
|
76
88
|
end
|
|
77
89
|
end
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
# Ruby 3.4.11 and 4.0 prepend a Ractor-aware require to Kernel on activation.
|
|
6
|
+
# Detect that wrapper so versions that install it on Object need no relocation.
|
|
7
|
+
# A later Kernel alias captures that wrapper and loses its super target.
|
|
8
|
+
# Keep the original implementation above Object so aliases capture Kernel's
|
|
9
|
+
# underlying require while secondary Ractors still use Ruby's loader service.
|
|
10
|
+
ractor_require = Kernel.ancestors.find { it.name == "<RactorRequire>" }
|
|
11
|
+
if ractor_require
|
|
12
|
+
replacement = Module.new
|
|
13
|
+
replacement.set_temporary_name("<FarceRactorRequire>")
|
|
14
|
+
replacement.define_method(:require, ractor_require.instance_method(:require))
|
|
15
|
+
replacement.send(:private, :require)
|
|
16
|
+
Object.prepend(replacement)
|
|
17
|
+
ractor_require.remove_method(:require)
|
|
18
|
+
end
|
data/lib/farce/engine/ruby.rb
CHANGED
|
@@ -14,6 +14,13 @@ module Farce
|
|
|
14
14
|
ENGINE_PATH = "farce/engine/ruby/#{RUBY_VERSION[/^\d+\.\d+/]}".freeze
|
|
15
15
|
private_constant :ENGINE_PATH
|
|
16
16
|
|
|
17
|
+
require "farce/engine/ruby/shared/require_hook"
|
|
18
|
+
# Older Ractor activation defines a public Object#require. Keep Ruby's
|
|
19
|
+
# ordinary private visibility even when integration hooks are disabled.
|
|
20
|
+
if Object.instance_method(:require).source_location&.first == "<internal:ractor>"
|
|
21
|
+
Object.class_eval { private :require }
|
|
22
|
+
end
|
|
23
|
+
|
|
17
24
|
begin
|
|
18
25
|
require "#{ENGINE_PATH}/rebind"
|
|
19
26
|
require "#{ENGINE_PATH}/farce"
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
module Farce
|
|
6
6
|
module Internal # :nodoc: all
|
|
7
|
-
class
|
|
7
|
+
class MainThreadExecutor < ThreadExecutor
|
|
8
8
|
# Enter the main emulated Ractor without starting another thread.
|
|
9
9
|
def execute(...) = FakeRactor.with_current(Ractor.main) { super }
|
|
10
10
|
def local?(wait: true) = Ractor.main? # rubocop:disable Lint/UnusedMethodArgument
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
MainScheduler =
|
|
13
|
+
MainScheduler = MainThreadExecutor.new do |*args, &block|
|
|
14
14
|
Thread.new do
|
|
15
15
|
ThreadGroup::Default.add(Thread.current)
|
|
16
16
|
block.call(*args)
|
|
@@ -17,7 +17,7 @@ module Farce
|
|
|
17
17
|
@value = value
|
|
18
18
|
@compare_by_identity = compare_by_identity
|
|
19
19
|
@mutex = Mutex.new
|
|
20
|
-
@signal =
|
|
20
|
+
@signal = Signal.new
|
|
21
21
|
@updating = false
|
|
22
22
|
@updating_fiber = nil
|
|
23
23
|
@updating_thread = nil
|
|
@@ -224,13 +224,14 @@ module Farce
|
|
|
224
224
|
end
|
|
225
225
|
|
|
226
226
|
def wait_for_signal(deadline)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
227
|
+
# Snapshot while holding the state lock so a change during registration is not missed.
|
|
228
|
+
generation = @signal.generation
|
|
229
|
+
begin
|
|
230
|
+
@mutex.unlock
|
|
231
|
+
ReservationWaiting.wait(@signal, generation, deadline)
|
|
232
|
+
ensure
|
|
233
|
+
@mutex.lock unless @mutex.owned?
|
|
234
|
+
end
|
|
234
235
|
end
|
|
235
236
|
|
|
236
237
|
def reject_update_wait!
|
|
@@ -252,6 +252,28 @@ module Farce
|
|
|
252
252
|
finish_update if reservation
|
|
253
253
|
end
|
|
254
254
|
|
|
255
|
+
# Yield private storage and publish it only after the block finishes.
|
|
256
|
+
# The yielded Array is never retained. Callbacks run outside the mutex.
|
|
257
|
+
def batch_update(timeout: nil)
|
|
258
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
259
|
+
check_frozen!
|
|
260
|
+
completed, values = reserve(timeout_deadline(timeout)) do
|
|
261
|
+
snapshot = @values.dup
|
|
262
|
+
@updating = true
|
|
263
|
+
snapshot
|
|
264
|
+
end
|
|
265
|
+
return false unless completed
|
|
266
|
+
yield values
|
|
267
|
+
replacement = values.dup
|
|
268
|
+
@mutex.synchronize do
|
|
269
|
+
check_frozen_locked!
|
|
270
|
+
@values = replacement
|
|
271
|
+
end
|
|
272
|
+
true
|
|
273
|
+
ensure
|
|
274
|
+
finish_update if completed
|
|
275
|
+
end
|
|
276
|
+
|
|
255
277
|
def wait_until_changed(index, expected, timeout: nil)
|
|
256
278
|
wait_for_value(convert_index(index), expected, timeout_deadline(timeout), non_nil: false)
|
|
257
279
|
end
|
|
@@ -272,11 +294,16 @@ module Farce
|
|
|
272
294
|
|
|
273
295
|
def with_available(deadline)
|
|
274
296
|
while true
|
|
275
|
-
generation
|
|
276
|
-
|
|
277
|
-
@updating
|
|
297
|
+
generation = nil
|
|
298
|
+
result = @mutex.synchronize do
|
|
299
|
+
if @updating
|
|
300
|
+
generation = @signal.generation
|
|
301
|
+
nil
|
|
302
|
+
else
|
|
303
|
+
[true, yield]
|
|
304
|
+
end
|
|
278
305
|
end
|
|
279
|
-
return
|
|
306
|
+
return result if result
|
|
280
307
|
return [false, nil] unless wait_for_signal(generation, deadline)
|
|
281
308
|
end
|
|
282
309
|
end
|
|
@@ -184,7 +184,20 @@ module Farce
|
|
|
184
184
|
|
|
185
185
|
def data_for(key) = Internal.garbage_collectable?(key) ? @data : @immediate
|
|
186
186
|
def key_reference(key) = UnsharedWeakMapWeakReference.for(key)
|
|
187
|
-
|
|
187
|
+
|
|
188
|
+
def registry_enumerator
|
|
189
|
+
entries = @entries
|
|
190
|
+
Enumerator.new do |yielder|
|
|
191
|
+
# Compaction can rehash a weak registry and repeat earlier entries.
|
|
192
|
+
# Tokens have no back-reference to cells, keys, or values.
|
|
193
|
+
seen = {}.compare_by_identity
|
|
194
|
+
entries.each_value do |entry|
|
|
195
|
+
next if seen.key?(entry.token)
|
|
196
|
+
seen[entry.token] = true
|
|
197
|
+
yielder << entry
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
188
201
|
|
|
189
202
|
def register(entry)
|
|
190
203
|
@registry_mutex.synchronize do
|
data/lib/farce/engine/shared.rb
CHANGED
|
@@ -13,6 +13,15 @@ module Farce
|
|
|
13
13
|
module Internal # :nodoc: all
|
|
14
14
|
include Autoloads["#{__dir__}/shared"]
|
|
15
15
|
|
|
16
|
+
# Keep thread-only scheduler workers and their tasks in their emulated Ractor.
|
|
17
|
+
def with_scheduler_thread_group
|
|
18
|
+
group = ThreadGroup.new
|
|
19
|
+
FakeRactor::GROUPS[group] = Ractor.current
|
|
20
|
+
yield group
|
|
21
|
+
ensure
|
|
22
|
+
FakeRactor::GROUPS.delete(group)
|
|
23
|
+
end
|
|
24
|
+
|
|
16
25
|
# @api private
|
|
17
26
|
# The shim's shareable? can delegate to a lazy or weak value and change its state.
|
|
18
27
|
# Snapshot metadata uses only structural properties on engines without Ractors.
|
|
@@ -6,23 +6,30 @@ module Farce
|
|
|
6
6
|
# @!visibility private
|
|
7
7
|
module Internal # :nodoc: all
|
|
8
8
|
class Flag
|
|
9
|
+
include FlagWaiting
|
|
10
|
+
|
|
9
11
|
def initialize(value = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
10
12
|
raise "Flag is already initialized" if defined?(@reference)
|
|
11
13
|
check_frozen!
|
|
14
|
+
@signal = Signal.new
|
|
12
15
|
@reference = TruffleRuby::AtomicReference.new(validate_boolean(value, "initial value"))
|
|
13
16
|
end
|
|
14
17
|
|
|
18
|
+
def change_signal = @signal
|
|
19
|
+
|
|
15
20
|
def value = @reference.get
|
|
16
21
|
|
|
17
22
|
def set # rubocop:disable Naming/PredicateMethod
|
|
18
23
|
check_frozen!
|
|
19
24
|
@reference.set(true)
|
|
25
|
+
@signal.broadcast
|
|
20
26
|
true
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
def unset # rubocop:disable Naming/PredicateMethod
|
|
24
30
|
check_frozen!
|
|
25
31
|
@reference.set(false)
|
|
32
|
+
@signal.broadcast
|
|
26
33
|
false
|
|
27
34
|
end
|
|
28
35
|
|
|
@@ -30,20 +37,21 @@ module Farce
|
|
|
30
37
|
check_frozen!
|
|
31
38
|
value = validate_boolean(value, "value")
|
|
32
39
|
@reference.set(value)
|
|
40
|
+
@signal.broadcast
|
|
33
41
|
value
|
|
34
42
|
end
|
|
35
43
|
|
|
36
44
|
def swap(value)
|
|
37
45
|
check_frozen!
|
|
38
46
|
value = validate_boolean(value, "value")
|
|
39
|
-
@reference.get_and_set(value)
|
|
47
|
+
@reference.get_and_set(value).tap { @signal.broadcast }
|
|
40
48
|
end
|
|
41
49
|
|
|
42
50
|
def compare_and_set(expected_value, replacement_value)
|
|
43
51
|
check_frozen!
|
|
44
52
|
expected_value = validate_boolean(expected_value, "expected value")
|
|
45
53
|
replacement_value = validate_boolean(replacement_value, "replacement value")
|
|
46
|
-
@reference.compare_and_set(expected_value, replacement_value)
|
|
54
|
+
@reference.compare_and_set(expected_value, replacement_value).tap { |changed| @signal.broadcast if changed }
|
|
47
55
|
end
|
|
48
56
|
|
|
49
57
|
def toggle
|
|
@@ -51,7 +59,10 @@ module Farce
|
|
|
51
59
|
loop do
|
|
52
60
|
current = @reference.get
|
|
53
61
|
replacement = !current
|
|
54
|
-
|
|
62
|
+
if @reference.compare_and_set(current, replacement)
|
|
63
|
+
@signal.broadcast
|
|
64
|
+
return replacement
|
|
65
|
+
end
|
|
55
66
|
end
|
|
56
67
|
end
|
|
57
68
|
|
|
@@ -70,6 +81,7 @@ module Farce
|
|
|
70
81
|
end
|
|
71
82
|
|
|
72
83
|
def initialize_copy(other)
|
|
84
|
+
@signal = Signal.new
|
|
73
85
|
@reference = TruffleRuby::AtomicReference.new(other.value)
|
|
74
86
|
end
|
|
75
87
|
end
|
data/lib/farce/envelope.rb
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
module Farce
|
|
6
6
|
# An envelope can be used to wrap an unshareable value in a shareable object.
|
|
7
7
|
#
|
|
8
|
-
# Once created, the envelope can be shared between Ractors
|
|
9
|
-
# envelope is opened.
|
|
8
|
+
# Once created, the envelope can be shared between Ractors. Its payload is retrieved when the envelope is opened.
|
|
10
9
|
#
|
|
11
10
|
# When a Ractor attempts to open an envelope, it attempts "claim" it. Some envelopes may only be claimed by a single
|
|
12
11
|
# Ractor, others can be claimed by any Ractor. Claims cannot be taken back.
|
|
@@ -111,6 +110,53 @@ module Farce
|
|
|
111
110
|
def vault_key = defined?(@vault_key) ? @vault_key : self
|
|
112
111
|
end
|
|
113
112
|
|
|
113
|
+
# An envelope that serializes its contents with Ruby's Marshal protocol.
|
|
114
|
+
# The snapshot is created immediately and reconstructed once per Ractor that opens it.
|
|
115
|
+
# Farce containers rebuild their storage from logical snapshots.
|
|
116
|
+
# Contents must support Marshal. Nested shareable values may be reconstructed too.
|
|
117
|
+
#
|
|
118
|
+
# @example Transfer an Unshared map
|
|
119
|
+
# source = Farce::Unshared::Map.new({ jobs: [] })
|
|
120
|
+
# envelope = Farce::Envelope.new(source, mode: :marshal)
|
|
121
|
+
# envelope.value[:jobs] << :ready
|
|
122
|
+
# source[:jobs] # => []
|
|
123
|
+
class Marshal < Farce::Envelope
|
|
124
|
+
include Copyable
|
|
125
|
+
|
|
126
|
+
# @param value [BasicObject] the value to serialize
|
|
127
|
+
# @param auto_unwrap [ModeManager, nil] the manager that may automatically open this envelope
|
|
128
|
+
# @raise [TypeError] if the value cannot be marshaled
|
|
129
|
+
def initialize(value, auto_unwrap = nil)
|
|
130
|
+
@dump = ::Marshal.dump(value).freeze
|
|
131
|
+
super
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @api private
|
|
135
|
+
def marshal_dump = [1, value, auto_unwrap]
|
|
136
|
+
|
|
137
|
+
# @api private
|
|
138
|
+
def marshal_load(data)
|
|
139
|
+
value, manager = Internal::MarshalSupport.payload(data, 2)
|
|
140
|
+
initialize(value, manager)
|
|
141
|
+
Internal::Storage.ractor[self] = value
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# (see Envelope#claim)
|
|
145
|
+
def claim = self
|
|
146
|
+
|
|
147
|
+
# (see Envelope#claimed?)
|
|
148
|
+
def claimed? = true
|
|
149
|
+
|
|
150
|
+
# (see Envelope#owned?)
|
|
151
|
+
def owned? = true
|
|
152
|
+
|
|
153
|
+
protected
|
|
154
|
+
|
|
155
|
+
# The bytes are created by this envelope from its caller's value.
|
|
156
|
+
# rubocop:disable-next Security/MarshalLoad
|
|
157
|
+
def retrieve = ::Marshal.load(@dump)
|
|
158
|
+
end
|
|
159
|
+
|
|
114
160
|
# An envelope that moves its contents. Can only be opened by a single Ractor.
|
|
115
161
|
# The value will be moved once when the envelope is created, and then once when the envelope is opened.
|
|
116
162
|
#
|
|
@@ -253,7 +299,7 @@ module Farce
|
|
|
253
299
|
#
|
|
254
300
|
# @overload new(value, mode:)
|
|
255
301
|
# @param value [BasicObject] The value to wrap in the envelope.
|
|
256
|
-
# @param mode [Symbol, nil] The type of envelope to create. Can be `:move`, `:copy`, or `:local`.
|
|
302
|
+
# @param mode [Symbol, nil] The type of envelope to create. Can be `:move`, `:copy`, `:marshal`, or `:local`.
|
|
257
303
|
# @return [Envelope] A new envelope wrapping the given value.
|
|
258
304
|
#
|
|
259
305
|
# @return [Envelope] A new envelope wrapping the given value.
|
|
@@ -265,10 +311,11 @@ module Farce
|
|
|
265
311
|
|
|
266
312
|
def self.subclass_for(move: false, local: false, mode: nil)
|
|
267
313
|
case mode
|
|
268
|
-
when :move
|
|
269
|
-
when :copy
|
|
270
|
-
when :
|
|
271
|
-
when
|
|
314
|
+
when :move then Move
|
|
315
|
+
when :copy then Copy
|
|
316
|
+
when :marshal then Marshal
|
|
317
|
+
when :local then Local
|
|
318
|
+
when nil then local ? Local : (move ? Move : Copy)
|
|
272
319
|
else raise ArgumentError, "invalid mode: #{mode.inspect}"
|
|
273
320
|
end
|
|
274
321
|
end
|
data/lib/farce/exchanger.rb
CHANGED
|
@@ -15,7 +15,7 @@ module Farce
|
|
|
15
15
|
|
|
16
16
|
# @!macro modes
|
|
17
17
|
# @param mode [Symbol] the default mode used to transfer values between Ractors
|
|
18
|
-
def initialize(mode: :
|
|
18
|
+
def initialize(mode: :smart)
|
|
19
19
|
@manager = ModeManager.new(mode:)
|
|
20
20
|
@exchanger = Internal::Exchanger.new
|
|
21
21
|
super()
|
data/lib/farce/flag.rb
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
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
9
|
|
|
10
10
|
module Farce
|
|
11
11
|
module Integrations
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
# @!group Date Integration
|
|
6
|
+
require "farce" unless defined?(Farce::ModeManager)
|
|
7
|
+
require "date" unless defined?(Date)
|
|
8
|
+
Farce::ModeManager::SmartRule.define(Date, :make_shareable)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# warn_indent: true
|
|
4
4
|
|
|
5
5
|
# @!group Dry Types Integration
|
|
6
|
-
require "farce"
|
|
6
|
+
require "farce" unless defined?(Farce::Abstract)
|
|
7
7
|
require "dry/types"
|
|
8
8
|
|
|
9
9
|
module Farce
|
|
@@ -70,7 +70,7 @@ module Farce
|
|
|
70
70
|
when :shared
|
|
71
71
|
return {} unless %i[vector map set atom].include?(kind)
|
|
72
72
|
|
|
73
|
-
{ mode: UNDEFINED.equal?(mode) ? :
|
|
73
|
+
{ mode: UNDEFINED.equal?(mode) ? :smart : mode }
|
|
74
74
|
when :local then { scope: UNDEFINED.equal?(scope) ? :ractor : scope }
|
|
75
75
|
else {}
|
|
76
76
|
end
|
|
@@ -101,8 +101,11 @@ module Farce
|
|
|
101
101
|
|
|
102
102
|
if variant == :shared
|
|
103
103
|
raise ArgumentError, "scope is only supported by the local variant" unless UNDEFINED.equal?(scope)
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
if %i[move smart_move].include?(mode)
|
|
105
|
+
raise ArgumentError, "mode #{mode.inspect} is not supported by dry-types constructors"
|
|
106
|
+
end
|
|
107
|
+
allowed = %i[smart smart_raise smart_copy copy local make_shareable raise shareable_copy]
|
|
108
|
+
return if UNDEFINED.equal?(mode) || allowed.include?(mode)
|
|
106
109
|
|
|
107
110
|
raise ArgumentError, "invalid mode: #{mode.inspect}"
|
|
108
111
|
end
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
# warn_indent: true
|
|
4
4
|
|
|
5
5
|
# @!group Oj Integration
|
|
6
|
-
require "farce"
|
|
7
|
-
require "oj"
|
|
8
|
-
|
|
9
|
-
require "json" unless [].respond_to?(:to_json)
|
|
6
|
+
require "farce" unless defined?(Farce::Abstract)
|
|
7
|
+
require "oj" unless defined?(Oj::VERSION)
|
|
8
|
+
require "json" unless [].respond_to?(:to_json)
|
|
10
9
|
require_relative "shared/to_json"
|
|
11
10
|
|
|
12
11
|
module Farce
|