farce 0.1.0-aarch64-linux-gnu → 0.2.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 +80 -1
- data/README.md +72 -16
- data/docs/gems/dry-types.md +8 -8
- data/docs/modes.md +67 -10
- 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/flag.rb +73 -0
- data/lib/farce/abstract/queue.rb +4 -3
- data/lib/farce/abstract/scheduler.rb +3 -3
- 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/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/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/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/map_value_modes.rb +1 -1
- data/lib/farce/internal/scheduler_lifecycle.rb +1 -1
- 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/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 +1 -1
- 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 +7 -2
- data/lib/farce/queue.rb +1 -1
- data/lib/farce/scheduler.rb +2 -2
- data/lib/farce/set.rb +1 -1
- data/lib/farce/sorted_set.rb +1 -1
- 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/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 +11 -8
- metadata +5 -1
data/lib/farce/integrations.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Farce
|
|
|
10
10
|
active_support: %w[active_support active_support/core_ext].freeze,
|
|
11
11
|
bson: %w[bson].freeze,
|
|
12
12
|
cbor: %w[cbor].freeze,
|
|
13
|
+
date: %w[date].freeze,
|
|
13
14
|
dry_types: %w[dry/types].freeze,
|
|
14
15
|
concurrent: %w[concurrent/map concurrent/tvar concurrent].freeze,
|
|
15
16
|
json: %w[json].freeze,
|
|
@@ -61,28 +62,6 @@ module Farce
|
|
|
61
62
|
end
|
|
62
63
|
private_constant :RequireHook
|
|
63
64
|
|
|
64
|
-
# A prepended singleton hook can recurse when Zeitwerk aliases it. Preserve
|
|
65
|
-
# the original method explicitly instead of relying on super here.
|
|
66
|
-
class << Kernel
|
|
67
|
-
alias farce_original_require require
|
|
68
|
-
private :farce_original_require
|
|
69
|
-
|
|
70
|
-
# Load a feature and its integration through Kernel.require.
|
|
71
|
-
# @api private
|
|
72
|
-
# @param path [String, #to_path] The feature to load.
|
|
73
|
-
# @return [Boolean] Whether the requested feature was newly loaded.
|
|
74
|
-
def require(path, ...)
|
|
75
|
-
path = File.path(path)
|
|
76
|
-
result = Integrations.loading(path) { farce_original_require(path, ...) }
|
|
77
|
-
integration = PATHS[path.delete_suffix(".rb")]
|
|
78
|
-
if integration && !Integrations.loading?(integration) && Farce.config.autoload_integrations
|
|
79
|
-
integration_path = "farce/integrations/#{integration}"
|
|
80
|
-
Integrations.loading(integration_path) { farce_original_require(integration_path) }
|
|
81
|
-
end
|
|
82
|
-
result
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
65
|
private_constant :INTEGRATIONS, :PATHS
|
|
87
66
|
|
|
88
67
|
extend self
|
|
@@ -106,8 +85,39 @@ module Farce
|
|
|
106
85
|
# Activate integrations at startup when automatic loading is enabled.
|
|
107
86
|
# @api private
|
|
108
87
|
def setup
|
|
88
|
+
return unless Farce.config.autoload_integrations
|
|
89
|
+
install_require_hooks
|
|
90
|
+
load_active
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Install hooks once, including when automatic loading is enabled later.
|
|
94
|
+
# @api private
|
|
95
|
+
def install_require_hooks
|
|
96
|
+
return if Object.ancestors.include?(RequireHook)
|
|
97
|
+
# The instance hook handles ordinary require calls. The singleton alias
|
|
98
|
+
# chain handles Kernel.require, which bypasses Object's instance methods.
|
|
109
99
|
Object.prepend(RequireHook)
|
|
110
|
-
|
|
100
|
+
# A prepended singleton hook can recurse when Zeitwerk aliases it. Preserve
|
|
101
|
+
# the original method explicitly instead of relying on super here.
|
|
102
|
+
class << Kernel
|
|
103
|
+
alias farce_original_require require
|
|
104
|
+
private :farce_original_require
|
|
105
|
+
|
|
106
|
+
# Load a feature and its integration through Kernel.require.
|
|
107
|
+
# @api private
|
|
108
|
+
# @param path [String, #to_path] The feature to load.
|
|
109
|
+
# @return [Boolean] Whether the requested feature was newly loaded.
|
|
110
|
+
def require(path, ...)
|
|
111
|
+
path = File.path(path)
|
|
112
|
+
result = Integrations.loading(path) { farce_original_require(path, ...) }
|
|
113
|
+
integration = PATHS[path.delete_suffix(".rb")]
|
|
114
|
+
if integration && !Integrations.loading?(integration) && Farce.config.autoload_integrations
|
|
115
|
+
integration_path = "farce/integrations/#{integration}"
|
|
116
|
+
Integrations.loading(integration_path) { farce_original_require(integration_path) }
|
|
117
|
+
end
|
|
118
|
+
result
|
|
119
|
+
end
|
|
120
|
+
end
|
|
111
121
|
end
|
|
112
122
|
|
|
113
123
|
# Loads all integrations for gems that have already been loaded.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
# @!visibility private
|
|
7
|
+
module Internal # :nodoc: all
|
|
8
|
+
module FlagWaiting
|
|
9
|
+
# Wait until the current value differs from the expected value.
|
|
10
|
+
# @param expected [Boolean] the value to wait to change from
|
|
11
|
+
# @param timeout [Numeric, nil] the total seconds available
|
|
12
|
+
# @yield called when the timeout expires
|
|
13
|
+
# @return [Boolean, BasicObject, nil] the changed value or timeout fallback
|
|
14
|
+
# @raise [ArgumentError] if expected is not true or false
|
|
15
|
+
def wait_until_changed(expected, timeout: nil)
|
|
16
|
+
unless expected.equal?(true) || expected.equal?(false)
|
|
17
|
+
raise ArgumentError, "expected value must be true or false"
|
|
18
|
+
end
|
|
19
|
+
signal = change_signal
|
|
20
|
+
Internal.with_timeout(timeout) do |_, deadline|
|
|
21
|
+
observed = signal.generation
|
|
22
|
+
current = value
|
|
23
|
+
return current unless expected == current
|
|
24
|
+
break unless signal.wait(observed, timeout: Internal.remaining_timeout(deadline))
|
|
25
|
+
end
|
|
26
|
+
yield if block_given?
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
module Farce
|
|
6
6
|
module Internal
|
|
7
|
-
#
|
|
7
|
+
# Frozen-state policies for wrappers with separate backing storage.
|
|
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.
|
|
@@ -20,14 +20,14 @@ module Farce
|
|
|
20
20
|
raise FrozenError.new("can't modify frozen #{object.class}", receiver: object)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
#
|
|
23
|
+
# Freeze owned storage and the facade without freezing stored values.
|
|
24
24
|
module Delegated
|
|
25
25
|
def freeze
|
|
26
26
|
backend = freeze_backend
|
|
27
27
|
raise "#{self.class} is not initialized" unless backend
|
|
28
28
|
|
|
29
29
|
backend.freeze
|
|
30
|
-
self
|
|
30
|
+
Object.instance_method(:freeze).bind_call(self)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def frozen?
|
|
@@ -39,6 +39,11 @@ module Farce
|
|
|
39
39
|
|
|
40
40
|
def check_frozen! = Internal::Freeze.check(self)
|
|
41
41
|
|
|
42
|
+
def initialize_clone(other, freeze: nil)
|
|
43
|
+
super
|
|
44
|
+
self.freeze if is_a?(Farce::Unshareable) && (freeze.nil? ? other.frozen? : freeze)
|
|
45
|
+
end
|
|
46
|
+
|
|
42
47
|
def publish_shareable_copy(other, operation:, freeze: nil)
|
|
43
48
|
logically_frozen = operation == :clone && (freeze.nil? ? other.frozen? : freeze)
|
|
44
49
|
self.freeze if logically_frozen
|
|
@@ -18,7 +18,7 @@ module Farce
|
|
|
18
18
|
# @param compare_by_identity [Boolean] the default comparison mode for keys and values
|
|
19
19
|
# @param compare_keys_by_identity [Boolean] whether keys are compared by identity
|
|
20
20
|
# @param compare_values_by_identity [Boolean] whether values are compared by identity
|
|
21
|
-
def initialize(initial_mapping = nil, mode: :
|
|
21
|
+
def initialize(initial_mapping = nil, mode: :smart, normalize_keys: nil, compare_by_identity: false,
|
|
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
|
|
|
@@ -15,7 +15,7 @@ module Farce
|
|
|
15
15
|
module SchedulerLifecycle
|
|
16
16
|
include Farce::Unshareable
|
|
17
17
|
|
|
18
|
-
#
|
|
18
|
+
# A live scheduler must keep updating its own lifecycle state.
|
|
19
19
|
def self.prepended(scheduler)
|
|
20
20
|
scheduler.undef_method(:freeze)
|
|
21
21
|
end
|
data/lib/farce/lazy.rb
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
module Farce
|
|
6
6
|
# A ractor-shareable lazy value that is computed on demand.
|
|
7
7
|
# The factory runs once. Its result is transferred according to {#mode}.
|
|
8
|
-
#
|
|
8
|
+
# The default `:smart` mode prepares results using smart rules or dedicated proxies.
|
|
9
9
|
# The factory and its bound receiver must remain Ractor-shareable.
|
|
10
10
|
# Use {Strict::Lazy} for direct shareable results or {Unshared::Lazy} for local factories.
|
|
11
11
|
#
|
|
@@ -63,15 +63,15 @@ module Farce
|
|
|
63
63
|
include Shareable::Tracked
|
|
64
64
|
|
|
65
65
|
# @!macro modes
|
|
66
|
-
# @overload initialize(factory, mode: :
|
|
66
|
+
# @overload initialize(factory, mode: :smart)
|
|
67
67
|
# @param factory [Class, Proc, #call] the shareable factory for the value
|
|
68
68
|
# @param mode [Symbol] the transfer mode for the computed result
|
|
69
|
-
# @overload initialize(mode: :
|
|
69
|
+
# @overload initialize(mode: :smart, self: nil)
|
|
70
70
|
# @param mode [Symbol] the transfer mode for the computed result
|
|
71
71
|
# @param self [BasicObject] the shareable receiver to bind to the block
|
|
72
72
|
# @yield computes the value on first access
|
|
73
73
|
# @yieldreturn [BasicObject] the result to transfer
|
|
74
|
-
def initialize(factory = nil, mode: :
|
|
74
|
+
def initialize(factory = nil, mode: :smart, **, &)
|
|
75
75
|
@manager = marshal_mode_manager(mode)
|
|
76
76
|
super(factory, **, &)
|
|
77
77
|
end
|
data/lib/farce/lazy_ref.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Farce
|
|
|
6
6
|
# @!parse
|
|
7
7
|
# # A {Reference} for a {Lazy} value.
|
|
8
8
|
# #
|
|
9
|
-
# # Results use the lazy value's transfer mode, which defaults to `:
|
|
9
|
+
# # Results use the lazy value's transfer mode, which defaults to `:smart`.
|
|
10
10
|
# #
|
|
11
11
|
# # Calling `freeze` resolves and freezes the target. Checking `frozen?` before
|
|
12
12
|
# # the first successful freeze does not resolve it.
|
|
@@ -24,11 +24,11 @@ module Farce
|
|
|
24
24
|
# # initialized.value # => 1
|
|
25
25
|
# class LazyRef < Farce::Reference
|
|
26
26
|
# # @!macro modes
|
|
27
|
-
# # @overload initialize(factory, mode: :
|
|
27
|
+
# # @overload initialize(factory, mode: :smart)
|
|
28
28
|
# # @param mode [Symbol] the transfer mode for the computed result
|
|
29
29
|
# # @param [Class, Proc, #call] factory The factory to use for creating the value. Must be ractor-shareable.
|
|
30
30
|
# #
|
|
31
|
-
# # @overload initialize(self: nil, mode: :
|
|
31
|
+
# # @overload initialize(self: nil, mode: :smart)
|
|
32
32
|
# # @param mode [Symbol] the transfer mode for the computed result
|
|
33
33
|
# # @yield The block to use for creating the value. Must be ractor-shareable.
|
|
34
34
|
# # @yieldreceiver [BasicObject] The `self` parameter provided, or `nil` if not provided.
|
data/lib/farce/lfu_map.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Farce
|
|
|
13
13
|
# @param entries [Hash, Array<Array(BasicObject, BasicObject)>, Abstract::Map, #each, nil]
|
|
14
14
|
# Optional initial entries. Entries are stored sequentially and may be evicted.
|
|
15
15
|
# @param mode [Symbol] The default value transfer mode.
|
|
16
|
-
def initialize(entries = nil, mode: :
|
|
16
|
+
def initialize(entries = nil, mode: :smart, **)
|
|
17
17
|
@manager = marshal_mode_manager(mode)
|
|
18
18
|
super(entries, **)
|
|
19
19
|
end
|
data/lib/farce/local/flag.rb
CHANGED
|
@@ -35,6 +35,9 @@ module Farce
|
|
|
35
35
|
# @return [Boolean] the new value
|
|
36
36
|
def store(value) = scoped_value.store(value)
|
|
37
37
|
|
|
38
|
+
# (see Abstract::Flag#wait_until_changed)
|
|
39
|
+
def wait_until_changed(...) = scoped_value.wait_until_changed(...)
|
|
40
|
+
|
|
38
41
|
alias value= store
|
|
39
42
|
|
|
40
43
|
# Replace the value and return its previous state.
|
|
@@ -60,6 +63,7 @@ module Farce
|
|
|
60
63
|
end
|
|
61
64
|
|
|
62
65
|
def new_scoped_value(...) = Internal::Flag.new(...)
|
|
66
|
+
def change_signal = scoped_value.change_signal
|
|
63
67
|
end
|
|
64
68
|
end
|
|
65
69
|
end
|
data/lib/farce/local/scoped.rb
CHANGED
data/lib/farce/lru_map.rb
CHANGED
|
@@ -23,7 +23,7 @@ module Farce
|
|
|
23
23
|
# @param entries [Hash, Array<Array(BasicObject, BasicObject)>, Abstract::Map, #each, nil]
|
|
24
24
|
# Optional initial entries. Entries are stored sequentially and may be evicted.
|
|
25
25
|
# @param mode [Symbol] The default value transfer mode.
|
|
26
|
-
def initialize(entries = nil, mode: :
|
|
26
|
+
def initialize(entries = nil, mode: :smart, **)
|
|
27
27
|
@manager = marshal_mode_manager(mode)
|
|
28
28
|
super(entries, **)
|
|
29
29
|
end
|
data/lib/farce/map.rb
CHANGED
|
@@ -30,11 +30,11 @@ module Farce
|
|
|
30
30
|
# class Map
|
|
31
31
|
# # @!macro map_initialization
|
|
32
32
|
# # @!macro modes
|
|
33
|
-
# # @param mode [Symbol] The default transfer mode for values. Defaults to `:
|
|
33
|
+
# # @param mode [Symbol] The default transfer mode for values. Defaults to `:smart`.
|
|
34
34
|
# # @return [Map]
|
|
35
35
|
# def initialize(
|
|
36
36
|
# initial_mapping = nil,
|
|
37
|
-
# mode: :
|
|
37
|
+
# mode: :smart,
|
|
38
38
|
# normalize_keys: nil,
|
|
39
39
|
# compare_by_identity: false,
|
|
40
40
|
# compare_keys_by_identity: compare_by_identity,
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
4
|
+
|
|
5
|
+
module Farce
|
|
6
|
+
class ModeManager
|
|
7
|
+
# Class for converting values based on smart rules.
|
|
8
|
+
class SmartRule
|
|
9
|
+
SMART_MODES = %i[smart smart_raise smart_move smart_copy].freeze
|
|
10
|
+
private_constant :SMART_MODES
|
|
11
|
+
|
|
12
|
+
include Shareable::Immutable
|
|
13
|
+
|
|
14
|
+
# @note This method must be called from the main ractor.
|
|
15
|
+
# Allows you to add rules for smart wrapping of specific classes.
|
|
16
|
+
#
|
|
17
|
+
# @overload define(klass, mode = nil)
|
|
18
|
+
# @example
|
|
19
|
+
# # Have smart modes use the :make_shareable mode for instances of MyClass
|
|
20
|
+
# Farce::ModeManager::SmartRule.define(MyClass, :make_shareable)
|
|
21
|
+
#
|
|
22
|
+
# @param klass [Class] the class to define a smart wrapping rule for
|
|
23
|
+
# @param mode [Symbol] the mode to use when wrapping instances of the class
|
|
24
|
+
#
|
|
25
|
+
# @overload define(klass)
|
|
26
|
+
# @example
|
|
27
|
+
# # Have smart modes use a custom block for instances of MyClass
|
|
28
|
+
# Farce::ModeManager::SmartRule.define(MyClass) do |value, mode_manager|
|
|
29
|
+
# raise Ractor::Error, "#{value.inspect} cannot be shared" if value.dont_share?
|
|
30
|
+
# mode_manager.wrap(value, mode: :make_shareable)
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# @param klass [Class] the class to define a smart wrapping rule for
|
|
34
|
+
# @yield [value, mode_manager, mode] The block will be called when wrapping an instance of klass.
|
|
35
|
+
# @yieldparam value [BasicObject] The value being wrapped.
|
|
36
|
+
# @yieldparam mode_manager [Farce::ModeManager] The mode manager instance.
|
|
37
|
+
# @yieldparam mode [Symbol] The mode to use when wrapping the value, like `:smart` or `:smart_copy`.
|
|
38
|
+
# @yieldreturn [BasicObject] The shareable version of the value.
|
|
39
|
+
#
|
|
40
|
+
# @overload define(klass)
|
|
41
|
+
# @example
|
|
42
|
+
# Farce::ModeManager::SmartRule.define(MyClass) do
|
|
43
|
+
# def smart_copy(value, mode_manager) = mode_manager.wrap(value, mode: :copy)
|
|
44
|
+
# def smart_move(value, mode_manager) = mode_manager.wrap(value, mode: :move)
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# @yield block used for defining different methods for different smart modes.
|
|
48
|
+
# @yieldreceiver [Class] The {SmartRule} subclass.
|
|
49
|
+
#
|
|
50
|
+
# @return [Class] the defined smart rule class.
|
|
51
|
+
def self.define(klass, mode = nil, &block)
|
|
52
|
+
SMART_RULES.define(klass) do
|
|
53
|
+
if mode
|
|
54
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
|
55
|
+
def call(value, mode_manager, _) = mode_manager.wrap(value, mode: #{mode.inspect})
|
|
56
|
+
RUBY
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if block_given?
|
|
60
|
+
if block.arity.zero?
|
|
61
|
+
class_eval(&block)
|
|
62
|
+
SMART_MODES.each { private it if method_defined?(it) }
|
|
63
|
+
else
|
|
64
|
+
define_method(:call, &Ractor.shareable_lambda(&block))
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @api private
|
|
71
|
+
def call(value, mode_manager, mode)
|
|
72
|
+
register = mode_manager.register || Proxy.default_register
|
|
73
|
+
return Proxy.new(value, register:) if register.dedicated_proxy?(value.class)
|
|
74
|
+
__send__(mode, value, mode_manager)
|
|
75
|
+
rescue TypeError => e
|
|
76
|
+
raise unless e.message.include?("allocator undefined for")
|
|
77
|
+
raise Ractor::IsolationError, "#{value.inspect} cannot be shared"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @api private
|
|
81
|
+
def marshal_dump = raise TypeError, "smart rules cannot be marshaled"
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
# smart is intentionally not an alias
|
|
86
|
+
def smart(value, mode_manager) = smart_raise(value, mode_manager)
|
|
87
|
+
def smart_raise(value, _) = raise Ractor::IsolationError, "#{value.inspect} cannot be shared"
|
|
88
|
+
def smart_move(value, mode_manager) = mode_manager.wrap(value, mode: :move)
|
|
89
|
+
|
|
90
|
+
def smart_copy(value, mode_manager)
|
|
91
|
+
mode_manager.wrap(value, mode: :copy)
|
|
92
|
+
rescue StandardError => e
|
|
93
|
+
begin
|
|
94
|
+
mode_manager.wrap(value, mode: :marshal)
|
|
95
|
+
rescue StandardError
|
|
96
|
+
raise e, cause: e.cause
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
SMART_RULES = ClassMirror.new(SmartRule) { |klass, _| klass.new }
|
|
102
|
+
private_constant :SMART_RULES
|
|
103
|
+
|
|
104
|
+
SmartRule.define(Time, :make_shareable)
|
|
105
|
+
|
|
106
|
+
[Array, ::Set].each do |klass|
|
|
107
|
+
SmartRule.define(klass) do
|
|
108
|
+
def smart_copy(value, mode_manager)
|
|
109
|
+
all_shareable = true
|
|
110
|
+
mapped = value.dup.map! do |entry|
|
|
111
|
+
result = mode_manager.wrap(entry, mode: :smart_raise)
|
|
112
|
+
if mode_manager.managed_envelope?(result)
|
|
113
|
+
all_shareable = false
|
|
114
|
+
entry
|
|
115
|
+
else
|
|
116
|
+
result
|
|
117
|
+
end
|
|
118
|
+
rescue StandardError
|
|
119
|
+
all_shareable = false
|
|
120
|
+
entry
|
|
121
|
+
end
|
|
122
|
+
return super unless all_shareable
|
|
123
|
+
Ractor.make_shareable(mapped)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def smart(value, mode_manager)
|
|
127
|
+
return super unless value.all? { Ractor.shareable?(it) }
|
|
128
|
+
Ractor.make_shareable(value)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
SmartRule.define(Hash) do |value, mode_manager, mode|
|
|
134
|
+
return super(value, mode_manager, mode) unless value.all? { _1.is_a?(Symbol) && Ractor.shareable?(_2) }
|
|
135
|
+
Ractor.make_shareable(value)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
data/lib/farce/mode_manager.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
# shareable_constant_value: literal
|
|
3
3
|
# warn_indent: true
|
|
4
4
|
|
|
5
|
+
require "farce/mode_manager/smart_rule"
|
|
6
|
+
|
|
5
7
|
module Farce
|
|
6
8
|
# A mode manager encapsulates logic for dealing with Ruby objects that aren't Ractor-shareable.
|
|
7
9
|
# It is a perfect companion piece for APIs that require Ractor-shareable objects.
|
|
@@ -17,7 +19,7 @@ module Farce
|
|
|
17
19
|
# class MyQueue
|
|
18
20
|
# include Farce::Shareable
|
|
19
21
|
#
|
|
20
|
-
# def initialize(capacity: 1024, mode: :
|
|
22
|
+
# def initialize(capacity: 1024, mode: :smart)
|
|
21
23
|
# @queue = Ratomic::Queue.new(capacity)
|
|
22
24
|
# @manager = Farce::ModeManager.new(mode:)
|
|
23
25
|
# end
|
|
@@ -55,10 +57,13 @@ module Farce
|
|
|
55
57
|
# @return [Symbol] The default mode to use when wrapping values.
|
|
56
58
|
attr_reader :mode
|
|
57
59
|
|
|
60
|
+
# @return [Farce::Proxy::Register, nil] The register used for generating Farce::Proxy instances.
|
|
61
|
+
attr_reader :register
|
|
62
|
+
|
|
58
63
|
# @!macro modes
|
|
59
64
|
# @param mode [Symbol] The default mode to use when wrapping values. Must be one of the valid modes.
|
|
60
65
|
# @param register [Proxy::Register, nil] The register to use for generating Farce::Proxy instances.
|
|
61
|
-
def initialize(mode: :
|
|
66
|
+
def initialize(mode: :smart, register: nil)
|
|
62
67
|
raise ArgumentError, "invalid mode: #{mode.inspect}" unless Farce::MODES.include?(mode)
|
|
63
68
|
@mode = mode
|
|
64
69
|
@register = register
|
|
@@ -87,7 +92,10 @@ module Farce
|
|
|
87
92
|
def wrap(value, mode: nil)
|
|
88
93
|
return value if Ractor.shareable?(value)
|
|
89
94
|
|
|
90
|
-
case mode
|
|
95
|
+
case mode ||= self.mode
|
|
96
|
+
when :smart, :smart_copy, :smart_move, :smart_raise
|
|
97
|
+
return Ractor.make_shareable(-value) if value.is_a?(String)
|
|
98
|
+
SMART_RULES[value.class].call(value, self, mode)
|
|
91
99
|
when :copy then Envelope::Copy.new(value, self)
|
|
92
100
|
when :dedup then Ractor.make_shareable(Farce.dedup(value))
|
|
93
101
|
when :local then Envelope::Local.new(value, self)
|
|
@@ -97,6 +105,7 @@ module Farce
|
|
|
97
105
|
when :raise then raise Ractor::IsolationError, "value is not Ractor-shareable: #{value.inspect}"
|
|
98
106
|
when :shareable_copy then Ractor.make_shareable(value, copy: true)
|
|
99
107
|
when :proxy then Proxy.new(value, register: @register)
|
|
108
|
+
when :marshal then Envelope::Marshal.new(value, self)
|
|
100
109
|
else raise ArgumentError, "invalid mode: #{mode.inspect}"
|
|
101
110
|
end
|
|
102
111
|
end
|
data/lib/farce/molecule.rb
CHANGED
|
@@ -21,7 +21,7 @@ module Farce
|
|
|
21
21
|
# Create a record class with defaults for newly created atoms.
|
|
22
22
|
# @!macro modes
|
|
23
23
|
# @param fields [Array<Symbol, String>] the field names
|
|
24
|
-
# @param mode [Symbol, nil] the default transfer mode, or nil for :
|
|
24
|
+
# @param mode [Symbol, nil] the default transfer mode, or nil for :smart
|
|
25
25
|
# @param compare_by_identity [Boolean, nil] the default comparison policy
|
|
26
26
|
# @return [Class<Molecule>]
|
|
27
27
|
def self.define(*fields, mode: nil, **, &)
|
|
@@ -32,7 +32,7 @@ module Farce
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# @return [Symbol] the default transfer mode for new records
|
|
35
|
-
def self.default_mode = :
|
|
35
|
+
def self.default_mode = :smart
|
|
36
36
|
|
|
37
37
|
# @!visibility private
|
|
38
38
|
def self.define_member(field)
|
data/lib/farce/pool.rb
CHANGED
|
@@ -111,7 +111,7 @@ module Farce
|
|
|
111
111
|
# @yield [*args] Task to execute.
|
|
112
112
|
# @return [Pool] self.
|
|
113
113
|
# @raise [PoolClosedError] If the pool is closing or closed.
|
|
114
|
-
def schedule(*args, mode: :
|
|
114
|
+
def schedule(*args, mode: :smart, auto_local: true, &block) # rubocop:disable Lint/UnusedMethodArgument
|
|
115
115
|
raise PoolClosedError, "cannot schedule task on a closed pool" if closed?
|
|
116
116
|
raise ArgumentError, "local transfer is not supported by a pool" if mode == :local
|
|
117
117
|
|
data/lib/farce/port.rb
CHANGED
|
@@ -12,9 +12,9 @@ module Farce
|
|
|
12
12
|
class Port < Internal::Port
|
|
13
13
|
include Abstract::Port
|
|
14
14
|
|
|
15
|
-
MANAGER = ModeManager.new
|
|
15
|
+
MANAGER = ModeManager.new(mode: :smart_copy)
|
|
16
16
|
SUBCLASSES = Farce::MODES.to_h do |mode|
|
|
17
|
-
if mode == :
|
|
17
|
+
if mode == :smart_copy
|
|
18
18
|
normal = self
|
|
19
19
|
else
|
|
20
20
|
normal = Class.new(self)
|
|
@@ -41,7 +41,7 @@ module Farce
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# @api private
|
|
44
|
-
def self.mode = :
|
|
44
|
+
def self.mode = :smart_copy
|
|
45
45
|
|
|
46
46
|
# @api private
|
|
47
47
|
def self.auto_local? = false
|
|
@@ -54,7 +54,7 @@ module Farce
|
|
|
54
54
|
self[mode || self.mode, auto_local: auto_local].new
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
# @!method initialize(mode: :
|
|
57
|
+
# @!method initialize(mode: :smart_copy, auto_local: false)
|
|
58
58
|
# Creates a new port with the given default mode.
|
|
59
59
|
#
|
|
60
60
|
# @!macro modes
|
data/lib/farce/priority_queue.rb
CHANGED
|
@@ -11,7 +11,7 @@ module Farce
|
|
|
11
11
|
# (see Farce::Abstract::PriorityQueue#initialize)
|
|
12
12
|
# @!macro modes
|
|
13
13
|
# @param mode [Symbol] the default mode for sending unshareable values
|
|
14
|
-
def initialize(capacity: nil, default_priority: 0, order: :ascending, mode: :
|
|
14
|
+
def initialize(capacity: nil, default_priority: 0, order: :ascending, mode: :smart, track_age: false)
|
|
15
15
|
@manager = ModeManager.new(mode:)
|
|
16
16
|
super(capacity:, default_priority:, order:, track_age:)
|
|
17
17
|
end
|
data/lib/farce/proxy/register.rb
CHANGED
|
@@ -87,6 +87,12 @@ module Farce
|
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
# Check whether this exact class has a definition in either proxy layer.
|
|
91
|
+
# Inherited wrapper methods do not count as a dedicated proxy.
|
|
92
|
+
# @param klass [Class] the source class
|
|
93
|
+
# @return [Boolean] whether the class has a dedicated proxy
|
|
94
|
+
def dedicated_proxy?(klass) = @inner_mirror.custom?(klass) || @outer_mirror.custom?(klass)
|
|
95
|
+
|
|
90
96
|
private
|
|
91
97
|
|
|
92
98
|
def mirror_for(layer)
|
|
@@ -120,7 +120,7 @@ module Farce
|
|
|
120
120
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
121
121
|
request.success.value = false
|
|
122
122
|
begin
|
|
123
|
-
result = @mode_manager.wrap(e)
|
|
123
|
+
result = @mode_manager.wrap(e, mode: :copy)
|
|
124
124
|
rescue StandardError
|
|
125
125
|
result = -"#{e.class}: #{e.message}"
|
|
126
126
|
end
|
data/lib/farce/proxy.rb
CHANGED
|
@@ -50,13 +50,18 @@ module Farce
|
|
|
50
50
|
# ```
|
|
51
51
|
#
|
|
52
52
|
# The above example has one issue:
|
|
53
|
-
#
|
|
53
|
+
# By default, `<<` makes the returned array shareable and freezes it.
|
|
54
|
+
# The next mutation raises `FrozenError`.
|
|
54
55
|
#
|
|
55
56
|
# ```ruby
|
|
56
57
|
# my_instance = MyClass.new
|
|
57
58
|
# my_proxy = Farce::Proxy.create(my_instance)
|
|
58
59
|
#
|
|
59
|
-
# Ractor.new(my_proxy)
|
|
60
|
+
# Ractor.new(my_proxy) do |proxy|
|
|
61
|
+
# proxy << 42 << 43
|
|
62
|
+
# rescue FrozenError
|
|
63
|
+
# :frozen_result
|
|
64
|
+
# end.join
|
|
60
65
|
#
|
|
61
66
|
# my_instance.record # => [42]
|
|
62
67
|
# ```
|
data/lib/farce/queue.rb
CHANGED
|
@@ -11,7 +11,7 @@ module Farce
|
|
|
11
11
|
# (see Farce::Abstract::Queue#initialize)
|
|
12
12
|
# @!macro modes
|
|
13
13
|
# @param mode [Symbol] the default mode for sending unshareable values
|
|
14
|
-
def initialize(capacity: 1024, mode: :
|
|
14
|
+
def initialize(capacity: 1024, mode: :smart, track_age: false)
|
|
15
15
|
@manager = ModeManager.new(mode:)
|
|
16
16
|
@queue = Internal::Queue.new(capacity:, track_age:)
|
|
17
17
|
super()
|
data/lib/farce/scheduler.rb
CHANGED
|
@@ -326,14 +326,14 @@ module Farce
|
|
|
326
326
|
#
|
|
327
327
|
# @param args [Array<Object>] Positional arguments passed to the task block.
|
|
328
328
|
# @param mode [Symbol]
|
|
329
|
-
# Argument transfer mode
|
|
329
|
+
# Argument transfer mode from {Farce::MODES}, including `:copy`, `:marshal`, and `:local`.
|
|
330
330
|
# See {ModeManager#wrap}.
|
|
331
331
|
# @param auto_local [Boolean] Whether to override mode with `:local` in the owning Ractor.
|
|
332
332
|
# @yield [*args] The task to execute.
|
|
333
333
|
# @return [Scheduler] self.
|
|
334
334
|
# @raise [SchedulerClosedError] If the handle is closing, closed, or in an error state.
|
|
335
335
|
# @see Ractor.shareable_proc
|
|
336
|
-
def schedule(*args, mode: :
|
|
336
|
+
def schedule(*args, mode: :smart, auto_local: true, &block)
|
|
337
337
|
raise SchedulerClosedError, "cannot schedule task on a closed scheduler" if closed?
|
|
338
338
|
|
|
339
339
|
if auto_local
|
data/lib/farce/set.rb
CHANGED