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.
@@ -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 = 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
- @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 ||= IO.for_fd(@native.send_fd, autoclose: false)
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 = IO.for_fd(@native.recv_fd, autoclose: false)
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
- io = IO.for_fd(fd, autoclose: false)
210
- @parent_task.async(transient: true) do
211
- io.wait_readable
212
- promise.resolve(true) unless promise.resolved? || @closed
213
- rescue IOError, Errno::EBADF
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
- monitor_io = IO.for_fd(@native.monitor_fd, autoclose: false)
220
- @parent_task.async(transient: true, annotation: "rust-monitor") do
221
- loop do
222
- monitor_io.wait_readable
223
- monitor_io.read_nonblock(256, exception: false)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OMQ
4
4
  module Rust
5
- VERSION = "0.1.7"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
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.1.7
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: