omq-backend-rust 0.1.7 → 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 +34 -0
- data/README.md +9 -5
- data/lib/omq/backend/rust.rb +14 -7
- data/lib/omq/rust/engine.rb +100 -90
- data/lib/omq/rust/fd_watcher.rb +184 -0
- 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 +11 -19
- data/Cargo.toml +0 -3
- data/ext/omq_backend_rust/Cargo.toml +0 -34
- data/ext/omq_backend_rust/extconf.rb +0 -8
- data/ext/omq_backend_rust/src/error.rs +0 -15
- data/ext/omq_backend_rust/src/lib.rs +0 -26
- data/ext/omq_backend_rust/src/notify.rs +0 -69
- data/ext/omq_backend_rust/src/options.rs +0 -207
- data/ext/omq_backend_rust/src/runtime.rs +0 -493
- data/ext/omq_backend_rust/src/socket.rs +0 -478
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f12671673afe657015b59cf65873132c88c5cbb15cf34962b54318fb944310c
|
|
4
|
+
data.tar.gz: 3b7de77e6e01dc461fa0aa969870472442a2f9b3b4657fa5b35ca1fe90bd8940
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 77dda34fcf69ef4d048d74276b1ae5cd455b23086de0a826f751862014e0d048dd8f27ccc2042f5332ff91a5410f11243f687450ca0093e58ab19377b64ec9dd
|
|
7
|
+
data.tar.gz: 2536562047da85d828e732a03c5a551a85b2b0774821f3ada32f890ba48f4453696c0243ef5e66248cbdee763623d1cf0f9b45bb74ac5e9aedba6aab014558ed
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.3.0] - 2026-08-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added JRuby support through OMQ.java Maven artifacts for Linux x86_64,
|
|
10
|
+
macOS aarch64, macOS x86_64, and Windows x86_64.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Replaced the private MRI/TruffleRuby native extension with the first-class
|
|
15
|
+
`omq-rs` binding.
|
|
16
|
+
|
|
17
|
+
## [0.2.0] - 2026-08-08
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Added TruffleRuby support for the Rust backend.
|
|
22
|
+
- Added a shared fallback file descriptor watcher for Rubies without a
|
|
23
|
+
native `Fiber.scheduler`.
|
|
24
|
+
- Added TruffleRuby CI coverage for the Rust backend.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Updated to `omq-tokio` 0.21.1 and `omq-proto` 0.25.1.
|
|
29
|
+
- Replaced Magnus with a small `rb-sys` helper layer that uses public
|
|
30
|
+
Ruby C API calls.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Raised Ruby exceptions through public C API entry points for
|
|
35
|
+
TruffleRuby compatibility.
|
|
36
|
+
- Rejected invalid negative numeric options before they can overflow Rust
|
|
37
|
+
sizes or panic on invalid durations.
|
|
38
|
+
|
|
5
39
|
## [0.1.7] - 2026-08-02
|
|
6
40
|
|
|
7
41
|
### Added
|
data/README.md
CHANGED
|
@@ -6,12 +6,16 @@
|
|
|
6
6
|
[](https://www.ruby-lang.org)
|
|
7
7
|
|
|
8
8
|
Rust-backed engine for [OMQ](https://github.com/zeromq/omq.rb). Same socket API,
|
|
9
|
-
but networking runs
|
|
10
|
-
|
|
9
|
+
but networking runs through the first-class
|
|
10
|
+
[omq-rs](https://github.com/paddor/omq.rs/tree/main/bindings/ruby) binding on
|
|
11
|
+
MRI and TruffleRuby, or through OMQ.java on JRuby.
|
|
11
12
|
|
|
12
13
|
## Install
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
The `omq-rs` dependency compiles OMQ.rs during installation on MRI and
|
|
16
|
+
TruffleRuby. JRuby uses OMQ.java on Java 25 or newer. The Java gem selects the
|
|
17
|
+
matching OMQ.java artifact on Linux x86_64, macOS aarch64, macOS x86_64, and
|
|
18
|
+
Windows x86_64.
|
|
15
19
|
|
|
16
20
|
```ruby
|
|
17
21
|
# Gemfile
|
|
@@ -73,8 +77,8 @@ the first `bind` or `connect` for a Rust-backed socket.
|
|
|
73
77
|
## Development
|
|
74
78
|
|
|
75
79
|
```sh
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
OMQ_RS_PATH=/path/to/omq.rs/bindings/ruby bundle install
|
|
81
|
+
OMQ_RS_PATH=/path/to/omq.rs/bindings/ruby bundle exec rake
|
|
78
82
|
```
|
|
79
83
|
|
|
80
84
|
## License
|
data/lib/omq/backend/rust.rb
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "omq"
|
|
4
|
-
require_relative "../rust/omq_backend_rust"
|
|
5
4
|
require_relative "../rust/version"
|
|
6
|
-
require_relative "../rust/engine"
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
if RUBY_ENGINE == "jruby"
|
|
7
|
+
require_relative "../rust/java/engine"
|
|
8
|
+
else
|
|
9
|
+
require "omq/rs"
|
|
10
|
+
require_relative "../rust/engine"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if RUBY_ENGINE == "jruby"
|
|
14
|
+
module OMQ
|
|
15
|
+
module Rust
|
|
16
|
+
@io_threads = 1
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
class << self
|
|
19
|
+
attr_accessor :io_threads
|
|
20
|
+
end
|
|
14
21
|
end
|
|
15
22
|
end
|
|
16
23
|
end
|
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,20 +15,22 @@ 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
|
|
29
|
-
|
|
30
|
-
@
|
|
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 = {}
|
|
30
|
+
@socket = nil
|
|
31
|
+
@monitor_task = nil
|
|
32
|
+
@monitor_watching = false
|
|
33
|
+
@had_peer = false
|
|
31
34
|
|
|
32
35
|
@routing = RoutingStub.new(self)
|
|
33
36
|
end
|
|
@@ -40,6 +43,8 @@ module OMQ
|
|
|
40
43
|
@parent_task = parent
|
|
41
44
|
elsif Async::Task.current?
|
|
42
45
|
@parent_task = Async::Task.current
|
|
46
|
+
elsif !Reactor.native_fiber_scheduler?
|
|
47
|
+
@parent_task = nil
|
|
43
48
|
else
|
|
44
49
|
@parent_task = Reactor.root_task
|
|
45
50
|
@on_io_thread = true
|
|
@@ -52,7 +57,7 @@ module OMQ
|
|
|
52
57
|
capture_parent_task(parent: parent)
|
|
53
58
|
apply_endpoint_options!(opts)
|
|
54
59
|
ensure_materialized
|
|
55
|
-
resolved = @
|
|
60
|
+
resolved = @socket.bind(endpoint)
|
|
56
61
|
URI.parse(resolved)
|
|
57
62
|
end
|
|
58
63
|
|
|
@@ -61,54 +66,39 @@ module OMQ
|
|
|
61
66
|
capture_parent_task(parent: parent)
|
|
62
67
|
apply_endpoint_options!(opts)
|
|
63
68
|
ensure_materialized
|
|
64
|
-
@
|
|
69
|
+
@socket.connect(endpoint)
|
|
65
70
|
URI.parse(endpoint)
|
|
66
71
|
end
|
|
67
72
|
|
|
68
73
|
|
|
69
74
|
def disconnect(endpoint)
|
|
70
|
-
@
|
|
75
|
+
@socket.disconnect(endpoint)
|
|
71
76
|
end
|
|
72
77
|
|
|
73
78
|
|
|
74
79
|
def unbind(endpoint)
|
|
75
|
-
@
|
|
80
|
+
@socket.unbind(endpoint)
|
|
76
81
|
end
|
|
77
82
|
|
|
78
83
|
|
|
79
84
|
def enqueue_send(parts)
|
|
80
85
|
ensure_materialized
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@send_signal_r ||= IO.for_fd(@native.send_fd, autoclose: false)
|
|
85
|
-
loop do
|
|
86
|
-
result = @native.enqueue_send(parts)
|
|
87
|
-
return if result == :ok
|
|
88
|
-
|
|
89
|
-
@send_signal_r.wait_readable
|
|
90
|
-
@send_signal_r.read_nonblock(256, exception: false)
|
|
91
|
-
end
|
|
86
|
+
@socket.send(normalize_outgoing(parts))
|
|
87
|
+
nil
|
|
92
88
|
end
|
|
93
89
|
|
|
94
90
|
|
|
95
91
|
def dequeue_recv
|
|
96
92
|
ensure_materialized
|
|
97
93
|
|
|
98
|
-
|
|
99
|
-
return @recv_batch.shift
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
msg = try_recv_batch
|
|
94
|
+
msg = try_recv
|
|
103
95
|
return msg if msg
|
|
104
96
|
|
|
105
97
|
return take_recv_sentinel if @recv_sentinels.positive?
|
|
106
98
|
|
|
107
99
|
loop do
|
|
108
|
-
@
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
msg = try_recv_batch
|
|
100
|
+
@socket.wait_readable
|
|
101
|
+
msg = try_recv
|
|
112
102
|
return msg if msg
|
|
113
103
|
|
|
114
104
|
return take_recv_sentinel if @recv_sentinels.positive?
|
|
@@ -118,7 +108,7 @@ module OMQ
|
|
|
118
108
|
|
|
119
109
|
def dequeue_recv_sentinel
|
|
120
110
|
@recv_sentinels += 1
|
|
121
|
-
@
|
|
111
|
+
@socket.wake_recv if @materialized
|
|
122
112
|
nil
|
|
123
113
|
end
|
|
124
114
|
|
|
@@ -130,7 +120,9 @@ module OMQ
|
|
|
130
120
|
@peer_connected.resolve(nil) unless @peer_connected.resolved?
|
|
131
121
|
@all_peers_gone.resolve(nil) unless @all_peers_gone.resolved?
|
|
132
122
|
@subscriber_joined.resolve(nil) unless @subscriber_joined.resolved?
|
|
133
|
-
|
|
123
|
+
FdWatcher.unwatch_owner(self) unless Reactor.native_fiber_scheduler?
|
|
124
|
+
@socket&.close
|
|
125
|
+
@monitor_task&.stop
|
|
134
126
|
end
|
|
135
127
|
|
|
136
128
|
|
|
@@ -173,19 +165,10 @@ module OMQ
|
|
|
173
165
|
return if @materialized
|
|
174
166
|
|
|
175
167
|
capture_parent_task unless @parent_task
|
|
176
|
-
|
|
177
|
-
@
|
|
178
|
-
@
|
|
179
|
-
|
|
180
|
-
@materialized = true
|
|
181
|
-
|
|
182
|
-
@routing.replay_pending(@native)
|
|
183
|
-
|
|
184
|
-
spawn_lifecycle_watcher(@native.peer_connected_fd, @peer_connected)
|
|
185
|
-
spawn_lifecycle_watcher(@native.all_peers_gone_fd, @all_peers_gone)
|
|
186
|
-
spawn_lifecycle_watcher(@native.subscriber_joined_fd, @subscriber_joined)
|
|
187
|
-
|
|
188
|
-
start_monitor_forwarder if @monitor_queue
|
|
168
|
+
@socket = OMQ::Rust.socket(@socket_type, **extract_options.transform_keys(&:to_sym))
|
|
169
|
+
@materialized = true
|
|
170
|
+
@routing.replay_pending(@socket)
|
|
171
|
+
start_monitor_forwarder
|
|
189
172
|
end
|
|
190
173
|
|
|
191
174
|
|
|
@@ -195,55 +178,82 @@ module OMQ
|
|
|
195
178
|
end
|
|
196
179
|
|
|
197
180
|
|
|
198
|
-
def
|
|
199
|
-
|
|
200
|
-
return unless
|
|
181
|
+
def try_recv
|
|
182
|
+
message = @socket.try_recv
|
|
183
|
+
return unless message
|
|
184
|
+
return message unless @socket_type.to_sym == :SERVER
|
|
201
185
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
msg
|
|
186
|
+
routing_id, *parts = message
|
|
187
|
+
[[routing_id].pack("N"), *parts]
|
|
205
188
|
end
|
|
206
189
|
|
|
207
190
|
|
|
208
|
-
def
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
191
|
+
def normalize_outgoing(parts)
|
|
192
|
+
return parts unless @socket_type.to_sym == :SERVER
|
|
193
|
+
|
|
194
|
+
routing_id, *body = parts
|
|
195
|
+
unless routing_id.is_a?(String) && routing_id.bytesize == 4
|
|
196
|
+
raise ArgumentError, "SERVER routing ID must be a 4-byte String"
|
|
214
197
|
end
|
|
198
|
+
[routing_id.unpack1("N"), *body]
|
|
215
199
|
end
|
|
216
200
|
|
|
217
201
|
|
|
218
202
|
def start_monitor_forwarder
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
203
|
+
return if @monitor_task || @monitor_watching
|
|
204
|
+
|
|
205
|
+
if Reactor.native_fiber_scheduler?
|
|
206
|
+
@monitor_task = @parent_task.async(transient: true, annotation: "rust-monitor") do
|
|
207
|
+
monitor_loop
|
|
208
|
+
end
|
|
209
|
+
else
|
|
210
|
+
@monitor_watching = true
|
|
211
|
+
FdWatcher.watch_loop(@socket.monitor_fd, owner: self) do |io|
|
|
212
|
+
io.read_nonblock(256, exception: false)
|
|
213
|
+
while (data = @socket.try_monitor_event)
|
|
214
|
+
handle_monitor_event(data)
|
|
227
215
|
end
|
|
216
|
+
@socket.monitor_fd unless @closed
|
|
228
217
|
end
|
|
229
218
|
end
|
|
230
219
|
end
|
|
231
220
|
|
|
232
221
|
|
|
233
|
-
def
|
|
234
|
-
|
|
235
|
-
|
|
222
|
+
def monitor_loop
|
|
223
|
+
until @closed
|
|
224
|
+
data = @socket.monitor.recv
|
|
225
|
+
break unless data
|
|
226
|
+
|
|
227
|
+
handle_monitor_event(data)
|
|
228
|
+
end
|
|
229
|
+
rescue IOError, Errno::EBADF
|
|
230
|
+
raise unless @closed
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def handle_monitor_event(data)
|
|
235
|
+
type = data.fetch(:event)
|
|
236
|
+
endpoint = data[:endpoint]
|
|
237
|
+
connection_id = data[:connection_id]
|
|
236
238
|
|
|
237
|
-
case
|
|
239
|
+
case type
|
|
238
240
|
when :handshake_succeeded
|
|
241
|
+
@had_peer = true
|
|
239
242
|
@connections[connection_id || Object.new] = true
|
|
243
|
+
@peer_connected.resolve(true) unless @peer_connected.resolved?
|
|
240
244
|
when :disconnected
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
@connections.shift
|
|
245
|
+
connection_id ? @connections.delete(connection_id) : @connections.shift
|
|
246
|
+
if @had_peer && @connections.empty? && !@all_peers_gone.resolved?
|
|
247
|
+
@all_peers_gone.resolve(true)
|
|
245
248
|
end
|
|
249
|
+
when :subscribe_received
|
|
250
|
+
@subscriber_joined.resolve(true) unless @subscriber_joined.resolved?
|
|
246
251
|
end
|
|
252
|
+
|
|
253
|
+
return unless @monitor_queue
|
|
254
|
+
|
|
255
|
+
detail = data.except(:event, :endpoint)
|
|
256
|
+
@monitor_queue.enqueue(MonitorEvent.new(type:, endpoint:, detail: detail.empty? ? nil : detail))
|
|
247
257
|
end
|
|
248
258
|
|
|
249
259
|
|
|
@@ -404,9 +414,9 @@ module OMQ
|
|
|
404
414
|
|
|
405
415
|
|
|
406
416
|
def subscribe(prefix)
|
|
407
|
-
|
|
417
|
+
socket = @engine.instance_variable_get(:@socket)
|
|
408
418
|
if @engine.instance_variable_get(:@materialized)
|
|
409
|
-
|
|
419
|
+
socket.subscribe(prefix.b)
|
|
410
420
|
else
|
|
411
421
|
@pending_subscribe << prefix.b
|
|
412
422
|
end
|
|
@@ -414,14 +424,14 @@ module OMQ
|
|
|
414
424
|
|
|
415
425
|
|
|
416
426
|
def unsubscribe(prefix)
|
|
417
|
-
@engine.instance_variable_get(:@
|
|
427
|
+
@engine.instance_variable_get(:@socket).unsubscribe(prefix.b)
|
|
418
428
|
end
|
|
419
429
|
|
|
420
430
|
|
|
421
431
|
def join(group)
|
|
422
|
-
|
|
432
|
+
socket = @engine.instance_variable_get(:@socket)
|
|
423
433
|
if @engine.instance_variable_get(:@materialized)
|
|
424
|
-
|
|
434
|
+
socket.join(group)
|
|
425
435
|
else
|
|
426
436
|
@pending_join << group
|
|
427
437
|
end
|
|
@@ -429,14 +439,14 @@ module OMQ
|
|
|
429
439
|
|
|
430
440
|
|
|
431
441
|
def leave(group)
|
|
432
|
-
@engine.instance_variable_get(:@
|
|
442
|
+
@engine.instance_variable_get(:@socket).leave(group)
|
|
433
443
|
end
|
|
434
444
|
|
|
435
445
|
|
|
436
|
-
def replay_pending(
|
|
437
|
-
@pending_subscribe.each { |p|
|
|
446
|
+
def replay_pending(socket)
|
|
447
|
+
@pending_subscribe.each { |p| socket.subscribe(p) }
|
|
438
448
|
@pending_subscribe.clear
|
|
439
|
-
@pending_join.each { |g|
|
|
449
|
+
@pending_join.each { |g| socket.join(g) }
|
|
440
450
|
@pending_join.clear
|
|
441
451
|
end
|
|
442
452
|
end
|
|
@@ -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
|