omq-backend-rust 0.1.1
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 +7 -0
- data/CHANGELOG.md +29 -0
- data/Cargo.toml +3 -0
- data/LICENSE +15 -0
- data/README.md +70 -0
- data/ext/omq_backend_rust/Cargo.toml +33 -0
- data/ext/omq_backend_rust/extconf.rb +8 -0
- data/ext/omq_backend_rust/src/error.rs +15 -0
- data/ext/omq_backend_rust/src/lib.rs +24 -0
- data/ext/omq_backend_rust/src/notify.rs +69 -0
- data/ext/omq_backend_rust/src/options.rs +185 -0
- data/ext/omq_backend_rust/src/runtime.rs +455 -0
- data/ext/omq_backend_rust/src/socket.rs +470 -0
- data/lib/omq/rust/engine.rb +359 -0
- data/lib/omq/rust/version.rb +7 -0
- data/lib/omq/rust.rb +18 -0
- metadata +87 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "async"
|
|
4
|
+
|
|
5
|
+
module OMQ
|
|
6
|
+
module Rust
|
|
7
|
+
class Engine
|
|
8
|
+
attr_reader :options, :connections, :routing, :socket_type
|
|
9
|
+
attr_reader :peer_connected, :all_peers_gone, :parent_task
|
|
10
|
+
attr_reader :on_io_thread
|
|
11
|
+
alias on_io_thread? on_io_thread
|
|
12
|
+
attr_writer :reconnect_enabled
|
|
13
|
+
attr_accessor :subscriber_joined
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def initialize(socket_type, options)
|
|
17
|
+
@socket_type = socket_type
|
|
18
|
+
@options = options
|
|
19
|
+
@peer_connected = Async::Promise.new
|
|
20
|
+
@all_peers_gone = Async::Promise.new
|
|
21
|
+
@subscriber_joined = Async::Promise.new
|
|
22
|
+
@connections = []
|
|
23
|
+
@closed = false
|
|
24
|
+
@parent_task = nil
|
|
25
|
+
@on_io_thread = false
|
|
26
|
+
@materialized = false
|
|
27
|
+
@recv_sentinels = 0
|
|
28
|
+
@recv_sentinel_r = nil
|
|
29
|
+
@recv_sentinel_w = nil
|
|
30
|
+
|
|
31
|
+
@native = Native::RustSocket.new(socket_type.to_s)
|
|
32
|
+
|
|
33
|
+
@routing = RoutingStub.new(self)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def capture_parent_task(parent: nil)
|
|
38
|
+
return if @parent_task
|
|
39
|
+
|
|
40
|
+
if parent
|
|
41
|
+
@parent_task = parent
|
|
42
|
+
elsif Async::Task.current?
|
|
43
|
+
@parent_task = Async::Task.current
|
|
44
|
+
else
|
|
45
|
+
@parent_task = Reactor.root_task
|
|
46
|
+
@on_io_thread = true
|
|
47
|
+
Reactor.track_linger(@options.linger)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def bind(endpoint, parent: nil, **)
|
|
53
|
+
capture_parent_task(parent: parent)
|
|
54
|
+
ensure_materialized
|
|
55
|
+
resolved = @native.bind(endpoint)
|
|
56
|
+
URI.parse(resolved)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def connect(endpoint, parent: nil, **)
|
|
61
|
+
capture_parent_task(parent: parent)
|
|
62
|
+
ensure_materialized
|
|
63
|
+
@native.connect(endpoint)
|
|
64
|
+
URI.parse(endpoint)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def disconnect(endpoint)
|
|
69
|
+
@native.disconnect(endpoint)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def unbind(endpoint)
|
|
74
|
+
@native.unbind(endpoint)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def enqueue_send(parts)
|
|
79
|
+
ensure_materialized
|
|
80
|
+
result = @native.enqueue_send(parts)
|
|
81
|
+
return if result == :ok
|
|
82
|
+
|
|
83
|
+
@send_signal_r ||= IO.for_fd(@native.send_fd, autoclose: false)
|
|
84
|
+
loop do
|
|
85
|
+
result = @native.enqueue_send(parts)
|
|
86
|
+
return if result == :ok
|
|
87
|
+
|
|
88
|
+
@send_signal_r.wait_readable
|
|
89
|
+
@send_signal_r.read_nonblock(256, exception: false)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def dequeue_recv
|
|
95
|
+
ensure_materialized
|
|
96
|
+
|
|
97
|
+
if @recv_batch && !@recv_batch.empty?
|
|
98
|
+
return @recv_batch.shift
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
msg = try_recv_batch
|
|
102
|
+
return msg if msg
|
|
103
|
+
|
|
104
|
+
return take_recv_sentinel if @recv_sentinels.positive?
|
|
105
|
+
|
|
106
|
+
loop do
|
|
107
|
+
readable = IO.select([@recv_signal_r, @recv_sentinel_r]).first
|
|
108
|
+
|
|
109
|
+
if readable.include?(@recv_signal_r)
|
|
110
|
+
@recv_signal_r.read_nonblock(256, exception: false)
|
|
111
|
+
msg = try_recv_batch
|
|
112
|
+
return msg
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if readable.include?(@recv_sentinel_r)
|
|
116
|
+
@recv_sentinel_r.read_nonblock(256, exception: false)
|
|
117
|
+
return take_recv_sentinel if @recv_sentinels.positive?
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def dequeue_recv_sentinel
|
|
124
|
+
@recv_sentinels += 1
|
|
125
|
+
@recv_sentinel_w&.write_nonblock(".", exception: false) rescue nil
|
|
126
|
+
nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def close
|
|
131
|
+
return if @closed
|
|
132
|
+
|
|
133
|
+
@closed = true
|
|
134
|
+
@peer_connected.resolve(nil) unless @peer_connected.resolved?
|
|
135
|
+
@all_peers_gone.resolve(nil) unless @all_peers_gone.resolved?
|
|
136
|
+
@subscriber_joined.resolve(nil) unless @subscriber_joined.resolved?
|
|
137
|
+
@recv_sentinel_r&.close rescue nil
|
|
138
|
+
@recv_sentinel_w&.close rescue nil
|
|
139
|
+
@native.close
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def closed?
|
|
144
|
+
@closed
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def subscribe(prefix)
|
|
149
|
+
@routing.subscribe(prefix)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def unsubscribe(prefix)
|
|
154
|
+
@routing.unsubscribe(prefix)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def emit_monitor_event(type, endpoint: nil, detail: nil)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def monitor_queue=(queue)
|
|
163
|
+
@monitor_queue = queue
|
|
164
|
+
return unless queue && @materialized
|
|
165
|
+
|
|
166
|
+
start_monitor_forwarder
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def verbose_monitor=(val)
|
|
171
|
+
@verbose_monitor = val
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def ensure_materialized
|
|
179
|
+
return if @materialized
|
|
180
|
+
|
|
181
|
+
capture_parent_task unless @parent_task
|
|
182
|
+
Native.send(:io_threads=, OMQ::Rust.io_threads)
|
|
183
|
+
@native.set_options(extract_options)
|
|
184
|
+
@native.materialize
|
|
185
|
+
@recv_signal_r = IO.for_fd(@native.recv_fd, autoclose: false)
|
|
186
|
+
ensure_recv_sentinel_pipe
|
|
187
|
+
@materialized = true
|
|
188
|
+
|
|
189
|
+
@routing.replay_pending(@native)
|
|
190
|
+
|
|
191
|
+
spawn_lifecycle_watcher(@native.peer_connected_fd, @peer_connected)
|
|
192
|
+
spawn_lifecycle_watcher(@native.all_peers_gone_fd, @all_peers_gone)
|
|
193
|
+
spawn_lifecycle_watcher(@native.subscriber_joined_fd, @subscriber_joined)
|
|
194
|
+
|
|
195
|
+
start_monitor_forwarder if @monitor_queue
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def ensure_recv_sentinel_pipe
|
|
200
|
+
return if @recv_sentinel_r
|
|
201
|
+
|
|
202
|
+
@recv_sentinel_r, @recv_sentinel_w = IO.pipe
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def take_recv_sentinel
|
|
207
|
+
@recv_sentinels -= 1
|
|
208
|
+
nil
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def try_recv_batch
|
|
213
|
+
batch = @native.try_recv_batch
|
|
214
|
+
return unless batch
|
|
215
|
+
|
|
216
|
+
msg = batch.shift
|
|
217
|
+
@recv_batch = batch unless batch.empty?
|
|
218
|
+
msg
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def spawn_lifecycle_watcher(fd, promise)
|
|
223
|
+
io = IO.for_fd(fd, autoclose: false)
|
|
224
|
+
@parent_task.async(transient: true) do
|
|
225
|
+
io.wait_readable
|
|
226
|
+
promise.resolve(true) unless promise.resolved? || @closed
|
|
227
|
+
rescue IOError, Errno::EBADF
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def start_monitor_forwarder
|
|
233
|
+
monitor_io = IO.for_fd(@native.monitor_fd, autoclose: false)
|
|
234
|
+
@parent_task.async(transient: true, annotation: "rust-monitor") do
|
|
235
|
+
loop do
|
|
236
|
+
monitor_io.wait_readable
|
|
237
|
+
monitor_io.read_nonblock(256, exception: false)
|
|
238
|
+
while (data = @native.try_recv_monitor)
|
|
239
|
+
@monitor_queue.enqueue(MonitorEvent.new(**data))
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def extract_options
|
|
247
|
+
h = {}
|
|
248
|
+
h["send_hwm"] = @options.send_hwm
|
|
249
|
+
h["recv_hwm"] = @options.recv_hwm
|
|
250
|
+
h["linger"] = @options.linger == Float::INFINITY ? Float::INFINITY : @options.linger
|
|
251
|
+
h["identity"] = @options.identity if @options.identity && !@options.identity.empty?
|
|
252
|
+
h["router_mandatory"] = @options.router_mandatory
|
|
253
|
+
h["conflate"] = @options.conflate
|
|
254
|
+
h["heartbeat_interval"] = @options.heartbeat_interval
|
|
255
|
+
h["heartbeat_ttl"] = @options.heartbeat_ttl
|
|
256
|
+
h["heartbeat_timeout"] = @options.heartbeat_timeout
|
|
257
|
+
h["max_message_size"] = @options.max_message_size
|
|
258
|
+
h["sndbuf"] = @options.sndbuf
|
|
259
|
+
h["rcvbuf"] = @options.rcvbuf
|
|
260
|
+
h["on_mute"] = @options.on_mute.to_s
|
|
261
|
+
|
|
262
|
+
ri = @options.reconnect_interval
|
|
263
|
+
if ri.is_a?(Range)
|
|
264
|
+
h["reconnect_interval_min"] = ri.begin.to_f
|
|
265
|
+
h["reconnect_interval_max"] = ri.end.to_f
|
|
266
|
+
elsif ri
|
|
267
|
+
h["reconnect_interval"] = ri.to_f
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
extract_mechanism(h)
|
|
271
|
+
|
|
272
|
+
h
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def extract_mechanism(h)
|
|
277
|
+
mech = @options.mechanism
|
|
278
|
+
case mech
|
|
279
|
+
when Protocol::ZMTP::Mechanism::Null
|
|
280
|
+
h["mechanism_type"] = "null"
|
|
281
|
+
else
|
|
282
|
+
klass = mech.class.name
|
|
283
|
+
if klass&.include?("Curve")
|
|
284
|
+
extract_curve_mechanism(h, mech)
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def extract_curve_mechanism(h, mech)
|
|
291
|
+
h["mechanism_type"] = "curve"
|
|
292
|
+
h["mechanism_server"] = mech.instance_variable_get(:@as_server)
|
|
293
|
+
|
|
294
|
+
pub_key = mech.instance_variable_get(:@permanent_public)
|
|
295
|
+
sec_key = mech.instance_variable_get(:@permanent_secret)
|
|
296
|
+
h["mechanism_public_key"] = pub_key.to_s.b if pub_key
|
|
297
|
+
h["mechanism_secret_key"] = sec_key.to_s.b if sec_key
|
|
298
|
+
|
|
299
|
+
unless h["mechanism_server"]
|
|
300
|
+
srv_key = mech.instance_variable_get(:@server_public)
|
|
301
|
+
h["mechanism_server_key"] = srv_key.to_s.b if srv_key
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class RoutingStub
|
|
307
|
+
def initialize(engine)
|
|
308
|
+
@engine = engine
|
|
309
|
+
@pending_subscribe = []
|
|
310
|
+
@pending_join = []
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def subscriber_joined
|
|
315
|
+
@engine.subscriber_joined
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def subscribe(prefix)
|
|
320
|
+
native = @engine.instance_variable_get(:@native)
|
|
321
|
+
if @engine.instance_variable_get(:@materialized)
|
|
322
|
+
native.subscribe(prefix.b)
|
|
323
|
+
else
|
|
324
|
+
@pending_subscribe << prefix.b
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def unsubscribe(prefix)
|
|
330
|
+
@engine.instance_variable_get(:@native).unsubscribe(prefix.b)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def join(group)
|
|
335
|
+
native = @engine.instance_variable_get(:@native)
|
|
336
|
+
if @engine.instance_variable_get(:@materialized)
|
|
337
|
+
native.join(group)
|
|
338
|
+
else
|
|
339
|
+
@pending_join << group
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def leave(group)
|
|
345
|
+
@engine.instance_variable_get(:@native).leave(group)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def replay_pending(native)
|
|
350
|
+
@pending_subscribe.each { |p| native.subscribe(p) }
|
|
351
|
+
@pending_subscribe.clear
|
|
352
|
+
@pending_join.each { |g| native.join(g) }
|
|
353
|
+
@pending_join.clear
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
data/lib/omq/rust.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "omq"
|
|
4
|
+
require "omq/rust/omq_backend_rust"
|
|
5
|
+
require_relative "rust/version"
|
|
6
|
+
require_relative "rust/engine"
|
|
7
|
+
|
|
8
|
+
module OMQ
|
|
9
|
+
module Rust
|
|
10
|
+
@io_threads = 1
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
attr_accessor :io_threads
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
OMQ::Backend.register(:rust, OMQ::Rust::Engine)
|
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: omq-backend-rust
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Patrik Wenger
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: omq
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.28'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.28'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rb_sys
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.9'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.9'
|
|
40
|
+
description: Drop-in Rust backend for OMQ. Same socket API (REQ/REP, PUB/SUB, PUSH/PULL,
|
|
41
|
+
DEALER/ROUTER, and all draft types), but networking runs on a Tokio runtime inside
|
|
42
|
+
a native extension compiled via rb_sys. Fully interoperable with the default Ruby
|
|
43
|
+
engine.
|
|
44
|
+
email:
|
|
45
|
+
- paddor@gmail.com
|
|
46
|
+
executables: []
|
|
47
|
+
extensions:
|
|
48
|
+
- ext/omq_backend_rust/extconf.rb
|
|
49
|
+
extra_rdoc_files: []
|
|
50
|
+
files:
|
|
51
|
+
- CHANGELOG.md
|
|
52
|
+
- Cargo.toml
|
|
53
|
+
- LICENSE
|
|
54
|
+
- README.md
|
|
55
|
+
- ext/omq_backend_rust/Cargo.toml
|
|
56
|
+
- ext/omq_backend_rust/extconf.rb
|
|
57
|
+
- ext/omq_backend_rust/src/error.rs
|
|
58
|
+
- ext/omq_backend_rust/src/lib.rs
|
|
59
|
+
- ext/omq_backend_rust/src/notify.rs
|
|
60
|
+
- ext/omq_backend_rust/src/options.rs
|
|
61
|
+
- ext/omq_backend_rust/src/runtime.rs
|
|
62
|
+
- ext/omq_backend_rust/src/socket.rs
|
|
63
|
+
- lib/omq/rust.rb
|
|
64
|
+
- lib/omq/rust/engine.rb
|
|
65
|
+
- lib/omq/rust/version.rb
|
|
66
|
+
homepage: https://github.com/zeromq/omq.rb/tree/main/gems/omq-backend-rust
|
|
67
|
+
licenses:
|
|
68
|
+
- ISC
|
|
69
|
+
metadata: {}
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '3.3'
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubygems_version: 4.0.16
|
|
85
|
+
specification_version: 4
|
|
86
|
+
summary: Rust-backed engine for OMQ using omq-tokio
|
|
87
|
+
test_files: []
|