omq-ractor 0.1.2 → 0.1.3
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/lib/omq/ractor.rb +85 -5
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4672a215899078075ce99ebd8fdff3b985803dee92e0e17577f9748f2e2a89d0
|
|
4
|
+
data.tar.gz: 7ca1e9d1cc2470901b2bd17c8a1ef8f4db19e08cb118cde74415c350c06e5f04
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9702ec46b247fadeecedec98a4786992bfc4c47dffb01af09643514ffa835ca2747ae013b3a5e7bab5062d07997c4362a267ae4f8ab3c396f5fc8fa8a8cc63e
|
|
7
|
+
data.tar.gz: 90e3ed42c28560345f9171612029ed85d0f54b67b4ea9e2890da94b28797c4c2855f8b044b7f2c84968bddc974f870e545069a0803ac0c663f50aa884c78ea88
|
data/lib/omq/ractor.rb
CHANGED
|
@@ -33,6 +33,7 @@ module OMQ
|
|
|
33
33
|
|
|
34
34
|
HANDSHAKE_TIMEOUT = 0.1
|
|
35
35
|
|
|
36
|
+
|
|
36
37
|
# Socket types that use topic/group-based routing.
|
|
37
38
|
# These get topic-aware connection wrappers that preserve
|
|
38
39
|
# the first frame (topic/group) as a plain string for matching.
|
|
@@ -45,6 +46,8 @@ module OMQ
|
|
|
45
46
|
# the wrapped class (e.g. DirectPipe) still work.
|
|
46
47
|
#
|
|
47
48
|
module TransparentDelegator
|
|
49
|
+
# @param klass [Class] class to check against
|
|
50
|
+
# @return [Boolean] true if self or the wrapped object is_a? klass
|
|
48
51
|
def is_a?(klass)
|
|
49
52
|
super || __getobj__.is_a?(klass)
|
|
50
53
|
end
|
|
@@ -56,11 +59,18 @@ module OMQ
|
|
|
56
59
|
# The send pump is single-threaded, so identity check suffices.
|
|
57
60
|
#
|
|
58
61
|
class SerializeCache
|
|
62
|
+
# @return [SerializeCache]
|
|
59
63
|
def initialize
|
|
60
64
|
@last = nil
|
|
61
65
|
@bytes = nil
|
|
62
66
|
end
|
|
63
67
|
|
|
68
|
+
|
|
69
|
+
# Returns the Marshal-dumped bytes for +obj+, reusing the cached result
|
|
70
|
+
# if +obj+ is the same object as the last call.
|
|
71
|
+
#
|
|
72
|
+
# @param obj [Object] object to serialize
|
|
73
|
+
# @return [String] frozen Marshal bytes
|
|
64
74
|
def marshal(obj)
|
|
65
75
|
return @bytes if obj.equal?(@last)
|
|
66
76
|
@last = obj
|
|
@@ -75,19 +85,29 @@ module OMQ
|
|
|
75
85
|
class MarshalConnection < SimpleDelegator
|
|
76
86
|
include TransparentDelegator
|
|
77
87
|
|
|
88
|
+
# @param conn [Object] underlying connection to wrap
|
|
89
|
+
# @param cache [SerializeCache] shared serialization cache
|
|
78
90
|
def initialize(conn, cache)
|
|
79
91
|
super(conn)
|
|
80
92
|
@cache = cache
|
|
81
93
|
end
|
|
82
94
|
|
|
95
|
+
|
|
96
|
+
# @param parts [Array<String>] message frames to serialize and send
|
|
97
|
+
# @return [void]
|
|
83
98
|
def send_message(parts)
|
|
84
99
|
super([@cache.marshal(parts)])
|
|
85
100
|
end
|
|
86
101
|
|
|
102
|
+
|
|
103
|
+
# @param parts [Array<String>] message frames to serialize and write
|
|
104
|
+
# @return [void]
|
|
87
105
|
def write_message(parts)
|
|
88
106
|
super([@cache.marshal(parts)])
|
|
89
107
|
end
|
|
90
108
|
|
|
109
|
+
|
|
110
|
+
# @return [Object] deserialized message
|
|
91
111
|
def receive_message
|
|
92
112
|
Marshal.load(super.first)
|
|
93
113
|
end
|
|
@@ -99,6 +119,8 @@ module OMQ
|
|
|
99
119
|
class ShareableConnection < SimpleDelegator
|
|
100
120
|
include TransparentDelegator
|
|
101
121
|
|
|
122
|
+
# @param obj [Object] message to freeze and send via Ractor.make_shareable
|
|
123
|
+
# @return [void]
|
|
102
124
|
def send_message(obj)
|
|
103
125
|
super(::Ractor.make_shareable(obj))
|
|
104
126
|
end
|
|
@@ -112,19 +134,29 @@ module OMQ
|
|
|
112
134
|
class TopicMarshalConnection < SimpleDelegator
|
|
113
135
|
include TransparentDelegator
|
|
114
136
|
|
|
137
|
+
# @param conn [Object] underlying connection to wrap
|
|
138
|
+
# @param cache [SerializeCache] shared serialization cache
|
|
115
139
|
def initialize(conn, cache)
|
|
116
140
|
super(conn)
|
|
117
141
|
@cache = cache
|
|
118
142
|
end
|
|
119
143
|
|
|
144
|
+
|
|
145
|
+
# @param parts [Array<String>] message frames; first frame is topic
|
|
146
|
+
# @return [void]
|
|
120
147
|
def send_message(parts)
|
|
121
148
|
super([parts[0], @cache.marshal(parts[1..])])
|
|
122
149
|
end
|
|
123
150
|
|
|
151
|
+
|
|
152
|
+
# @param parts [Array<String>] message frames; first frame is topic
|
|
153
|
+
# @return [void]
|
|
124
154
|
def write_message(parts)
|
|
125
155
|
super([parts[0], @cache.marshal(parts[1..])])
|
|
126
156
|
end
|
|
127
157
|
|
|
158
|
+
|
|
159
|
+
# @return [Array<String>] deserialized message with topic as first element
|
|
128
160
|
def receive_message
|
|
129
161
|
parts = super
|
|
130
162
|
[parts[0], *Marshal.load(parts[1])]
|
|
@@ -137,6 +169,8 @@ module OMQ
|
|
|
137
169
|
class TopicShareableConnection < SimpleDelegator
|
|
138
170
|
include TransparentDelegator
|
|
139
171
|
|
|
172
|
+
# @param parts [Array<String>] message frames to freeze and send
|
|
173
|
+
# @return [void]
|
|
140
174
|
def send_message(parts)
|
|
141
175
|
super(::Ractor.make_shareable(parts))
|
|
142
176
|
end
|
|
@@ -148,10 +182,16 @@ module OMQ
|
|
|
148
182
|
# Raised by SocketProxy#receive after the socket has been closed.
|
|
149
183
|
# The first receive after closure returns nil; subsequent calls raise.
|
|
150
184
|
#
|
|
151
|
-
class SocketClosedError < IOError
|
|
185
|
+
class SocketClosedError < IOError
|
|
186
|
+
end
|
|
152
187
|
|
|
153
188
|
|
|
189
|
+
# Ractor-side proxy for an OMQ socket. Provides #receive, #<<, and #publish
|
|
190
|
+
# to communicate with the main-thread socket through Ractor ports.
|
|
154
191
|
class SocketProxy
|
|
192
|
+
# @param input_port [Ractor::Port, nil] port for receiving messages (nil if write-only)
|
|
193
|
+
# @param output_port [Ractor::Port, nil] port for sending messages (nil if read-only)
|
|
194
|
+
# @param topic_type [Boolean] whether this is a topic-based socket (PUB/SUB, RADIO/DISH)
|
|
155
195
|
def initialize(input_port, output_port, topic_type)
|
|
156
196
|
@in = input_port
|
|
157
197
|
@out = output_port
|
|
@@ -159,6 +199,7 @@ module OMQ
|
|
|
159
199
|
@closed = false
|
|
160
200
|
end
|
|
161
201
|
|
|
202
|
+
|
|
162
203
|
# Receives the next message from this socket.
|
|
163
204
|
# Returns nil once when the socket closes, then raises
|
|
164
205
|
# SocketClosedError on subsequent calls.
|
|
@@ -176,6 +217,7 @@ module OMQ
|
|
|
176
217
|
@topic_type ? msg.last : msg
|
|
177
218
|
end
|
|
178
219
|
|
|
220
|
+
|
|
179
221
|
# Receives the next message with its topic (PUB/SUB, RADIO/DISH).
|
|
180
222
|
#
|
|
181
223
|
# @return [Array(String, Object), nil] [topic, payload], or nil on close
|
|
@@ -191,6 +233,7 @@ module OMQ
|
|
|
191
233
|
[msg.first, msg.last]
|
|
192
234
|
end
|
|
193
235
|
|
|
236
|
+
|
|
194
237
|
# Sends a message through this socket.
|
|
195
238
|
# For topic-based sockets, wraps as ["", obj] (all subscribers).
|
|
196
239
|
#
|
|
@@ -207,6 +250,7 @@ module OMQ
|
|
|
207
250
|
self
|
|
208
251
|
end
|
|
209
252
|
|
|
253
|
+
|
|
210
254
|
# Publishes a message with an explicit topic (PUB/SUB, RADIO/DISH).
|
|
211
255
|
#
|
|
212
256
|
# @param msg [Object] payload
|
|
@@ -219,6 +263,7 @@ module OMQ
|
|
|
219
263
|
self
|
|
220
264
|
end
|
|
221
265
|
|
|
266
|
+
|
|
222
267
|
# Returns the input port for use with Ractor.select.
|
|
223
268
|
#
|
|
224
269
|
# @return [Ractor::Port]
|
|
@@ -234,14 +279,20 @@ module OMQ
|
|
|
234
279
|
# Ractor.select results.
|
|
235
280
|
#
|
|
236
281
|
class SocketSet < Array
|
|
282
|
+
# @param proxies [Array<SocketProxy>] socket proxies to include
|
|
237
283
|
def initialize(proxies)
|
|
238
284
|
super(proxies)
|
|
239
285
|
@by_port = {}
|
|
240
286
|
proxies.each do |proxy|
|
|
241
|
-
|
|
287
|
+
begin
|
|
288
|
+
@by_port[proxy.to_port] = proxy if proxy.to_port
|
|
289
|
+
rescue ::Ractor::ClosedError
|
|
290
|
+
# Skip non-readable proxies.
|
|
291
|
+
end
|
|
242
292
|
end
|
|
243
293
|
end
|
|
244
294
|
|
|
295
|
+
|
|
245
296
|
# Returns the SocketProxy whose input port matches +port+.
|
|
246
297
|
# Use after Ractor.select to map back to the proxy:
|
|
247
298
|
#
|
|
@@ -262,14 +313,32 @@ module OMQ
|
|
|
262
313
|
# Frozen, shareable context passed to the worker Ractor.
|
|
263
314
|
# The user calls #sockets to trigger the setup handshake.
|
|
264
315
|
#
|
|
316
|
+
# An optional +data+ object (any Ractor.make_shareable-able value)
|
|
317
|
+
# can be passed via OMQ::Ractor.new(data: …) and retrieved inside
|
|
318
|
+
# the worker block with +omq.data+. This is the only way to pass
|
|
319
|
+
# extra information into a worker block under Ruby 4.0's strict
|
|
320
|
+
# Ractor isolation, which forbids capturing any outer local variable.
|
|
321
|
+
#
|
|
265
322
|
class Context
|
|
266
|
-
|
|
323
|
+
# @param setup_port [Ractor::Port] port for the setup handshake
|
|
324
|
+
# @param output_ports [Array<Ractor::Port, nil>] output ports for writable sockets
|
|
325
|
+
# @param socket_configs [Array<Hash>] per-socket configuration hashes
|
|
326
|
+
# @param data [Object, nil] optional shareable data for the worker
|
|
327
|
+
def initialize(setup_port, output_ports, socket_configs, data: nil)
|
|
267
328
|
@setup_port = setup_port
|
|
268
329
|
@output_ports = output_ports
|
|
269
330
|
@socket_configs = socket_configs
|
|
331
|
+
@data = data
|
|
270
332
|
::Ractor.make_shareable(self)
|
|
271
333
|
end
|
|
272
334
|
|
|
335
|
+
|
|
336
|
+
# User-supplied shareable data (passed as +data:+ to OMQ::Ractor.new).
|
|
337
|
+
#
|
|
338
|
+
# @return [Object, nil]
|
|
339
|
+
#
|
|
340
|
+
attr_reader :data
|
|
341
|
+
|
|
273
342
|
# Performs the setup handshake and returns SocketProxy objects.
|
|
274
343
|
#
|
|
275
344
|
# @return [Array<SocketProxy>]
|
|
@@ -293,10 +362,14 @@ module OMQ
|
|
|
293
362
|
#
|
|
294
363
|
# @param sockets [Array<Socket>] sockets to bridge
|
|
295
364
|
# @param serialize [Boolean] whether to auto-serialize per connection (default: true)
|
|
365
|
+
# @param data [Object, nil] optional shareable data accessible as +omq.data+
|
|
366
|
+
# inside the worker block. Under Ruby 4.0's strict Ractor isolation,
|
|
367
|
+
# worker blocks cannot close over outer local variables; use +data:+ to
|
|
368
|
+
# pass configuration into the block.
|
|
296
369
|
# @yield [Context] block executes inside the worker Ractor;
|
|
297
370
|
# must call omq.sockets immediately
|
|
298
371
|
#
|
|
299
|
-
def initialize(*sockets, serialize: true, &block)
|
|
372
|
+
def initialize(*sockets, serialize: true, data: nil, &block)
|
|
300
373
|
raise ArgumentError, "no sockets given" if sockets.empty?
|
|
301
374
|
raise ArgumentError, "no block given" unless block
|
|
302
375
|
|
|
@@ -311,6 +384,7 @@ module OMQ
|
|
|
311
384
|
serialize: serialize, topic_type: topic_type }
|
|
312
385
|
end
|
|
313
386
|
|
|
387
|
+
|
|
314
388
|
# Main Ractor creates output ports (one per writable socket)
|
|
315
389
|
@output_ports = socket_configs.map { |cfg| cfg[:writable] ? ::Ractor::Port.new : nil }
|
|
316
390
|
output_ports = @output_ports
|
|
@@ -321,7 +395,8 @@ module OMQ
|
|
|
321
395
|
# Build frozen context for the worker
|
|
322
396
|
frozen_configs = ::Ractor.make_shareable(socket_configs)
|
|
323
397
|
frozen_outputs = ::Ractor.make_shareable(output_ports)
|
|
324
|
-
|
|
398
|
+
frozen_data = data ? ::Ractor.make_shareable(data) : nil
|
|
399
|
+
ctx = Context.new(setup_port, frozen_outputs, frozen_configs, data: frozen_data)
|
|
325
400
|
|
|
326
401
|
# Install connection wrappers for per-connection serialization
|
|
327
402
|
install_connection_wrappers(socket_configs) if serialize
|
|
@@ -356,6 +431,7 @@ module OMQ
|
|
|
356
431
|
# Waits for the worker Ractor to finish naturally.
|
|
357
432
|
# The worker must return from its block on its own.
|
|
358
433
|
#
|
|
434
|
+
# @return [void]
|
|
359
435
|
def join
|
|
360
436
|
await_ractor { @ractor.join }
|
|
361
437
|
ensure
|
|
@@ -366,6 +442,7 @@ module OMQ
|
|
|
366
442
|
# Returns the worker Ractor's return value.
|
|
367
443
|
# The worker must return from its block on its own.
|
|
368
444
|
#
|
|
445
|
+
# @return [Object] the worker block's return value
|
|
369
446
|
def value
|
|
370
447
|
await_ractor { @ractor.value }
|
|
371
448
|
ensure
|
|
@@ -377,6 +454,7 @@ module OMQ
|
|
|
377
454
|
# Sends nil through all input ports, causing proxy.receive
|
|
378
455
|
# to return nil (first time) or raise SocketClosedError.
|
|
379
456
|
#
|
|
457
|
+
# @return [void]
|
|
380
458
|
def close
|
|
381
459
|
@input_ports.each { |p| p&.send(nil) rescue nil }
|
|
382
460
|
await_ractor { @ractor.join } rescue nil
|
|
@@ -507,6 +585,7 @@ module OMQ
|
|
|
507
585
|
wr.close rescue nil
|
|
508
586
|
end
|
|
509
587
|
|
|
588
|
+
|
|
510
589
|
# Async task: wait on pipe, drain queue, enqueue to engine
|
|
511
590
|
@input_tasks << @parent_task.async(transient: true, annotation: "ractor output bridge") do
|
|
512
591
|
loop do
|
|
@@ -532,6 +611,7 @@ module OMQ
|
|
|
532
611
|
|
|
533
612
|
SHUTDOWN = :__omq_ractor_shutdown__
|
|
534
613
|
|
|
614
|
+
|
|
535
615
|
def cleanup_bridges
|
|
536
616
|
@input_tasks.each { |t| t.stop rescue nil }
|
|
537
617
|
# Unblock output bridge Threads waiting on port.receive
|