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.
@@ -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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ module Rust
5
+ VERSION = "0.1.1"
6
+ end
7
+ 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: []