julewire-ractor 1.0.1 → 1.1.0
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 +12 -0
- data/docs/bridge.md +23 -4
- data/julewire-ractor.gemspec +1 -1
- data/lib/julewire/ractor/bridge/bridge_thread.rb +2 -2
- data/lib/julewire/ractor/bridge/stats.rb +2 -1
- data/lib/julewire/ractor/bridge.rb +28 -12
- data/lib/julewire/ractor/child_stats.rb +14 -18
- data/lib/julewire/ractor/destination.rb +141 -102
- data/lib/julewire/ractor/destination_worker.rb +40 -45
- data/lib/julewire/ractor/fanout.rb +28 -21
- data/lib/julewire/ractor/port_lifecycle.rb +7 -1
- data/lib/julewire/ractor/remote_payload.rb +9 -14
- data/lib/julewire/ractor/remote_runtime.rb +42 -33
- data/lib/julewire/ractor/remote_serializer.rb +27 -0
- data/lib/julewire/ractor/remote_summary_record.rb +1 -1
- data/lib/julewire/ractor/reply_timeout_scheduler.rb +15 -12
- data/lib/julewire/ractor/version.rb +1 -1
- data/lib/julewire/ractor/worker_handshake.rb +28 -0
- data/lib/julewire/ractor.rb +2 -7
- metadata +6 -4
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
module Julewire
|
|
4
4
|
module Ractor
|
|
5
5
|
module RemotePayload
|
|
6
|
-
MISSING = Object.new.freeze
|
|
7
|
-
private_constant :MISSING
|
|
8
|
-
|
|
9
6
|
class << self
|
|
10
7
|
def extract(payload)
|
|
8
|
+
validate_hash!(payload)
|
|
11
9
|
{
|
|
12
|
-
input:
|
|
10
|
+
input: hash_value(payload, :input),
|
|
13
11
|
context: hash_value(payload, :context),
|
|
14
12
|
neutral: hash_value(payload, :neutral),
|
|
15
13
|
attributes: hash_value(payload, :attributes),
|
|
@@ -18,14 +16,6 @@ module Julewire
|
|
|
18
16
|
}
|
|
19
17
|
end
|
|
20
18
|
|
|
21
|
-
def input_value(payload)
|
|
22
|
-
value = Core::Integration::Values::Read.hash_value(payload, :input, default: MISSING)
|
|
23
|
-
return {} if value.equal?(MISSING)
|
|
24
|
-
return Core::Fields::FieldSet.deep_symbolize_owned_keys(value) if value.is_a?(Hash)
|
|
25
|
-
|
|
26
|
-
value
|
|
27
|
-
end
|
|
28
|
-
|
|
29
19
|
def scope_snapshot(scope_payload)
|
|
30
20
|
Core::Execution::ScopeSnapshot.new(
|
|
31
21
|
execution: hash_value(scope_payload, :execution),
|
|
@@ -37,8 +27,13 @@ module Julewire
|
|
|
37
27
|
end
|
|
38
28
|
|
|
39
29
|
def hash_value(hash, key)
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
validate_hash!(hash.fetch(key))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def validate_hash!(value)
|
|
36
|
+
Core::Integration::Protocol.validate_symbol_hash(value)
|
|
42
37
|
end
|
|
43
38
|
end
|
|
44
39
|
end
|
|
@@ -47,11 +47,22 @@ module Julewire
|
|
|
47
47
|
remote_emit(:emit_without_level, record, fields, &)
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def emit_integration(record, enforce_level:)
|
|
51
|
+
command = enforce_level ? :emit : :emit_without_level
|
|
52
|
+
input = Core::Records::BuildInput.validate_owned(record)
|
|
53
|
+
notify(command, payload: remote_emit_payload(input))
|
|
54
|
+
rescue StandardError => e
|
|
55
|
+
@child_stats.message_dropped(e)
|
|
56
|
+
end
|
|
57
|
+
|
|
50
58
|
def remote_emit(command, record, fields, &)
|
|
51
59
|
record = Core.emit_input(record, fields)
|
|
52
60
|
record = Core::Records::LazyEmitInput.call(record, &) if block_given?
|
|
53
|
-
|
|
54
|
-
|
|
61
|
+
input = Core::Records::BuildInput.normalize_public(record)
|
|
62
|
+
input = Core::Fields::FieldSet.deep_symbolize_keys(input)
|
|
63
|
+
notify(command, payload: remote_emit_payload(input))
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
@child_stats.message_dropped(e)
|
|
55
66
|
end
|
|
56
67
|
private :remote_emit
|
|
57
68
|
|
|
@@ -81,22 +92,22 @@ module Julewire
|
|
|
81
92
|
Core::ContextStore.reset_current!
|
|
82
93
|
end
|
|
83
94
|
|
|
95
|
+
def reset_facade! = reset!
|
|
96
|
+
|
|
84
97
|
def emit_summary_record(scope)
|
|
85
|
-
notify(:emit_record, payload:
|
|
98
|
+
notify(:emit_record, payload: serialize_remote(summary_record_input(scope)))
|
|
99
|
+
rescue StandardError => e
|
|
100
|
+
@child_stats.message_dropped(e)
|
|
86
101
|
end
|
|
87
102
|
|
|
88
103
|
private
|
|
89
104
|
|
|
90
105
|
def emit_non_standard_exception_summaries? = @emit_non_standard_exception_summaries
|
|
91
106
|
|
|
92
|
-
def summary_finalizer_failure
|
|
93
|
-
@summary_finalizer_failure ||= ->(_error) {}
|
|
94
|
-
end
|
|
95
|
-
|
|
96
107
|
def build_execution_boundary
|
|
97
108
|
Core::Execution::Boundary.new(
|
|
98
109
|
emit_summary_record: ->(scope) { emit_summary_record(scope) },
|
|
99
|
-
summary_finalizer_failure:
|
|
110
|
+
summary_finalizer_failure: nil,
|
|
100
111
|
emit_non_standard_exception_summaries: -> { emit_non_standard_exception_summaries? }
|
|
101
112
|
)
|
|
102
113
|
end
|
|
@@ -108,19 +119,23 @@ module Julewire
|
|
|
108
119
|
end
|
|
109
120
|
|
|
110
121
|
def remote_emit_payload(record)
|
|
111
|
-
|
|
112
|
-
input:
|
|
113
|
-
context: Core::
|
|
114
|
-
neutral: Core::
|
|
115
|
-
attributes: Core::
|
|
116
|
-
carry: Core::
|
|
117
|
-
scope:
|
|
118
|
-
|
|
122
|
+
serialize_remote(
|
|
123
|
+
input: record,
|
|
124
|
+
context: Core::ContextStore.current.context_hash,
|
|
125
|
+
neutral: Core::ContextStore.current.neutral_hash,
|
|
126
|
+
attributes: Core::ContextStore.current.attributes_hash,
|
|
127
|
+
carry: Core::ContextStore.current.carry_hash,
|
|
128
|
+
scope: scope_payload
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def serialize_remote(value)
|
|
133
|
+
RemoteSerializer.call(value)
|
|
119
134
|
end
|
|
120
135
|
|
|
121
136
|
def scope_payload
|
|
122
137
|
scope = Core::ContextStore.current.current_scope_or_snapshot
|
|
123
|
-
return
|
|
138
|
+
return empty_scope_payload unless scope
|
|
124
139
|
|
|
125
140
|
{
|
|
126
141
|
execution: scope.execution_hash,
|
|
@@ -131,31 +146,28 @@ module Julewire
|
|
|
131
146
|
}
|
|
132
147
|
end
|
|
133
148
|
|
|
149
|
+
def empty_scope_payload
|
|
150
|
+
{ execution: {}, neutral: {}, attributes: {}, carry: {}, labels: {} }
|
|
151
|
+
end
|
|
152
|
+
|
|
134
153
|
def notify(command, payload:)
|
|
135
154
|
@request_mutex.synchronize do
|
|
136
155
|
@port.send({ command: command, payload: payload })
|
|
137
156
|
end
|
|
138
157
|
@child_stats.message_sent
|
|
139
|
-
nil
|
|
140
|
-
rescue StandardError => e
|
|
141
|
-
@child_stats.message_dropped(e)
|
|
142
|
-
nil
|
|
143
158
|
end
|
|
144
159
|
|
|
145
160
|
def request(command, timeout:)
|
|
146
161
|
reply = ::Ractor::Port.new
|
|
147
|
-
waiting_for_reply = false
|
|
148
162
|
@request_mutex.synchronize do
|
|
149
163
|
@port.send({ command: command, payload: { timeout: timeout }, reply: reply })
|
|
150
164
|
end
|
|
151
165
|
@child_stats.request_sent
|
|
152
|
-
waiting_for_reply = true
|
|
153
166
|
wait_for_reply(reply, timeout)
|
|
154
167
|
rescue StandardError => e
|
|
155
168
|
@child_stats.request_failed(e)
|
|
156
|
-
nil
|
|
157
169
|
ensure
|
|
158
|
-
close_reply(reply)
|
|
170
|
+
close_reply(reply)
|
|
159
171
|
end
|
|
160
172
|
|
|
161
173
|
def effective_timeout(timeout)
|
|
@@ -163,20 +175,17 @@ module Julewire
|
|
|
163
175
|
end
|
|
164
176
|
|
|
165
177
|
def wait_for_reply(reply, timeout)
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
response = if timeout.nil?
|
|
179
|
+
reply.receive
|
|
180
|
+
else
|
|
181
|
+
@timeout_scheduler.with_timeout(reply, timeout: timeout) { reply.receive }
|
|
182
|
+
end
|
|
168
183
|
|
|
169
184
|
if response.equal?(REQUEST_TIMEOUT)
|
|
170
185
|
@child_stats.request_timed_out
|
|
171
|
-
nil
|
|
172
186
|
else
|
|
173
187
|
response
|
|
174
188
|
end
|
|
175
|
-
rescue StandardError
|
|
176
|
-
nil
|
|
177
|
-
ensure
|
|
178
|
-
@timeout_scheduler.cancel(timeout_token)
|
|
179
|
-
close_reply(reply)
|
|
180
189
|
end
|
|
181
190
|
|
|
182
191
|
def close_reply(reply)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Ractor
|
|
5
|
+
# Serializes cross-ractor values without changing the internal Symbol-key
|
|
6
|
+
# protocol into a JSON-shaped String-key protocol.
|
|
7
|
+
class RemoteSerializer < Core::Serialization::Serializer
|
|
8
|
+
def serialize(value)
|
|
9
|
+
Core::Integration::Protocol.validate_symbol_keys(value)
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def initialize(**)
|
|
16
|
+
super
|
|
17
|
+
@truncation_key = TRUNCATION_METADATA_KEY.to_sym
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def key_value(key) = super.to_sym
|
|
21
|
+
|
|
22
|
+
def truncation_metadata(fields) = super(fields, key_style: :symbol)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private_constant :RemoteSerializer
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -8,7 +8,7 @@ module Julewire
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def owned_summary_record_input
|
|
11
|
-
@owned_summary_record_input ||= Core::
|
|
11
|
+
@owned_summary_record_input ||= Core::Integration::Protocol.validate_symbol_keys(@record)
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -5,32 +5,35 @@ module Julewire
|
|
|
5
5
|
class ReplyTimeoutScheduler
|
|
6
6
|
THREAD_NAME = "julewire-ractor-reply-timeout"
|
|
7
7
|
|
|
8
|
-
# This stays stdlib-only because RemoteRuntime creates it inside a Ractor.
|
|
9
|
-
# Concurrent::ScheduledTask currently touches non-shareable concurrent-ruby
|
|
10
|
-
# constants from worker ractors.
|
|
11
|
-
|
|
12
8
|
def initialize(timeout_value:)
|
|
13
9
|
@timeout_value = timeout_value
|
|
14
|
-
@scheduler = Core::Scheduling::DeadlineScheduler.new(thread_name: THREAD_NAME, idle: :exit)
|
|
15
10
|
end
|
|
16
11
|
|
|
17
12
|
def schedule(reply, timeout:)
|
|
18
|
-
|
|
13
|
+
Thread.new(Float(timeout)) do |delay|
|
|
14
|
+
Thread.current.name = THREAD_NAME
|
|
15
|
+
sleep(delay)
|
|
16
|
+
send_timeout(reply)
|
|
17
|
+
end
|
|
19
18
|
end
|
|
20
19
|
|
|
21
|
-
def cancel(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@scheduler.cancel(token)
|
|
25
|
-
rescue StandardError
|
|
20
|
+
def cancel(thread)
|
|
21
|
+
thread&.kill
|
|
22
|
+
thread&.join
|
|
26
23
|
nil
|
|
27
24
|
end
|
|
28
25
|
|
|
26
|
+
def with_timeout(reply, timeout:)
|
|
27
|
+
token = schedule(reply, timeout: timeout)
|
|
28
|
+
yield
|
|
29
|
+
ensure
|
|
30
|
+
cancel(token)
|
|
31
|
+
end
|
|
32
|
+
|
|
29
33
|
private
|
|
30
34
|
|
|
31
35
|
def send_timeout(reply)
|
|
32
36
|
reply.send(@timeout_value)
|
|
33
|
-
nil
|
|
34
37
|
rescue StandardError
|
|
35
38
|
nil
|
|
36
39
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Julewire
|
|
4
|
+
module Ractor
|
|
5
|
+
class WorkerHandshake
|
|
6
|
+
DEFAULT_TIMEOUT = 1
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def receive(setup_port:, worker:, scheduler:, timeout: DEFAULT_TIMEOUT)
|
|
10
|
+
PortLifecycle.with_port do |timeout_port|
|
|
11
|
+
scheduler.with_timeout(timeout_port, timeout: timeout) do
|
|
12
|
+
selected, value = ::Ractor.select(setup_port, worker, timeout_port)
|
|
13
|
+
|
|
14
|
+
if selected.equal?(timeout_port)
|
|
15
|
+
raise Core::Error, "ractor destination worker did not start within #{timeout} seconds"
|
|
16
|
+
end
|
|
17
|
+
return value if selected.equal?(setup_port) && value.is_a?(::Ractor::Port)
|
|
18
|
+
|
|
19
|
+
raise ArgumentError, "ractor destination worker did not start"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private_constant :WorkerHandshake
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/julewire/ractor.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Julewire
|
|
|
7
7
|
module Ractor
|
|
8
8
|
class << self
|
|
9
9
|
def health
|
|
10
|
-
|
|
10
|
+
Bridge.health
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def child_stats
|
|
@@ -24,15 +24,10 @@ module Julewire
|
|
|
24
24
|
|
|
25
25
|
def enable_default_destination_workers!
|
|
26
26
|
Core::Destinations.register(:default) { |name:, **options| Destination.new(name: name, **options) }
|
|
27
|
-
nil
|
|
28
27
|
end
|
|
29
28
|
|
|
30
29
|
private
|
|
31
30
|
|
|
32
|
-
def bridge_health
|
|
33
|
-
Bridge.health
|
|
34
|
-
end
|
|
35
|
-
|
|
36
31
|
def child_runtime
|
|
37
32
|
runtime = Core::RuntimeLocator.current
|
|
38
33
|
runtime if runtime.respond_to?(:child_stats)
|
|
@@ -53,7 +48,7 @@ module Julewire
|
|
|
53
48
|
def ractor(*args, name: nil, &block)
|
|
54
49
|
raise ArgumentError, "block required" unless block
|
|
55
50
|
|
|
56
|
-
Ractor::Bridge.
|
|
51
|
+
Ractor::Bridge.start(args: args, name: name, runtime: Core::RuntimeLocator.current, &block)
|
|
57
52
|
end
|
|
58
53
|
end
|
|
59
54
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: julewire-ractor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexander Grebennik
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 1.0
|
|
18
|
+
version: 1.1.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 1.0
|
|
25
|
+
version: 1.1.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: zeitwerk
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -65,9 +65,11 @@ files:
|
|
|
65
65
|
- lib/julewire/ractor/port_lifecycle.rb
|
|
66
66
|
- lib/julewire/ractor/remote_payload.rb
|
|
67
67
|
- lib/julewire/ractor/remote_runtime.rb
|
|
68
|
+
- lib/julewire/ractor/remote_serializer.rb
|
|
68
69
|
- lib/julewire/ractor/remote_summary_record.rb
|
|
69
70
|
- lib/julewire/ractor/reply_timeout_scheduler.rb
|
|
70
71
|
- lib/julewire/ractor/version.rb
|
|
72
|
+
- lib/julewire/ractor/worker_handshake.rb
|
|
71
73
|
homepage: https://github.com/slbug/julewire
|
|
72
74
|
licenses:
|
|
73
75
|
- MIT
|
|
@@ -90,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
90
92
|
- !ruby/object:Gem::Version
|
|
91
93
|
version: '0'
|
|
92
94
|
requirements: []
|
|
93
|
-
rubygems_version: 4.0.
|
|
95
|
+
rubygems_version: 4.0.16
|
|
94
96
|
specification_version: 4
|
|
95
97
|
summary: Experimental Ractor bridge for julewire-core.
|
|
96
98
|
test_files: []
|