omq-backend-rust 0.1.7 → 0.2.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 +22 -0
- data/ext/omq_backend_rust/Cargo.toml +3 -4
- data/ext/omq_backend_rust/build.rs +23 -0
- data/ext/omq_backend_rust/src/error.rs +11 -10
- data/ext/omq_backend_rust/src/lib.rs +35 -10
- data/ext/omq_backend_rust/src/options.rs +117 -104
- data/ext/omq_backend_rust/src/rb.rs +433 -0
- data/ext/omq_backend_rust/src/runtime.rs +45 -99
- data/ext/omq_backend_rust/src/socket.rs +395 -166
- data/lib/omq/rust/engine.rb +64 -24
- data/lib/omq/rust/fd_watcher.rb +184 -0
- data/lib/omq/rust/version.rb +1 -1
- metadata +4 -1
data/lib/omq/rust/engine.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "async"
|
|
4
|
+
require_relative "fd_watcher"
|
|
4
5
|
|
|
5
6
|
module OMQ
|
|
6
7
|
module Rust
|
|
@@ -14,18 +15,18 @@ module OMQ
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def initialize(socket_type, options)
|
|
17
|
-
@socket_type
|
|
18
|
-
@options
|
|
19
|
-
@peer_connected
|
|
20
|
-
@all_peers_gone
|
|
21
|
-
@subscriber_joined
|
|
22
|
-
@connections
|
|
23
|
-
@closed
|
|
24
|
-
@parent_task
|
|
25
|
-
@on_io_thread
|
|
26
|
-
@materialized
|
|
27
|
-
@recv_sentinels
|
|
28
|
-
@compression_options
|
|
18
|
+
@socket_type = socket_type
|
|
19
|
+
@options = options
|
|
20
|
+
@peer_connected = Async::Promise.new
|
|
21
|
+
@all_peers_gone = Async::Promise.new
|
|
22
|
+
@subscriber_joined = Async::Promise.new
|
|
23
|
+
@connections = {}
|
|
24
|
+
@closed = false
|
|
25
|
+
@parent_task = nil
|
|
26
|
+
@on_io_thread = false
|
|
27
|
+
@materialized = false
|
|
28
|
+
@recv_sentinels = 0
|
|
29
|
+
@compression_options = {}
|
|
29
30
|
|
|
30
31
|
@native = Native::RustSocket.new(socket_type.to_s)
|
|
31
32
|
|
|
@@ -40,6 +41,8 @@ module OMQ
|
|
|
40
41
|
@parent_task = parent
|
|
41
42
|
elsif Async::Task.current?
|
|
42
43
|
@parent_task = Async::Task.current
|
|
44
|
+
elsif !Reactor.native_fiber_scheduler?
|
|
45
|
+
@parent_task = nil
|
|
43
46
|
else
|
|
44
47
|
@parent_task = Reactor.root_task
|
|
45
48
|
@on_io_thread = true
|
|
@@ -81,7 +84,7 @@ module OMQ
|
|
|
81
84
|
result = @native.enqueue_send(parts)
|
|
82
85
|
return if result == :ok
|
|
83
86
|
|
|
84
|
-
@send_signal_r ||=
|
|
87
|
+
@send_signal_r ||= io_for_native_fd(@native.send_fd)
|
|
85
88
|
loop do
|
|
86
89
|
result = @native.enqueue_send(parts)
|
|
87
90
|
return if result == :ok
|
|
@@ -130,7 +133,10 @@ module OMQ
|
|
|
130
133
|
@peer_connected.resolve(nil) unless @peer_connected.resolved?
|
|
131
134
|
@all_peers_gone.resolve(nil) unless @all_peers_gone.resolved?
|
|
132
135
|
@subscriber_joined.resolve(nil) unless @subscriber_joined.resolved?
|
|
136
|
+
FdWatcher.unwatch_owner(self) unless Reactor.native_fiber_scheduler?
|
|
133
137
|
@native.close
|
|
138
|
+
close_io_wrapper(@recv_signal_r)
|
|
139
|
+
close_io_wrapper(@send_signal_r)
|
|
134
140
|
end
|
|
135
141
|
|
|
136
142
|
|
|
@@ -176,7 +182,7 @@ module OMQ
|
|
|
176
182
|
Native.send(:io_threads=, OMQ::Rust.io_threads)
|
|
177
183
|
@native.set_options(extract_options)
|
|
178
184
|
@native.materialize
|
|
179
|
-
@recv_signal_r =
|
|
185
|
+
@recv_signal_r = io_for_native_fd(@native.recv_fd)
|
|
180
186
|
@materialized = true
|
|
181
187
|
|
|
182
188
|
@routing.replay_pending(@native)
|
|
@@ -206,21 +212,42 @@ module OMQ
|
|
|
206
212
|
|
|
207
213
|
|
|
208
214
|
def spawn_lifecycle_watcher(fd, promise)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
215
|
+
if Reactor.native_fiber_scheduler?
|
|
216
|
+
io = io_for_native_fd(fd)
|
|
217
|
+
@parent_task.async(transient: true) do
|
|
218
|
+
io.wait_readable
|
|
219
|
+
promise.resolve(true) unless promise.resolved? || @closed
|
|
220
|
+
rescue IOError, Errno::EBADF
|
|
221
|
+
ensure
|
|
222
|
+
close_io_wrapper(io)
|
|
223
|
+
end
|
|
224
|
+
else
|
|
225
|
+
FdWatcher.watch_once(fd, owner: self) do
|
|
226
|
+
promise.resolve(true) unless promise.resolved? || @closed
|
|
227
|
+
end
|
|
214
228
|
end
|
|
215
229
|
end
|
|
216
230
|
|
|
217
231
|
|
|
218
232
|
def start_monitor_forwarder
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
233
|
+
if Reactor.native_fiber_scheduler?
|
|
234
|
+
monitor_io = io_for_native_fd(@native.monitor_fd)
|
|
235
|
+
@parent_task.async(transient: true, annotation: "rust-monitor") do
|
|
236
|
+
until @closed
|
|
237
|
+
monitor_io.wait_readable
|
|
238
|
+
monitor_io.read_nonblock(256, exception: false)
|
|
239
|
+
while (data = @native.try_recv_monitor)
|
|
240
|
+
track_connection_event(data)
|
|
241
|
+
@monitor_queue.enqueue(MonitorEvent.new(**data))
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
rescue IOError, Errno::EBADF
|
|
245
|
+
ensure
|
|
246
|
+
close_io_wrapper(monitor_io)
|
|
247
|
+
end
|
|
248
|
+
else
|
|
249
|
+
FdWatcher.watch_loop(@native.monitor_fd, owner: self) do |io|
|
|
250
|
+
io.read_nonblock(256, exception: false)
|
|
224
251
|
while (data = @native.try_recv_monitor)
|
|
225
252
|
track_connection_event(data)
|
|
226
253
|
@monitor_queue.enqueue(MonitorEvent.new(**data))
|
|
@@ -230,6 +257,19 @@ module OMQ
|
|
|
230
257
|
end
|
|
231
258
|
|
|
232
259
|
|
|
260
|
+
def io_for_native_fd(fd)
|
|
261
|
+
IO.for_fd(fd, autoclose: false)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def close_io_wrapper(io)
|
|
266
|
+
return unless io && !io.closed?
|
|
267
|
+
|
|
268
|
+
io.close
|
|
269
|
+
rescue IOError, SystemCallError
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
|
|
233
273
|
def track_connection_event(data)
|
|
234
274
|
detail = data[:detail] || {}
|
|
235
275
|
connection_id = detail[:connection_id]
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Rust
|
|
5
|
+
class FdWatcher
|
|
6
|
+
Watch = Struct.new(:id, :io, :owner, :once, :callback, keyword_init: true)
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def watch_once(fd, owner:, &block)
|
|
10
|
+
watch(fd, owner: owner, once: true, &block)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def watch_loop(fd, owner:, &block)
|
|
15
|
+
watch(fd, owner: owner, once: false, &block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def unwatch_owner(owner)
|
|
20
|
+
removed = nil
|
|
21
|
+
|
|
22
|
+
mutex.synchronize do
|
|
23
|
+
reset_after_fork
|
|
24
|
+
removed = watchers.values.select { |watch| watch.owner.equal?(owner) }
|
|
25
|
+
removed.each { |watch| watchers.delete(watch.id) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
removed.each { |watch| close_io(watch.io) }
|
|
29
|
+
wake
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def watch(fd, owner:, once:, &block)
|
|
37
|
+
raise ArgumentError, "block required" unless block
|
|
38
|
+
|
|
39
|
+
io = IO.for_fd(fd, autoclose: false)
|
|
40
|
+
mutex.synchronize do
|
|
41
|
+
reset_after_fork
|
|
42
|
+
ensure_started
|
|
43
|
+
id = next_id
|
|
44
|
+
watchers[id] = Watch.new(id: id, io: io, owner: owner, once: once, callback: block)
|
|
45
|
+
id
|
|
46
|
+
end
|
|
47
|
+
rescue StandardError
|
|
48
|
+
close_io(io)
|
|
49
|
+
raise
|
|
50
|
+
ensure
|
|
51
|
+
wake if io
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def reset_after_fork
|
|
56
|
+
pid = Process.pid
|
|
57
|
+
return if @pid == pid
|
|
58
|
+
|
|
59
|
+
watchers.each_value { |watch| close_io(watch.io) }
|
|
60
|
+
close_io(@wake_r)
|
|
61
|
+
close_io(@wake_w)
|
|
62
|
+
@watchers = {}
|
|
63
|
+
@wake_r = nil
|
|
64
|
+
@wake_w = nil
|
|
65
|
+
@thread = nil
|
|
66
|
+
@next_id = 0
|
|
67
|
+
@pid = pid
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def ensure_started
|
|
72
|
+
return if @thread&.alive?
|
|
73
|
+
|
|
74
|
+
@wake_r, @wake_w = IO.pipe
|
|
75
|
+
@thread = Thread.new do
|
|
76
|
+
Thread.current.name = "omq-rust-watch" if Thread.current.respond_to?(:name=)
|
|
77
|
+
run
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def run
|
|
83
|
+
loop do
|
|
84
|
+
wake_io, current = snapshot
|
|
85
|
+
readable = [wake_io, *current.map(&:io)].reject(&:closed?)
|
|
86
|
+
ready = IO.select(readable)&.first || []
|
|
87
|
+
|
|
88
|
+
drain_wake(wake_io) if ready.include?(wake_io)
|
|
89
|
+
|
|
90
|
+
current.each do |watch|
|
|
91
|
+
next unless ready.include?(watch.io)
|
|
92
|
+
|
|
93
|
+
dispatch(watch)
|
|
94
|
+
end
|
|
95
|
+
rescue IOError, SystemCallError
|
|
96
|
+
sweep_closed
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def dispatch(watch)
|
|
102
|
+
active = watch.once ? delete_if_current(watch) : current?(watch)
|
|
103
|
+
return unless active
|
|
104
|
+
|
|
105
|
+
watch.callback.call(watch.io)
|
|
106
|
+
rescue StandardError
|
|
107
|
+
delete_if_current(watch)
|
|
108
|
+
ensure
|
|
109
|
+
close_io(watch.io) if watch.once
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def snapshot
|
|
114
|
+
mutex.synchronize { [@wake_r, watchers.values] }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def current?(watch)
|
|
119
|
+
mutex.synchronize { watchers[watch.id].equal?(watch) }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def delete_if_current(watch)
|
|
124
|
+
mutex.synchronize do
|
|
125
|
+
if watchers[watch.id].equal?(watch)
|
|
126
|
+
watchers.delete(watch.id)
|
|
127
|
+
true
|
|
128
|
+
else
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def sweep_closed
|
|
136
|
+
removed = nil
|
|
137
|
+
mutex.synchronize do
|
|
138
|
+
removed = watchers.values.select { |watch| watch.io.closed? }
|
|
139
|
+
removed.each { |watch| watchers.delete(watch.id) }
|
|
140
|
+
end
|
|
141
|
+
removed.each { |watch| close_io(watch.io) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def drain_wake(io)
|
|
146
|
+
loop do
|
|
147
|
+
result = io.read_nonblock(256, exception: false)
|
|
148
|
+
break if result == :wait_readable || result.nil? || result.empty?
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def wake
|
|
154
|
+
return unless @wake_w && !@wake_w.closed?
|
|
155
|
+
|
|
156
|
+
@wake_w.write_nonblock(".", exception: false)
|
|
157
|
+
rescue IOError, SystemCallError
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def close_io(io)
|
|
162
|
+
io.close if io && !io.closed?
|
|
163
|
+
rescue IOError, SystemCallError
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def watchers
|
|
168
|
+
@watchers ||= {}
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def mutex
|
|
173
|
+
@mutex ||= Mutex.new
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def next_id
|
|
178
|
+
@next_id ||= 0
|
|
179
|
+
@next_id += 1
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
data/lib/omq/rust/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omq-backend-rust
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -53,15 +53,18 @@ files:
|
|
|
53
53
|
- LICENSE
|
|
54
54
|
- README.md
|
|
55
55
|
- ext/omq_backend_rust/Cargo.toml
|
|
56
|
+
- ext/omq_backend_rust/build.rs
|
|
56
57
|
- ext/omq_backend_rust/extconf.rb
|
|
57
58
|
- ext/omq_backend_rust/src/error.rs
|
|
58
59
|
- ext/omq_backend_rust/src/lib.rs
|
|
59
60
|
- ext/omq_backend_rust/src/notify.rs
|
|
60
61
|
- ext/omq_backend_rust/src/options.rs
|
|
62
|
+
- ext/omq_backend_rust/src/rb.rs
|
|
61
63
|
- ext/omq_backend_rust/src/runtime.rs
|
|
62
64
|
- ext/omq_backend_rust/src/socket.rs
|
|
63
65
|
- lib/omq/backend/rust.rb
|
|
64
66
|
- lib/omq/rust/engine.rb
|
|
67
|
+
- lib/omq/rust/fd_watcher.rb
|
|
65
68
|
- lib/omq/rust/version.rb
|
|
66
69
|
homepage: https://github.com/zeromq/omq.rb/tree/main/gems/omq-backend-rust
|
|
67
70
|
licenses:
|