omq-backend-rust 0.2.0 → 0.3.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/README.md +9 -5
- data/lib/omq/backend/rust.rb +14 -7
- data/lib/omq/rust/engine.rb +76 -106
- data/lib/omq/rust/java/engine.rb +642 -0
- data/lib/omq/rust/java/platform.rb +95 -0
- data/lib/omq/rust/version.rb +13 -1
- metadata +10 -21
- data/Cargo.toml +0 -3
- data/ext/omq_backend_rust/Cargo.toml +0 -33
- data/ext/omq_backend_rust/build.rs +0 -23
- data/ext/omq_backend_rust/extconf.rb +0 -8
- data/ext/omq_backend_rust/src/error.rs +0 -16
- data/ext/omq_backend_rust/src/lib.rs +0 -51
- data/ext/omq_backend_rust/src/notify.rs +0 -69
- data/ext/omq_backend_rust/src/options.rs +0 -220
- data/ext/omq_backend_rust/src/rb.rs +0 -433
- data/ext/omq_backend_rust/src/runtime.rs +0 -439
- data/ext/omq_backend_rust/src/socket.rs +0 -707
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "jar_dependencies"
|
|
4
|
+
require "thread"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "java"
|
|
7
|
+
|
|
8
|
+
require_relative "../version"
|
|
9
|
+
require_relative "platform"
|
|
10
|
+
|
|
11
|
+
require_jar "io.github.paddor", "omq-java",
|
|
12
|
+
OMQ::Rust::Java.classifier, OMQ::Rust::OMQ_JAVA_VERSION
|
|
13
|
+
|
|
14
|
+
module OMQ
|
|
15
|
+
module Rust
|
|
16
|
+
module Java
|
|
17
|
+
Duration = ::Java::JavaTime::Duration
|
|
18
|
+
JavaOMQ = ::Java::IoOmq::OMQ
|
|
19
|
+
SocketOptions = ::Java::IoOmq::SocketOptions
|
|
20
|
+
SocketType = ::Java::IoOmq::SocketType
|
|
21
|
+
Message = ::Java::IoOmq::Message
|
|
22
|
+
CurveKeypair = ::Java::IoOmq::CurveKeypair
|
|
23
|
+
OnMute = ::Java::IoOmq::OnMute
|
|
24
|
+
@context_mutex = Mutex.new
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
def context
|
|
28
|
+
@context_mutex.synchronize do
|
|
29
|
+
@context ||= JavaOMQ.context(OMQ::Rust.io_threads.to_i).tap do
|
|
30
|
+
at_exit { @context&.close rescue nil } unless @context_at_exit
|
|
31
|
+
@context_at_exit = true
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Promise
|
|
38
|
+
def initialize(&resolver)
|
|
39
|
+
@mutex = Mutex.new
|
|
40
|
+
@cv = ConditionVariable.new
|
|
41
|
+
@resolved = false
|
|
42
|
+
@value = nil
|
|
43
|
+
@resolver = resolver
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolved?
|
|
48
|
+
@mutex.synchronize { @resolved }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def resolve(value = nil)
|
|
53
|
+
@mutex.synchronize do
|
|
54
|
+
return @value if @resolved
|
|
55
|
+
|
|
56
|
+
@resolved = true
|
|
57
|
+
@value = value
|
|
58
|
+
@cv.broadcast
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def wait
|
|
64
|
+
until resolved?
|
|
65
|
+
if @resolver
|
|
66
|
+
@resolver.call(self)
|
|
67
|
+
else
|
|
68
|
+
@mutex.synchronize { @cv.wait(@mutex, 0.05) unless @resolved }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
@value
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
class Engine
|
|
77
|
+
POLL_SECONDS = 0.005
|
|
78
|
+
POLL_DURATION = Duration.ofMillis((POLL_SECONDS * 1000).to_i)
|
|
79
|
+
|
|
80
|
+
attr_reader :options, :connections, :routing, :socket_type
|
|
81
|
+
attr_reader :peer_connected, :all_peers_gone, :parent_task
|
|
82
|
+
attr_reader :on_io_thread
|
|
83
|
+
alias on_io_thread? on_io_thread
|
|
84
|
+
attr_writer :reconnect_enabled
|
|
85
|
+
attr_accessor :subscriber_joined
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def initialize(socket_type, options)
|
|
89
|
+
@socket_type = socket_type
|
|
90
|
+
@options = options
|
|
91
|
+
@connections = {}
|
|
92
|
+
@closed = false
|
|
93
|
+
@parent_task = nil
|
|
94
|
+
@on_io_thread = false
|
|
95
|
+
@materialized = false
|
|
96
|
+
@recv_sentinels = 0
|
|
97
|
+
@compression_options = {}
|
|
98
|
+
|
|
99
|
+
@peer_connected = Promise.new do |promise|
|
|
100
|
+
wait_connected(1)
|
|
101
|
+
promise.resolve(true) unless @closed
|
|
102
|
+
end
|
|
103
|
+
@all_peers_gone = Promise.new do |promise|
|
|
104
|
+
wait_all_peers_gone
|
|
105
|
+
promise.resolve(true) unless @closed
|
|
106
|
+
end
|
|
107
|
+
@subscriber_joined = Promise.new do |promise|
|
|
108
|
+
wait_subscribed(1)
|
|
109
|
+
promise.resolve(true) unless @closed
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
@routing = RoutingStub.new(self)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def capture_parent_task(parent: nil)
|
|
117
|
+
@parent_task ||= parent
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def bind(endpoint, parent: nil, **opts)
|
|
122
|
+
capture_parent_task(parent: parent)
|
|
123
|
+
apply_endpoint_options!(opts)
|
|
124
|
+
ensure_materialized
|
|
125
|
+
URI.parse(with_java_errors { @native.bind(endpoint) })
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def connect(endpoint, parent: nil, **opts)
|
|
130
|
+
capture_parent_task(parent: parent)
|
|
131
|
+
apply_endpoint_options!(opts)
|
|
132
|
+
ensure_materialized
|
|
133
|
+
with_java_errors { @native.connect(endpoint) }
|
|
134
|
+
resolve_peer_connected if endpoint.start_with?("inproc://")
|
|
135
|
+
URI.parse(endpoint)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def disconnect(endpoint)
|
|
140
|
+
ensure_materialized
|
|
141
|
+
with_java_errors { @native.disconnect(endpoint) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def unbind(endpoint)
|
|
146
|
+
ensure_materialized
|
|
147
|
+
with_java_errors { @native.unbind(endpoint) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def enqueue_send(parts)
|
|
152
|
+
ensure_materialized
|
|
153
|
+
msg = java_message(parts)
|
|
154
|
+
|
|
155
|
+
if (timeout = @options.write_timeout)
|
|
156
|
+
ok = with_java_errors { @native.send(msg, duration_from_seconds("write_timeout", timeout)) }
|
|
157
|
+
raise IO::TimeoutError, "operation timed out" unless ok
|
|
158
|
+
else
|
|
159
|
+
with_java_errors { @native.send(msg) }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def dequeue_recv
|
|
167
|
+
ensure_materialized
|
|
168
|
+
@recv_deadline = monotonic_time + @options.read_timeout.to_f if @options.read_timeout
|
|
169
|
+
|
|
170
|
+
loop do
|
|
171
|
+
return take_recv_sentinel if @recv_sentinels.positive?
|
|
172
|
+
|
|
173
|
+
optional = with_java_errors { @native.tryReceive }
|
|
174
|
+
return ruby_parts(optional.get) if optional.isPresent
|
|
175
|
+
|
|
176
|
+
raise IO::TimeoutError, "operation timed out" if recv_deadline_expired?
|
|
177
|
+
|
|
178
|
+
sleep recv_poll_seconds
|
|
179
|
+
end
|
|
180
|
+
ensure
|
|
181
|
+
@recv_deadline = nil
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def dequeue_recv_sentinel
|
|
186
|
+
@recv_sentinels += 1
|
|
187
|
+
nil
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def close
|
|
192
|
+
return if @closed
|
|
193
|
+
|
|
194
|
+
@closed = true
|
|
195
|
+
@peer_connected.resolve(nil)
|
|
196
|
+
@all_peers_gone.resolve(nil)
|
|
197
|
+
@subscriber_joined.resolve(nil)
|
|
198
|
+
with_java_errors { @native&.close }
|
|
199
|
+
@connections.clear
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
alias stop close
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def closed?
|
|
208
|
+
@closed
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def subscribe(prefix)
|
|
213
|
+
@routing.subscribe(prefix)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def unsubscribe(prefix)
|
|
218
|
+
@routing.unsubscribe(prefix)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def emit_monitor_event(_type, endpoint: nil, detail: nil)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def monitor_queue=(queue)
|
|
227
|
+
@monitor_queue = queue
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def verbose_monitor=(val)
|
|
232
|
+
@verbose_monitor = val
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
private
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def ensure_materialized
|
|
240
|
+
return if @materialized
|
|
241
|
+
|
|
242
|
+
@native = Java.context.socket(SocketType.valueOf(@socket_type.to_s), java_options)
|
|
243
|
+
@materialized = true
|
|
244
|
+
@routing.replay_pending(@native)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def java_message(parts)
|
|
249
|
+
frames = parts.map { |part| part.to_java_bytes }
|
|
250
|
+
return Message.of(frames.first) if frames.size == 1
|
|
251
|
+
|
|
252
|
+
Message.multipart(*frames)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def ruby_parts(message)
|
|
257
|
+
parts = []
|
|
258
|
+
message.partCount.times do |index|
|
|
259
|
+
parts << String.from_java_bytes(message.part(index)).b.freeze
|
|
260
|
+
end
|
|
261
|
+
parts.freeze
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def take_recv_sentinel
|
|
266
|
+
@recv_sentinels -= 1
|
|
267
|
+
nil
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def recv_deadline_expired?
|
|
272
|
+
return false unless @options.read_timeout
|
|
273
|
+
|
|
274
|
+
monotonic_time >= @recv_deadline
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def recv_poll_seconds
|
|
279
|
+
return POLL_SECONDS unless @options.read_timeout
|
|
280
|
+
|
|
281
|
+
[[@recv_deadline - monotonic_time, POLL_SECONDS].min, 0].max
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def monotonic_time
|
|
286
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def wait_connected(count)
|
|
291
|
+
ensure_materialized
|
|
292
|
+
|
|
293
|
+
loop do
|
|
294
|
+
result = @native.waitConnected(count, POLL_DURATION)
|
|
295
|
+
resolve_peer_connected if result.to_i.positive?
|
|
296
|
+
return result
|
|
297
|
+
rescue ::Java::IoOmq::TimeoutException
|
|
298
|
+
return nil if @closed
|
|
299
|
+
rescue ::Java::IoOmq::ClosedException
|
|
300
|
+
return nil
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def resolve_peer_connected
|
|
306
|
+
@connections[:peer] = true
|
|
307
|
+
@peer_connected.resolve(true) unless @peer_connected.resolved?
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def wait_subscribed(count)
|
|
312
|
+
ensure_materialized
|
|
313
|
+
|
|
314
|
+
loop do
|
|
315
|
+
result = @native.waitSubscribed(count, POLL_DURATION)
|
|
316
|
+
return result
|
|
317
|
+
rescue ::Java::IoOmq::TimeoutException
|
|
318
|
+
return nil if @closed
|
|
319
|
+
rescue ::Java::IoOmq::ClosedException
|
|
320
|
+
return nil
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def wait_all_peers_gone
|
|
326
|
+
@peer_connected.wait
|
|
327
|
+
|
|
328
|
+
loop do
|
|
329
|
+
@native.waitConnected(1, POLL_DURATION)
|
|
330
|
+
sleep POLL_SECONDS
|
|
331
|
+
rescue ::Java::IoOmq::TimeoutException
|
|
332
|
+
@connections.clear
|
|
333
|
+
return true
|
|
334
|
+
rescue ::Java::IoOmq::ClosedException
|
|
335
|
+
return nil
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def java_options
|
|
341
|
+
builder = SocketOptions.builder
|
|
342
|
+
apply_socket_options(builder)
|
|
343
|
+
apply_compression_options(builder)
|
|
344
|
+
apply_mechanism(builder)
|
|
345
|
+
builder.build
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def apply_socket_options(builder)
|
|
350
|
+
builder.sendHighWaterMark([integer_option("send_hwm", @options.send_hwm), 0].max)
|
|
351
|
+
builder.receiveHighWaterMark([integer_option("recv_hwm", @options.recv_hwm), 0].max)
|
|
352
|
+
|
|
353
|
+
if @options.linger == Float::INFINITY
|
|
354
|
+
builder.lingerForever
|
|
355
|
+
else
|
|
356
|
+
builder.linger(duration_from_seconds("linger", @options.linger))
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
builder.identity(bytes(@options.identity)) if @options.identity && !@options.identity.empty?
|
|
360
|
+
builder.routerMandatory(!!@options.router_mandatory)
|
|
361
|
+
builder.conflate(!!@options.conflate)
|
|
362
|
+
builder.maxMessageSize(nonnegative_integer("max_message_size", @options.max_message_size)) if @options.max_message_size
|
|
363
|
+
builder.sendBufferSize(nonnegative_integer("sndbuf", @options.sndbuf)) if @options.sndbuf
|
|
364
|
+
builder.receiveBufferSize(nonnegative_integer("rcvbuf", @options.rcvbuf)) if @options.rcvbuf
|
|
365
|
+
builder.onMute(on_mute)
|
|
366
|
+
|
|
367
|
+
apply_duration_option(builder, :heartbeatInterval, "heartbeat_interval", @options.heartbeat_interval)
|
|
368
|
+
apply_duration_option(builder, :heartbeatTtl, "heartbeat_ttl", @options.heartbeat_ttl)
|
|
369
|
+
apply_duration_option(builder, :heartbeatTimeout, "heartbeat_timeout", @options.heartbeat_timeout)
|
|
370
|
+
apply_reconnect_option(builder)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def apply_reconnect_option(builder)
|
|
375
|
+
reconnect = @options.reconnect_interval
|
|
376
|
+
|
|
377
|
+
case reconnect
|
|
378
|
+
when Range
|
|
379
|
+
builder.reconnectExponential(
|
|
380
|
+
duration_from_seconds("reconnect_interval min", reconnect.begin),
|
|
381
|
+
duration_from_seconds("reconnect_interval max", reconnect.end),
|
|
382
|
+
)
|
|
383
|
+
when nil, false
|
|
384
|
+
builder.reconnectDisabled
|
|
385
|
+
else
|
|
386
|
+
builder.reconnectInterval(duration_from_seconds("reconnect_interval", reconnect))
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def apply_duration_option(builder, method, label, value)
|
|
392
|
+
builder.public_send(method, duration_from_seconds(label, value)) if value
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def apply_compression_options(builder)
|
|
397
|
+
if @compression_options.key?("compression_auto_train")
|
|
398
|
+
builder.compressionAutoTrain(!!@compression_options["compression_auto_train"])
|
|
399
|
+
end
|
|
400
|
+
if (value = @compression_options["compression_threshold"])
|
|
401
|
+
builder.compressionThreshold(nonnegative_integer("compression_threshold", value))
|
|
402
|
+
end
|
|
403
|
+
if (value = @compression_options["compression_level"])
|
|
404
|
+
builder.compressionLevel(integer_option("compression_level", value))
|
|
405
|
+
end
|
|
406
|
+
if (value = @compression_options["compression_dict"])
|
|
407
|
+
builder.compressionDict(bytes(value))
|
|
408
|
+
end
|
|
409
|
+
if (value = @compression_options["compression_dict_capacity"])
|
|
410
|
+
builder.compressionDictCapacity(nonnegative_integer("compression_dict_capacity", value))
|
|
411
|
+
end
|
|
412
|
+
if (value = @compression_options["max_recv_dict_size"])
|
|
413
|
+
builder.maxReceiveDictSize(nonnegative_integer("max_recv_dict_size", value))
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
return unless @compression_options.key?("compression_offload_threshold")
|
|
417
|
+
|
|
418
|
+
value = @compression_options["compression_offload_threshold"]
|
|
419
|
+
if value.nil? || value.to_i.negative?
|
|
420
|
+
builder.noCompressionOffload
|
|
421
|
+
else
|
|
422
|
+
builder.compressionOffloadThreshold(nonnegative_integer("compression_offload_threshold", value))
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def apply_mechanism(builder)
|
|
428
|
+
mech = @options.mechanism
|
|
429
|
+
klass = mech.class.name
|
|
430
|
+
return unless klass&.include?("Curve")
|
|
431
|
+
|
|
432
|
+
require "protocol/zmtp/z85"
|
|
433
|
+
|
|
434
|
+
keypair = CurveKeypair.new(
|
|
435
|
+
z85_key(mech.instance_variable_get(:@permanent_public), "public key"),
|
|
436
|
+
z85_key(mech.instance_variable_get(:@permanent_secret), "secret key"),
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
if mech.instance_variable_get(:@as_server)
|
|
440
|
+
builder.curveServer(keypair)
|
|
441
|
+
else
|
|
442
|
+
builder.curveClient(keypair, z85_key(mech.instance_variable_get(:@server_public), "server key"))
|
|
443
|
+
end
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def z85_key(key, label)
|
|
448
|
+
raw = key&.to_s&.b
|
|
449
|
+
raise ArgumentError, "#{label} must be exactly 32 bytes" unless raw&.bytesize == 32
|
|
450
|
+
|
|
451
|
+
Protocol::ZMTP::Z85.encode(raw)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def on_mute
|
|
456
|
+
case @options.on_mute&.to_sym
|
|
457
|
+
when :drop_newest, :drop
|
|
458
|
+
OnMute::DROP_NEWEST
|
|
459
|
+
when :drop_oldest
|
|
460
|
+
OnMute::DROP_OLDEST
|
|
461
|
+
else
|
|
462
|
+
OnMute::BLOCK
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def duration_from_seconds(label, value)
|
|
468
|
+
seconds = Float(value)
|
|
469
|
+
raise ArgumentError, "#{label} must be finite and non-negative" unless seconds.finite? && !seconds.negative?
|
|
470
|
+
|
|
471
|
+
Duration.ofNanos((seconds * 1_000_000_000).round)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def nonnegative_integer(label, value)
|
|
476
|
+
value = integer_option(label, value)
|
|
477
|
+
raise ArgumentError, "#{label} must be non-negative" if value.negative?
|
|
478
|
+
|
|
479
|
+
value
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def integer_option(label, value)
|
|
484
|
+
Integer(value)
|
|
485
|
+
rescue ArgumentError, TypeError
|
|
486
|
+
raise ArgumentError, "#{label} must be an Integer"
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def bytes(value)
|
|
491
|
+
value.to_s.b.to_java_bytes
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def apply_endpoint_options!(opts)
|
|
496
|
+
compression = extract_endpoint_compression_options(opts)
|
|
497
|
+
return if compression.empty?
|
|
498
|
+
|
|
499
|
+
if @materialized
|
|
500
|
+
existing = compression.keys.to_h do |key|
|
|
501
|
+
[key, @compression_options.fetch(key, default_compression_option(key))]
|
|
502
|
+
end
|
|
503
|
+
return if compression == existing
|
|
504
|
+
|
|
505
|
+
raise ArgumentError,
|
|
506
|
+
"Rust backend compression options must be set before first bind/connect"
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
@compression_options.merge!(compression)
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def extract_endpoint_compression_options(opts)
|
|
514
|
+
out = {}
|
|
515
|
+
|
|
516
|
+
if opts.key?(:level)
|
|
517
|
+
validate_zstd_level!(opts[:level])
|
|
518
|
+
out["compression_level"] = opts[:level]
|
|
519
|
+
end
|
|
520
|
+
out["compression_dict"] = opts[:dict].b if opts.key?(:dict) && opts[:dict]
|
|
521
|
+
|
|
522
|
+
if opts.key?(:auto_dict)
|
|
523
|
+
auto_dict = opts[:auto_dict]
|
|
524
|
+
if auto_dict && opts[:dict]
|
|
525
|
+
raise ArgumentError, "cannot combine auto_dict: and dict:"
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
case auto_dict
|
|
529
|
+
when nil, false
|
|
530
|
+
out["compression_auto_train"] = false
|
|
531
|
+
when true
|
|
532
|
+
out["compression_auto_train"] = true
|
|
533
|
+
when Hash
|
|
534
|
+
if auto_dict.key?(:trigger)
|
|
535
|
+
raise ArgumentError,
|
|
536
|
+
"Rust backend does not support auto_dict: trigger"
|
|
537
|
+
end
|
|
538
|
+
validate_positive!("auto_dict capacity", auto_dict[:capacity]) if auto_dict[:capacity]
|
|
539
|
+
out["compression_auto_train"] = true
|
|
540
|
+
out["compression_dict_capacity"] = auto_dict[:capacity] if auto_dict[:capacity]
|
|
541
|
+
else
|
|
542
|
+
raise TypeError, "auto_dict: must be true, false, or a Hash; got #{auto_dict.class}"
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
out["compression_threshold"] = opts[:compression_threshold] if opts.key?(:compression_threshold)
|
|
547
|
+
out["max_recv_dict_size"] = opts[:max_recv_dict_size] if opts.key?(:max_recv_dict_size)
|
|
548
|
+
if opts.key?(:compression_offload_threshold)
|
|
549
|
+
out["compression_offload_threshold"] = opts[:compression_offload_threshold] || -1
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
out
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def default_compression_option(key)
|
|
557
|
+
key == "compression_auto_train" ? false : nil
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def validate_positive!(label, value)
|
|
562
|
+
return if value.respond_to?(:positive?) && value.positive?
|
|
563
|
+
|
|
564
|
+
raise ArgumentError, "#{label} must be positive"
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def validate_zstd_level!(level)
|
|
569
|
+
return if level.is_a?(Integer) && (-8..4).cover?(level)
|
|
570
|
+
|
|
571
|
+
raise ArgumentError, "zstd compression level must be -8..4, got #{level.inspect}"
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def with_java_errors
|
|
576
|
+
yield
|
|
577
|
+
rescue ::Java::IoOmq::TimeoutException => error
|
|
578
|
+
raise IO::TimeoutError, error.message
|
|
579
|
+
rescue ::Java::IoOmq::ClosedException => error
|
|
580
|
+
raise IOError, error.message
|
|
581
|
+
rescue ::Java::IoOmq::InvalidEndpointException => error
|
|
582
|
+
raise ArgumentError, error.message
|
|
583
|
+
rescue ::Java::IoOmq::OMQException => error
|
|
584
|
+
raise RuntimeError, error.message
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
class RoutingStub
|
|
588
|
+
def initialize(engine)
|
|
589
|
+
@engine = engine
|
|
590
|
+
@pending_subscribe = []
|
|
591
|
+
@pending_join = []
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def subscriber_joined
|
|
596
|
+
@engine.subscriber_joined
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def subscribe(prefix)
|
|
601
|
+
native = @engine.instance_variable_get(:@native)
|
|
602
|
+
if @engine.instance_variable_get(:@materialized)
|
|
603
|
+
native.subscribe(prefix.b.to_java_bytes)
|
|
604
|
+
else
|
|
605
|
+
@pending_subscribe << prefix.b
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def unsubscribe(prefix)
|
|
611
|
+
@engine.instance_variable_get(:@native).unsubscribe(prefix.b.to_java_bytes)
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def join(group)
|
|
616
|
+
native = @engine.instance_variable_get(:@native)
|
|
617
|
+
if @engine.instance_variable_get(:@materialized)
|
|
618
|
+
native.join(group.b.to_java_bytes)
|
|
619
|
+
else
|
|
620
|
+
@pending_join << group.b
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def leave(group)
|
|
626
|
+
@engine.instance_variable_get(:@native).leave(group.b.to_java_bytes)
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def replay_pending(native)
|
|
631
|
+
@pending_subscribe.each { |prefix| native.subscribe(prefix.to_java_bytes) }
|
|
632
|
+
@pending_subscribe.clear
|
|
633
|
+
@pending_join.each { |group| native.join(group.to_java_bytes) }
|
|
634
|
+
@pending_join.clear
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
Engine = Java::Engine
|
|
641
|
+
end
|
|
642
|
+
end
|