omq-rs 0.2.5 → 0.2.7
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 +19 -0
- data/ext/omq_rs_native/Cargo.toml +3 -3
- data/ext/omq_rs_native/src/auth.rs +71 -76
- data/ext/omq_rs_native/src/options.rs +134 -99
- data/ext/omq_rs_native/src/runtime.rs +237 -174
- data/ext/omq_rs_native/src/socket.rs +89 -61
- data/lib/omq/rs/socket.rb +6 -4
- data/lib/omq/rs/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39bae86e920966ad2f4ec940ffb54a150447afd6950b6692105b67414c93658f
|
|
4
|
+
data.tar.gz: 706fef1a060bf0bdd1b120d75e0094b883d30c91e21d91bcfa25829e9034213f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ac452e8ed4a8e39e6a35a494fb6c425794ec40ee443184c78a5c8392317880d41d60a74caa8d748ad19bc263fc943d18f04254b23cd8559a63fda8faf8fd0e9
|
|
7
|
+
data.tar.gz: 4dbfc59cbdd388c5f3e478b62d1ddcc83bd8f96e29eebf5c7d09cefbed1c7319de37bd1750b8bd59a189c3c0749886b8c1f57dbd243e00fc8492db10846b3fd2
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.7] - 2026-10-20
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Bundle `omq-tokio` 0.30.0 and `omq-proto` 0.34.0 with adaptive Zstd
|
|
10
|
+
levels, handshake flood limits, and wakeup and lifecycle fixes.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Socket construction no longer releases the GVL under the materialization
|
|
15
|
+
lock, and close serializes with wrapper setup.
|
|
16
|
+
|
|
17
|
+
## [0.2.6] - 2026-10-16
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Require `omq-tokio` 0.29.0 and `omq-proto` 0.33.0 with the latest PEER
|
|
22
|
+
limits and fan-out send readiness fixes.
|
|
23
|
+
|
|
5
24
|
## [0.2.5] - 2026-10-15
|
|
6
25
|
|
|
7
26
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "omq_rs_native"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.7"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.93"
|
|
6
6
|
license = "ISC"
|
|
@@ -20,8 +20,8 @@ zstd = ["omq-tokio/zstd"]
|
|
|
20
20
|
ws = ["omq-tokio/ws"]
|
|
21
21
|
|
|
22
22
|
[dependencies]
|
|
23
|
-
omq-proto = { version = "0.
|
|
24
|
-
omq-tokio = { version = "0.
|
|
23
|
+
omq-proto = { version = "0.34.0", default-features = false }
|
|
24
|
+
omq-tokio = { version = "0.30.0", default-features = false }
|
|
25
25
|
yring = { version = "0.3.20", features = ["async"] }
|
|
26
26
|
|
|
27
27
|
bytes = "1.12.0"
|
|
@@ -10,13 +10,18 @@ use crate::notify::PipeNotify;
|
|
|
10
10
|
use crate::options::parse_curve_public_key;
|
|
11
11
|
use crate::rb::{self, RbResult};
|
|
12
12
|
|
|
13
|
+
/// Handshake facts handed to the Ruby auth callback as a hash.
|
|
14
|
+
struct AuthPeer {
|
|
15
|
+
public_key: [u8; 32],
|
|
16
|
+
identity: Option<Bytes>,
|
|
17
|
+
peer_address: Option<String>,
|
|
18
|
+
username: Option<String>,
|
|
19
|
+
password: Option<String>,
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
enum AuthRequest {
|
|
14
23
|
Check {
|
|
15
|
-
|
|
16
|
-
identity: Option<Bytes>,
|
|
17
|
-
peer_address: Option<String>,
|
|
18
|
-
username: Option<String>,
|
|
19
|
-
password: Option<String>,
|
|
24
|
+
peer: AuthPeer,
|
|
20
25
|
reply: flume::Sender<bool>,
|
|
21
26
|
},
|
|
22
27
|
Stop,
|
|
@@ -85,78 +90,18 @@ fn wait_for_request(data: &WorkerData) -> Option<AuthRequest> {
|
|
|
85
90
|
unsafe extern "C" fn auth_worker_main(data: *mut c_void) -> VALUE {
|
|
86
91
|
let data = unsafe { Box::from_raw(data.cast::<WorkerData>()) };
|
|
87
92
|
let _ = catch_unwind(AssertUnwindSafe(|| {
|
|
88
|
-
while let Some(AuthRequest::Check {
|
|
89
|
-
|
|
90
|
-
identity,
|
|
91
|
-
peer_address,
|
|
92
|
-
username,
|
|
93
|
-
password,
|
|
94
|
-
reply,
|
|
95
|
-
}) = wait_for_request(&data)
|
|
96
|
-
{
|
|
97
|
-
let accepted = invoke_callback(
|
|
98
|
-
data.callback,
|
|
99
|
-
public_key,
|
|
100
|
-
identity.as_ref(),
|
|
101
|
-
peer_address.as_deref(),
|
|
102
|
-
username.as_deref(),
|
|
103
|
-
password.as_deref(),
|
|
104
|
-
);
|
|
93
|
+
while let Some(AuthRequest::Check { peer, reply }) = wait_for_request(&data) {
|
|
94
|
+
let accepted = invoke_callback(data.callback, &peer);
|
|
105
95
|
let _ = reply.send(accepted);
|
|
106
96
|
}
|
|
107
97
|
}));
|
|
108
98
|
rb::qnil()
|
|
109
99
|
}
|
|
110
100
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
peer_address: Option<&str>,
|
|
116
|
-
username: Option<&str>,
|
|
117
|
-
password: Option<&str>,
|
|
118
|
-
) -> bool {
|
|
119
|
-
let result = (|| -> RbResult<VALUE> {
|
|
120
|
-
let peer = rb::hash_new()?;
|
|
121
|
-
let key = if username.is_some() {
|
|
122
|
-
rb::qnil()
|
|
123
|
-
} else {
|
|
124
|
-
#[cfg(feature = "curve")]
|
|
125
|
-
{
|
|
126
|
-
let key = omq_proto::CurvePublicKey::from_bytes(public_key)
|
|
127
|
-
.to_z85()
|
|
128
|
-
.into_bytes();
|
|
129
|
-
rb::new_binary_string(&key)?
|
|
130
|
-
}
|
|
131
|
-
#[cfg(not(feature = "curve"))]
|
|
132
|
-
{
|
|
133
|
-
let _ = public_key;
|
|
134
|
-
rb::qnil()
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
rb::hash_aset(peer, rb::symbol("public_key")?, key)?;
|
|
138
|
-
let identity = match identity {
|
|
139
|
-
Some(value) => rb::new_binary_string(value)?,
|
|
140
|
-
None => rb::qnil(),
|
|
141
|
-
};
|
|
142
|
-
rb::hash_aset(peer, rb::symbol("identity")?, identity)?;
|
|
143
|
-
let peer_address = match peer_address {
|
|
144
|
-
Some(value) => rb::new_utf8_string(value)?,
|
|
145
|
-
None => rb::qnil(),
|
|
146
|
-
};
|
|
147
|
-
rb::hash_aset(peer, rb::symbol("peer_address")?, peer_address)?;
|
|
148
|
-
let username = match username {
|
|
149
|
-
Some(value) => rb::new_utf8_string(value)?,
|
|
150
|
-
None => rb::qnil(),
|
|
151
|
-
};
|
|
152
|
-
rb::hash_aset(peer, rb::symbol("username")?, username)?;
|
|
153
|
-
let password = match password {
|
|
154
|
-
Some(value) => rb::new_utf8_string(value)?,
|
|
155
|
-
None => rb::qnil(),
|
|
156
|
-
};
|
|
157
|
-
rb::hash_aset(peer, rb::symbol("password")?, password)?;
|
|
158
|
-
rb::call_method_1(callback, c"call", peer)
|
|
159
|
-
})();
|
|
101
|
+
/// Call the Ruby callback with the peer hash; any truthy result accepts.
|
|
102
|
+
/// A raised exception rejects the peer and clears Ruby's error state.
|
|
103
|
+
fn invoke_callback(callback: VALUE, peer: &AuthPeer) -> bool {
|
|
104
|
+
let result = peer_hash(peer).and_then(|hash| rb::call_method_1(callback, c"call", hash));
|
|
160
105
|
|
|
161
106
|
if let Ok(value) = result {
|
|
162
107
|
value != rb::qfalse() && value != rb::qnil()
|
|
@@ -166,6 +111,54 @@ fn invoke_callback(
|
|
|
166
111
|
}
|
|
167
112
|
}
|
|
168
113
|
|
|
114
|
+
fn peer_hash(peer: &AuthPeer) -> RbResult<VALUE> {
|
|
115
|
+
let hash = rb::hash_new()?;
|
|
116
|
+
|
|
117
|
+
// PLAIN peers carry a username; only CURVE peers expose a public key.
|
|
118
|
+
let key = if peer.username.is_some() {
|
|
119
|
+
rb::qnil()
|
|
120
|
+
} else {
|
|
121
|
+
#[cfg(feature = "curve")]
|
|
122
|
+
{
|
|
123
|
+
let key = omq_proto::CurvePublicKey::from_bytes(peer.public_key)
|
|
124
|
+
.to_z85()
|
|
125
|
+
.into_bytes();
|
|
126
|
+
rb::new_binary_string(&key)?
|
|
127
|
+
}
|
|
128
|
+
#[cfg(not(feature = "curve"))]
|
|
129
|
+
{
|
|
130
|
+
let _ = peer.public_key;
|
|
131
|
+
rb::qnil()
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
rb::hash_aset(hash, rb::symbol("public_key")?, key)?;
|
|
135
|
+
|
|
136
|
+
let identity = match &peer.identity {
|
|
137
|
+
Some(value) => rb::new_binary_string(value)?,
|
|
138
|
+
None => rb::qnil(),
|
|
139
|
+
};
|
|
140
|
+
rb::hash_aset(hash, rb::symbol("identity")?, identity)?;
|
|
141
|
+
|
|
142
|
+
let peer_address = match &peer.peer_address {
|
|
143
|
+
Some(value) => rb::new_utf8_string(value)?,
|
|
144
|
+
None => rb::qnil(),
|
|
145
|
+
};
|
|
146
|
+
rb::hash_aset(hash, rb::symbol("peer_address")?, peer_address)?;
|
|
147
|
+
|
|
148
|
+
let username = match &peer.username {
|
|
149
|
+
Some(value) => rb::new_utf8_string(value)?,
|
|
150
|
+
None => rb::qnil(),
|
|
151
|
+
};
|
|
152
|
+
rb::hash_aset(hash, rb::symbol("username")?, username)?;
|
|
153
|
+
|
|
154
|
+
let password = match &peer.password {
|
|
155
|
+
Some(value) => rb::new_utf8_string(value)?,
|
|
156
|
+
None => rb::qnil(),
|
|
157
|
+
};
|
|
158
|
+
rb::hash_aset(hash, rb::symbol("password")?, password)?;
|
|
159
|
+
Ok(hash)
|
|
160
|
+
}
|
|
161
|
+
|
|
169
162
|
#[cfg(feature = "curve")]
|
|
170
163
|
pub fn allowed_keys(value: VALUE) -> RbResult<omq_proto::Authenticator> {
|
|
171
164
|
let count = rb::array_len(value)?;
|
|
@@ -207,11 +200,13 @@ pub fn callback(callback: VALUE) -> RbResult<(omq_proto::Authenticator, AuthWork
|
|
|
207
200
|
let authenticator = omq_proto::Authenticator::new(move |peer| {
|
|
208
201
|
let (reply, result) = flume::bounded(1);
|
|
209
202
|
let request = AuthRequest::Check {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
203
|
+
peer: AuthPeer {
|
|
204
|
+
public_key: peer.public_key,
|
|
205
|
+
identity: peer.identity.clone(),
|
|
206
|
+
peer_address: peer.peer_address.clone(),
|
|
207
|
+
username: peer.username.clone(),
|
|
208
|
+
password: peer.password.clone(),
|
|
209
|
+
},
|
|
215
210
|
reply,
|
|
216
211
|
};
|
|
217
212
|
if auth_sender.send(request).is_err() {
|
|
@@ -6,15 +6,28 @@ use rb_sys::VALUE;
|
|
|
6
6
|
|
|
7
7
|
use crate::rb::{self, RbResult, RubyErr};
|
|
8
8
|
|
|
9
|
-
#[expect(
|
|
10
|
-
clippy::too_many_lines,
|
|
11
|
-
reason = "flat option mapping mirrors omq-proto Options fields"
|
|
12
|
-
)]
|
|
13
9
|
pub fn build_options(hash: VALUE) -> RbResult<omq_tokio::Options> {
|
|
14
10
|
rb::check_hash(hash)?;
|
|
15
11
|
|
|
12
|
+
// Groups run in a fixed order, so the first invalid option reported
|
|
13
|
+
// stays the same. Each maps fields one to one onto omq-proto Options.
|
|
16
14
|
let mut opts = omq_tokio::Options::default();
|
|
15
|
+
apply_queue_options(hash, &mut opts)?;
|
|
16
|
+
apply_timing_options(hash, &mut opts)?;
|
|
17
|
+
apply_transport_options(hash, &mut opts)?;
|
|
18
|
+
apply_compression_options(hash, &mut opts)?;
|
|
19
|
+
apply_policy_options(hash, &mut opts)?;
|
|
20
|
+
if let Some(mechanism_type) = mechanism_type(hash)? {
|
|
21
|
+
apply_mechanism(hash, &mechanism_type, &mut opts)?;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
opts.validate()
|
|
25
|
+
.map_err(|error| RubyErr::arg(error.to_string()))?;
|
|
26
|
+
Ok(opts)
|
|
27
|
+
}
|
|
17
28
|
|
|
29
|
+
/// Queue limits, receive rate limits, workload, linger, and identity.
|
|
30
|
+
fn apply_queue_options(hash: VALUE, opts: &mut omq_tokio::Options) -> RbResult<()> {
|
|
18
31
|
if let Some(v) = get_opt_u32(hash, "send_hwm")? {
|
|
19
32
|
opts.send_hwm = v;
|
|
20
33
|
}
|
|
@@ -56,6 +69,11 @@ pub fn build_options(hash: VALUE) -> RbResult<omq_tokio::Options> {
|
|
|
56
69
|
if let Some(v) = get_opt_bool(hash, "conflate")? {
|
|
57
70
|
opts.conflate = v;
|
|
58
71
|
}
|
|
72
|
+
Ok(())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Heartbeat and handshake timing.
|
|
76
|
+
fn apply_timing_options(hash: VALUE, opts: &mut omq_tokio::Options) -> RbResult<()> {
|
|
59
77
|
if let Some(v) = get_opt_duration(hash, "heartbeat_interval")? {
|
|
60
78
|
opts.heartbeat_interval = Some(v);
|
|
61
79
|
}
|
|
@@ -71,6 +89,11 @@ pub fn build_options(hash: VALUE) -> RbResult<omq_tokio::Options> {
|
|
|
71
89
|
if let Some(v) = get_opt_usize(hash, "max_pending_handshakes")? {
|
|
72
90
|
opts.max_pending_handshakes = v;
|
|
73
91
|
}
|
|
92
|
+
Ok(())
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Message size limits, buffers, framing thresholds, and delivery flags.
|
|
96
|
+
fn apply_transport_options(hash: VALUE, opts: &mut omq_tokio::Options) -> RbResult<()> {
|
|
74
97
|
if let Some(v) = get_opt_usize(hash, "max_message_size")? {
|
|
75
98
|
opts.max_message_size = Some(v);
|
|
76
99
|
}
|
|
@@ -95,6 +118,11 @@ pub fn build_options(hash: VALUE) -> RbResult<omq_tokio::Options> {
|
|
|
95
118
|
if let Some(v) = get_opt_bool(hash, "reconnect_stop_conn_refused")? {
|
|
96
119
|
opts.reconnect_stop_conn_refused = v;
|
|
97
120
|
}
|
|
121
|
+
Ok(())
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Compression dictionary, training, level, and offload.
|
|
125
|
+
fn apply_compression_options(hash: VALUE, opts: &mut omq_tokio::Options) -> RbResult<()> {
|
|
98
126
|
if let Some(v) = get_opt_bytes(hash, "compression_dict")? {
|
|
99
127
|
opts.compression_dict = (!v.is_empty()).then(|| Bytes::from(v));
|
|
100
128
|
}
|
|
@@ -115,8 +143,14 @@ pub fn build_options(hash: VALUE) -> RbResult<omq_tokio::Options> {
|
|
|
115
143
|
opts.max_recv_dict_size = Some(v);
|
|
116
144
|
}
|
|
117
145
|
if let Some(v) = get_opt_i64(hash, "compression_offload_threshold")? {
|
|
146
|
+
// Negative disables offload.
|
|
118
147
|
opts.compression_offload_threshold = if v < 0 { None } else { Some(v as usize) };
|
|
119
148
|
}
|
|
149
|
+
Ok(())
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Mute and reconnect policies.
|
|
153
|
+
fn apply_policy_options(hash: VALUE, opts: &mut omq_tokio::Options) -> RbResult<()> {
|
|
120
154
|
if let Some(v) = get_opt_string(hash, "on_mute")? {
|
|
121
155
|
opts.on_mute = match v.as_str() {
|
|
122
156
|
"drop_newest" | "drop" => omq_tokio::OnMute::DropNewest,
|
|
@@ -143,37 +177,37 @@ pub fn build_options(hash: VALUE) -> RbResult<omq_tokio::Options> {
|
|
|
143
177
|
max: duration_from_seconds("reconnect_interval max", max)?,
|
|
144
178
|
};
|
|
145
179
|
}
|
|
180
|
+
Ok(())
|
|
181
|
+
}
|
|
146
182
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
.map_err(|error| RubyErr::arg(error.to_string()))?;
|
|
176
|
-
Ok(opts)
|
|
183
|
+
/// Explicit `mechanism_type`, or one inferred from CURVE or PLAIN keys.
|
|
184
|
+
/// CURVE wins when both kinds of keys are present.
|
|
185
|
+
fn mechanism_type(hash: VALUE) -> RbResult<Option<String>> {
|
|
186
|
+
if let Some(mechanism_type) = get_opt_string(hash, "mechanism_type")? {
|
|
187
|
+
return Ok(Some(mechanism_type));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let curve = option_present(
|
|
191
|
+
hash,
|
|
192
|
+
&[
|
|
193
|
+
"curve_server",
|
|
194
|
+
"curve_publickey",
|
|
195
|
+
"curve_public_key",
|
|
196
|
+
"curve_secretkey",
|
|
197
|
+
"curve_secret_key",
|
|
198
|
+
"curve_serverkey",
|
|
199
|
+
"curve_server_key",
|
|
200
|
+
],
|
|
201
|
+
)?;
|
|
202
|
+
let plain = option_present(hash, &["plain_server", "plain_username", "plain_password"])?;
|
|
203
|
+
|
|
204
|
+
Ok(if curve {
|
|
205
|
+
Some("curve".to_owned())
|
|
206
|
+
} else if plain {
|
|
207
|
+
Some("plain".to_owned())
|
|
208
|
+
} else {
|
|
209
|
+
None
|
|
210
|
+
})
|
|
177
211
|
}
|
|
178
212
|
|
|
179
213
|
fn apply_mechanism(hash: VALUE, mech_type: &str, opts: &mut omq_tokio::Options) -> RbResult<()> {
|
|
@@ -181,75 +215,12 @@ fn apply_mechanism(hash: VALUE, mech_type: &str, opts: &mut omq_tokio::Options)
|
|
|
181
215
|
"null" => {}
|
|
182
216
|
|
|
183
217
|
#[cfg(feature = "curve")]
|
|
184
|
-
"curve" =>
|
|
185
|
-
let is_server =
|
|
186
|
-
get_opt_bool_alias(hash, &["curve_server", "mechanism_server"])?.unwrap_or(false);
|
|
187
|
-
let pub_key = get_opt_bytes_alias(
|
|
188
|
-
hash,
|
|
189
|
-
&[
|
|
190
|
-
"curve_publickey",
|
|
191
|
-
"curve_public_key",
|
|
192
|
-
"mechanism_public_key",
|
|
193
|
-
],
|
|
194
|
-
)?;
|
|
195
|
-
let sec_key = get_opt_bytes_alias(
|
|
196
|
-
hash,
|
|
197
|
-
&[
|
|
198
|
-
"curve_secretkey",
|
|
199
|
-
"curve_secret_key",
|
|
200
|
-
"mechanism_secret_key",
|
|
201
|
-
],
|
|
202
|
-
)?;
|
|
203
|
-
|
|
204
|
-
if is_server {
|
|
205
|
-
let public = parse_curve_public_key(
|
|
206
|
-
&pub_key
|
|
207
|
-
.ok_or_else(|| RubyErr::arg("CURVE server requires curve_publickey"))?,
|
|
208
|
-
"curve_publickey",
|
|
209
|
-
)?;
|
|
210
|
-
let secret = curve_secret_key(
|
|
211
|
-
&sec_key
|
|
212
|
-
.ok_or_else(|| RubyErr::arg("CURVE server requires curve_secretkey"))?,
|
|
213
|
-
"curve_secretkey",
|
|
214
|
-
)?;
|
|
215
|
-
opts.mechanism = omq_proto::MechanismSetup::CurveServer {
|
|
216
|
-
our_keypair: omq_proto::CurveKeypair { public, secret },
|
|
217
|
-
options: omq_proto::CurveServerOptions::default(),
|
|
218
|
-
};
|
|
219
|
-
} else {
|
|
220
|
-
let server_key = get_opt_bytes_alias(
|
|
221
|
-
hash,
|
|
222
|
-
&[
|
|
223
|
-
"curve_serverkey",
|
|
224
|
-
"curve_server_key",
|
|
225
|
-
"mechanism_server_key",
|
|
226
|
-
],
|
|
227
|
-
)?;
|
|
228
|
-
let public = parse_curve_public_key(
|
|
229
|
-
&pub_key
|
|
230
|
-
.ok_or_else(|| RubyErr::arg("CURVE client requires curve_publickey"))?,
|
|
231
|
-
"curve_publickey",
|
|
232
|
-
)?;
|
|
233
|
-
let secret = curve_secret_key(
|
|
234
|
-
&sec_key
|
|
235
|
-
.ok_or_else(|| RubyErr::arg("CURVE client requires curve_secretkey"))?,
|
|
236
|
-
"curve_secretkey",
|
|
237
|
-
)?;
|
|
238
|
-
let server_public = parse_curve_public_key(
|
|
239
|
-
&server_key
|
|
240
|
-
.ok_or_else(|| RubyErr::arg("CURVE client requires curve_serverkey"))?,
|
|
241
|
-
"curve_serverkey",
|
|
242
|
-
)?;
|
|
243
|
-
opts.mechanism = omq_proto::MechanismSetup::CurveClient {
|
|
244
|
-
our_keypair: omq_proto::CurveKeypair { public, secret },
|
|
245
|
-
server_public,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
}
|
|
218
|
+
"curve" => apply_curve_mechanism(hash, opts)?,
|
|
249
219
|
|
|
250
220
|
#[cfg(feature = "plain")]
|
|
251
221
|
"plain" => {
|
|
252
222
|
if get_opt_bool_alias(hash, &["plain_server", "mechanism_server"])?.unwrap_or(false) {
|
|
223
|
+
// Fail closed until an authenticator is installed on the socket.
|
|
253
224
|
opts.mechanism = omq_proto::MechanismSetup::PlainServer {
|
|
254
225
|
authenticator: omq_proto::Authenticator::new(|_| false),
|
|
255
226
|
};
|
|
@@ -269,6 +240,70 @@ fn apply_mechanism(hash: VALUE, mech_type: &str, opts: &mut omq_tokio::Options)
|
|
|
269
240
|
Ok(())
|
|
270
241
|
}
|
|
271
242
|
|
|
243
|
+
#[cfg(feature = "curve")]
|
|
244
|
+
fn apply_curve_mechanism(hash: VALUE, opts: &mut omq_tokio::Options) -> RbResult<()> {
|
|
245
|
+
let is_server =
|
|
246
|
+
get_opt_bool_alias(hash, &["curve_server", "mechanism_server"])?.unwrap_or(false);
|
|
247
|
+
let pub_key = get_opt_bytes_alias(
|
|
248
|
+
hash,
|
|
249
|
+
&[
|
|
250
|
+
"curve_publickey",
|
|
251
|
+
"curve_public_key",
|
|
252
|
+
"mechanism_public_key",
|
|
253
|
+
],
|
|
254
|
+
)?;
|
|
255
|
+
let sec_key = get_opt_bytes_alias(
|
|
256
|
+
hash,
|
|
257
|
+
&[
|
|
258
|
+
"curve_secretkey",
|
|
259
|
+
"curve_secret_key",
|
|
260
|
+
"mechanism_secret_key",
|
|
261
|
+
],
|
|
262
|
+
)?;
|
|
263
|
+
|
|
264
|
+
if is_server {
|
|
265
|
+
let public = parse_curve_public_key(
|
|
266
|
+
&pub_key.ok_or_else(|| RubyErr::arg("CURVE server requires curve_publickey"))?,
|
|
267
|
+
"curve_publickey",
|
|
268
|
+
)?;
|
|
269
|
+
let secret = curve_secret_key(
|
|
270
|
+
&sec_key.ok_or_else(|| RubyErr::arg("CURVE server requires curve_secretkey"))?,
|
|
271
|
+
"curve_secretkey",
|
|
272
|
+
)?;
|
|
273
|
+
opts.mechanism = omq_proto::MechanismSetup::CurveServer {
|
|
274
|
+
our_keypair: omq_proto::CurveKeypair { public, secret },
|
|
275
|
+
options: omq_proto::CurveServerOptions::default(),
|
|
276
|
+
};
|
|
277
|
+
return Ok(());
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
let server_key = get_opt_bytes_alias(
|
|
281
|
+
hash,
|
|
282
|
+
&[
|
|
283
|
+
"curve_serverkey",
|
|
284
|
+
"curve_server_key",
|
|
285
|
+
"mechanism_server_key",
|
|
286
|
+
],
|
|
287
|
+
)?;
|
|
288
|
+
let public = parse_curve_public_key(
|
|
289
|
+
&pub_key.ok_or_else(|| RubyErr::arg("CURVE client requires curve_publickey"))?,
|
|
290
|
+
"curve_publickey",
|
|
291
|
+
)?;
|
|
292
|
+
let secret = curve_secret_key(
|
|
293
|
+
&sec_key.ok_or_else(|| RubyErr::arg("CURVE client requires curve_secretkey"))?,
|
|
294
|
+
"curve_secretkey",
|
|
295
|
+
)?;
|
|
296
|
+
let server_public = parse_curve_public_key(
|
|
297
|
+
&server_key.ok_or_else(|| RubyErr::arg("CURVE client requires curve_serverkey"))?,
|
|
298
|
+
"curve_serverkey",
|
|
299
|
+
)?;
|
|
300
|
+
opts.mechanism = omq_proto::MechanismSetup::CurveClient {
|
|
301
|
+
our_keypair: omq_proto::CurveKeypair { public, secret },
|
|
302
|
+
server_public,
|
|
303
|
+
};
|
|
304
|
+
Ok(())
|
|
305
|
+
}
|
|
306
|
+
|
|
272
307
|
#[cfg(feature = "curve")]
|
|
273
308
|
pub(crate) fn parse_curve_public_key(
|
|
274
309
|
bytes: &[u8],
|
|
@@ -11,12 +11,10 @@ use tokio::task::JoinHandle;
|
|
|
11
11
|
|
|
12
12
|
use crate::notify::PipeNotify;
|
|
13
13
|
|
|
14
|
-
type Job = Box<dyn FnOnce() + Send + 'static>;
|
|
15
|
-
|
|
16
14
|
struct RuntimeState {
|
|
17
15
|
pid: u32,
|
|
18
16
|
handle: Handle,
|
|
19
|
-
|
|
17
|
+
_keepalive: flume::Sender<()>,
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
static RUNTIME: Mutex<Option<RuntimeState>> = Mutex::new(None);
|
|
@@ -34,7 +32,7 @@ pub fn ensure_runtime(io_threads: usize) -> Handle {
|
|
|
34
32
|
{
|
|
35
33
|
return rt.handle.clone();
|
|
36
34
|
}
|
|
37
|
-
let (tx, rx) = flume::
|
|
35
|
+
let (tx, rx) = flume::bounded::<()>(1);
|
|
38
36
|
let (handle_tx, handle_rx) = flume::bounded::<Handle>(1);
|
|
39
37
|
let n = io_threads.max(1);
|
|
40
38
|
thread::Builder::new()
|
|
@@ -54,9 +52,7 @@ pub fn ensure_runtime(io_threads: usize) -> Handle {
|
|
|
54
52
|
};
|
|
55
53
|
let _ = handle_tx.send(rt.handle().clone());
|
|
56
54
|
rt.block_on(async move {
|
|
57
|
-
|
|
58
|
-
job();
|
|
59
|
-
}
|
|
55
|
+
let _ = rx.recv_async().await;
|
|
60
56
|
});
|
|
61
57
|
})
|
|
62
58
|
.expect("omq-rs: spawn tokio thread");
|
|
@@ -64,23 +60,11 @@ pub fn ensure_runtime(io_threads: usize) -> Handle {
|
|
|
64
60
|
*guard = Some(RuntimeState {
|
|
65
61
|
pid,
|
|
66
62
|
handle: handle.clone(),
|
|
67
|
-
|
|
63
|
+
_keepalive: tx,
|
|
68
64
|
});
|
|
69
65
|
handle
|
|
70
66
|
}
|
|
71
67
|
|
|
72
|
-
fn submit_job(io_threads: usize) -> flume::Sender<Job> {
|
|
73
|
-
let guard = RUNTIME.lock().unwrap();
|
|
74
|
-
if let Some(ref rt) = *guard
|
|
75
|
-
&& rt.pid == std::process::id()
|
|
76
|
-
{
|
|
77
|
-
return rt.submit.clone();
|
|
78
|
-
}
|
|
79
|
-
drop(guard);
|
|
80
|
-
ensure_runtime(io_threads);
|
|
81
|
-
RUNTIME.lock().unwrap().as_ref().unwrap().submit.clone()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
68
|
fn recv_blocking<T>(rx: flume::Receiver<T>, missing: &'static str) -> T {
|
|
85
69
|
struct RecvBox<U> {
|
|
86
70
|
rx: flume::Receiver<U>,
|
|
@@ -128,16 +112,14 @@ pub struct Materialized {
|
|
|
128
112
|
pub recv_notify: Arc<PipeNotify>,
|
|
129
113
|
pub send_notify: Arc<PipeNotify>,
|
|
130
114
|
pub recv_space: Arc<tokio::sync::Notify>,
|
|
131
|
-
pub send_pump: JoinHandle<()>,
|
|
132
115
|
pub send_waiting: tokio::sync::watch::Sender<bool>,
|
|
133
|
-
pub recv_pump: JoinHandle<()>,
|
|
134
116
|
|
|
135
117
|
pub monitor_rx: flume::Receiver<MonitorEventData>,
|
|
136
118
|
pub monitor_notify: Arc<PipeNotify>,
|
|
137
119
|
pub peer_connected_notify: Arc<PipeNotify>,
|
|
138
120
|
pub all_peers_gone_notify: Arc<PipeNotify>,
|
|
139
121
|
pub subscriber_joined_notify: Arc<PipeNotify>,
|
|
140
|
-
pub
|
|
122
|
+
pub tasks: SocketTasks,
|
|
141
123
|
}
|
|
142
124
|
|
|
143
125
|
#[derive(Clone)]
|
|
@@ -154,17 +136,24 @@ pub enum MonitorValue {
|
|
|
154
136
|
Text(String),
|
|
155
137
|
}
|
|
156
138
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
reason = "one exhaustive match keeps monitor event conversion auditable"
|
|
160
|
-
)]
|
|
139
|
+
/// Map a monitor event to the Ruby-facing name, endpoint, and detail pairs.
|
|
140
|
+
/// Event kinds this binding does not know map to "unknown".
|
|
161
141
|
fn convert_monitor_event(event: &omq_tokio::MonitorEvent) -> MonitorEventData {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
142
|
+
connection_event(event)
|
|
143
|
+
.or_else(|| attempt_event(event))
|
|
144
|
+
.or_else(|| subscription_event(event))
|
|
145
|
+
.unwrap_or_else(|| MonitorEventData {
|
|
146
|
+
event_type: "unknown",
|
|
147
|
+
endpoint: None,
|
|
148
|
+
detail: vec![],
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/// Listener and connection lifecycle events.
|
|
153
|
+
fn connection_event(event: &omq_tokio::MonitorEvent) -> Option<MonitorEventData> {
|
|
154
|
+
use omq_tokio::MonitorEvent::{Accepted, Connected, Disconnected, Listening};
|
|
155
|
+
|
|
156
|
+
let data = match event {
|
|
168
157
|
Listening { endpoint } => MonitorEventData {
|
|
169
158
|
event_type: "listening",
|
|
170
159
|
endpoint: Some(endpoint.to_string()),
|
|
@@ -188,6 +177,29 @@ fn convert_monitor_event(event: &omq_tokio::MonitorEvent) -> MonitorEventData {
|
|
|
188
177
|
endpoint: Some(endpoint.to_string()),
|
|
189
178
|
detail: vec![("connection_id", MonitorValue::Integer(*connection_id))],
|
|
190
179
|
},
|
|
180
|
+
Disconnected {
|
|
181
|
+
endpoint,
|
|
182
|
+
peer,
|
|
183
|
+
reason,
|
|
184
|
+
} => MonitorEventData {
|
|
185
|
+
event_type: "disconnected",
|
|
186
|
+
endpoint: Some(endpoint.to_string()),
|
|
187
|
+
detail: vec![
|
|
188
|
+
("reason", MonitorValue::Text(format!("{reason:?}"))),
|
|
189
|
+
("connection_id", MonitorValue::Integer(peer.connection_id)),
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
_ => return None,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
Some(data)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// Connect retries and handshake outcomes.
|
|
199
|
+
fn attempt_event(event: &omq_tokio::MonitorEvent) -> Option<MonitorEventData> {
|
|
200
|
+
use omq_tokio::MonitorEvent::{ConnectDelayed, HandshakeFailed, HandshakeSucceeded};
|
|
201
|
+
|
|
202
|
+
let data = match event {
|
|
191
203
|
HandshakeSucceeded { endpoint, peer } => {
|
|
192
204
|
let mut detail = vec![("connection_id", MonitorValue::Integer(peer.connection_id))];
|
|
193
205
|
if let Some(ref ident) = peer.peer_identity
|
|
@@ -223,18 +235,19 @@ fn convert_monitor_event(event: &omq_tokio::MonitorEvent) -> MonitorEventData {
|
|
|
223
235
|
("attempt", MonitorValue::Integer(u64::from(*attempt))),
|
|
224
236
|
],
|
|
225
237
|
},
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
_ => return None,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
Some(data)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/// Subscription and group traffic, plus socket close.
|
|
245
|
+
fn subscription_event(event: &omq_tokio::MonitorEvent) -> Option<MonitorEventData> {
|
|
246
|
+
use omq_tokio::MonitorEvent::{
|
|
247
|
+
Closed, JoinReceived, LeaveReceived, SubscribeReceived, UnsubscribeReceived,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
let data = match event {
|
|
238
251
|
SubscribeReceived { prefix } => MonitorEventData {
|
|
239
252
|
event_type: "subscribe_received",
|
|
240
253
|
endpoint: None,
|
|
@@ -260,12 +273,10 @@ fn convert_monitor_event(event: &omq_tokio::MonitorEvent) -> MonitorEventData {
|
|
|
260
273
|
endpoint: None,
|
|
261
274
|
detail: vec![],
|
|
262
275
|
},
|
|
263
|
-
_ =>
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
},
|
|
268
|
-
}
|
|
276
|
+
_ => return None,
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
Some(data)
|
|
269
280
|
}
|
|
270
281
|
|
|
271
282
|
async fn push_to_ring(
|
|
@@ -295,142 +306,188 @@ async fn push_to_ring(
|
|
|
295
306
|
}
|
|
296
307
|
}
|
|
297
308
|
|
|
298
|
-
|
|
309
|
+
/// Pump ends and wake signals between the Ruby-facing rings and the socket.
|
|
310
|
+
pub struct PumpChannels {
|
|
311
|
+
pub send_cons: yring::AsyncConsumer<omq_tokio::Message>,
|
|
312
|
+
pub send_notify: Arc<PipeNotify>,
|
|
313
|
+
pub send_waiting: tokio::sync::watch::Sender<bool>,
|
|
314
|
+
pub recv_prod: yring::Producer<omq_tokio::Message>,
|
|
315
|
+
pub recv_notify: Arc<PipeNotify>,
|
|
316
|
+
pub recv_space: Arc<tokio::sync::Notify>,
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// Where the monitor pump reports events and one-shot peer milestones.
|
|
320
|
+
pub struct MonitorSinks {
|
|
321
|
+
pub events: flume::Sender<MonitorEventData>,
|
|
322
|
+
pub notify: Arc<PipeNotify>,
|
|
323
|
+
pub peer_connected: Arc<PipeNotify>,
|
|
324
|
+
pub all_peers_gone: Arc<PipeNotify>,
|
|
325
|
+
pub subscriber_joined: Arc<PipeNotify>,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// Background tasks owned by one materialized socket.
|
|
329
|
+
pub struct SocketTasks {
|
|
330
|
+
pub send: JoinHandle<()>,
|
|
331
|
+
pub recv: JoinHandle<()>,
|
|
332
|
+
pub monitor: JoinHandle<()>,
|
|
333
|
+
}
|
|
334
|
+
|
|
299
335
|
pub fn materialize(
|
|
300
336
|
io_threads: usize,
|
|
301
337
|
socket_type: omq_tokio::SocketType,
|
|
302
338
|
options: omq_tokio::Options,
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
339
|
+
pumps: PumpChannels,
|
|
340
|
+
monitor: MonitorSinks,
|
|
341
|
+
) -> (Arc<InnerSocket>, SocketTasks) {
|
|
342
|
+
// Construction only spawns tasks. Keep the GVL while the caller holds
|
|
343
|
+
// its materialization lock; waiting without it would let close take
|
|
344
|
+
// the GVL and block on that same lock.
|
|
345
|
+
let handle = ensure_runtime(io_threads);
|
|
346
|
+
let _guard = handle.enter();
|
|
347
|
+
let sock = Arc::new(InnerSocket::new(socket_type, options));
|
|
348
|
+
|
|
349
|
+
let send_pump = tokio::spawn(run_send_pump(
|
|
350
|
+
sock.clone(),
|
|
351
|
+
pumps.send_cons,
|
|
352
|
+
pumps.send_notify,
|
|
353
|
+
pumps.send_waiting,
|
|
354
|
+
));
|
|
355
|
+
let recv_pump = tokio::spawn(run_recv_pump(
|
|
356
|
+
sock.clone(),
|
|
357
|
+
pumps.recv_prod,
|
|
358
|
+
pumps.recv_notify,
|
|
359
|
+
pumps.recv_space,
|
|
360
|
+
));
|
|
361
|
+
|
|
362
|
+
// Subscribe here, not inside the task: the broadcast receiver keeps
|
|
363
|
+
// events emitted before the task first runs.
|
|
364
|
+
let stream = sock.monitor();
|
|
365
|
+
let monitor_pump = tokio::spawn(run_monitor_pump(sock.clone(), stream, monitor));
|
|
366
|
+
|
|
367
|
+
let tasks = SocketTasks {
|
|
368
|
+
send: send_pump,
|
|
369
|
+
recv: recv_pump,
|
|
370
|
+
monitor: monitor_pump,
|
|
371
|
+
};
|
|
372
|
+
(sock, tasks)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/// Feed queued Ruby sends to the socket with a drain budget. REP instead
|
|
376
|
+
/// only watches send capacity.
|
|
377
|
+
async fn run_send_pump(
|
|
378
|
+
s: Arc<InnerSocket>,
|
|
379
|
+
mut send_cons: yring::AsyncConsumer<omq_tokio::Message>,
|
|
380
|
+
sn: Arc<PipeNotify>,
|
|
307
381
|
send_waiting: tokio::sync::watch::Sender<bool>,
|
|
308
|
-
recv_space: Arc<tokio::sync::Notify>,
|
|
309
|
-
monitor_tx: flume::Sender<MonitorEventData>,
|
|
310
|
-
monitor_notify: Arc<PipeNotify>,
|
|
311
|
-
peer_connected_notify: Arc<PipeNotify>,
|
|
312
|
-
all_peers_gone_notify: Arc<PipeNotify>,
|
|
313
|
-
subscriber_joined_notify: Arc<PipeNotify>,
|
|
314
|
-
) -> (
|
|
315
|
-
Arc<InnerSocket>,
|
|
316
|
-
JoinHandle<()>,
|
|
317
|
-
JoinHandle<()>,
|
|
318
|
-
JoinHandle<()>,
|
|
319
382
|
) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
383
|
+
if s.socket_type() == omq_tokio::SocketType::Rep {
|
|
384
|
+
// REP submits at application send. Watch only failed sends;
|
|
385
|
+
// a successful retry cancels an obsolete capacity wait.
|
|
386
|
+
watch_rep_send_capacity(&s, &send_waiting, &sn).await;
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
let mut budget = omq_proto::flow::DrainBudget::new(256, 1024 * 1024);
|
|
391
|
+
while let Some(msg) = futures::StreamExt::next(&mut send_cons).await {
|
|
392
|
+
let byte_len = msg.byte_len();
|
|
393
|
+
// Release the ring slot before the send so Ruby can refill while
|
|
394
|
+
// this message is in flight.
|
|
395
|
+
send_cons.release();
|
|
396
|
+
sn.notify();
|
|
397
|
+
let _ = s.send(msg).await;
|
|
398
|
+
if !budget.account(byte_len) {
|
|
399
|
+
budget.reset();
|
|
400
|
+
tokio::task::yield_now().await;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
sn.notify();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/// Move received messages into the Ruby ring in budgeted batches, waking
|
|
407
|
+
/// the reader once per batch.
|
|
408
|
+
async fn run_recv_pump(
|
|
409
|
+
s: Arc<InnerSocket>,
|
|
410
|
+
mut recv_prod: yring::Producer<omq_tokio::Message>,
|
|
411
|
+
rn: Arc<PipeNotify>,
|
|
412
|
+
rs: Arc<tokio::sync::Notify>,
|
|
413
|
+
) {
|
|
414
|
+
loop {
|
|
415
|
+
match s.recv_for_external_recv().await {
|
|
416
|
+
Ok(msg) => {
|
|
417
|
+
let mut budget = omq_proto::flow::DrainBudget::WORKER;
|
|
418
|
+
let mut more = budget.account(msg.byte_len());
|
|
419
|
+
push_to_ring(&mut recv_prod, msg, &rs).await;
|
|
420
|
+
|
|
421
|
+
while more && !recv_prod.is_full() {
|
|
422
|
+
match s.try_recv_for_external_recv() {
|
|
423
|
+
Ok(msg) => {
|
|
424
|
+
more = budget.account(msg.byte_len());
|
|
425
|
+
push_to_ring(&mut recv_prod, msg, &rs).await;
|
|
426
|
+
}
|
|
427
|
+
Err(_) => break,
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
recv_prod.flush();
|
|
432
|
+
rn.force_wake();
|
|
433
|
+
if !more {
|
|
343
434
|
tokio::task::yield_now().await;
|
|
344
435
|
}
|
|
345
436
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
Ok(msg) => {
|
|
363
|
-
more = budget.account(msg.byte_len());
|
|
364
|
-
push_to_ring(&mut recv_prod, msg, &rs).await;
|
|
365
|
-
}
|
|
366
|
-
Err(_) => break,
|
|
367
|
-
}
|
|
368
|
-
}
|
|
437
|
+
Err(omq_tokio::Error::Closed) => break,
|
|
438
|
+
Err(_) => {}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/// Forward monitor events to Ruby and fire the one-shot peer milestones.
|
|
444
|
+
async fn run_monitor_pump(
|
|
445
|
+
peer_ready_sock: Arc<InnerSocket>,
|
|
446
|
+
mut stream: omq_tokio::MonitorStream,
|
|
447
|
+
sinks: MonitorSinks,
|
|
448
|
+
) {
|
|
449
|
+
let mut peer_count: u32 = 0;
|
|
450
|
+
let mut had_peers = false;
|
|
451
|
+
let mut peer_connected_fired = false;
|
|
452
|
+
let mut subscriber_joined_fired = false;
|
|
369
453
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
454
|
+
loop {
|
|
455
|
+
match stream.recv().await {
|
|
456
|
+
Ok(event) => {
|
|
457
|
+
match &event {
|
|
458
|
+
omq_tokio::MonitorEvent::HandshakeSucceeded { .. } => {
|
|
459
|
+
peer_count += 1;
|
|
460
|
+
had_peers = true;
|
|
461
|
+
if !peer_connected_fired {
|
|
462
|
+
peer_connected_fired = true;
|
|
463
|
+
let _ = peer_ready_sock.connections().await;
|
|
464
|
+
sinks.peer_connected.force_wake();
|
|
374
465
|
}
|
|
375
466
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
let mut stream = sock.monitor();
|
|
383
|
-
let peer_ready_sock = sock.clone();
|
|
384
|
-
let monitor_pump = tokio::spawn(async move {
|
|
385
|
-
let mut peer_count: u32 = 0;
|
|
386
|
-
let mut had_peers = false;
|
|
387
|
-
let mut peer_connected_fired = false;
|
|
388
|
-
let mut subscriber_joined_fired = false;
|
|
389
|
-
|
|
390
|
-
loop {
|
|
391
|
-
match stream.recv().await {
|
|
392
|
-
Ok(event) => {
|
|
393
|
-
match &event {
|
|
394
|
-
omq_tokio::MonitorEvent::HandshakeSucceeded { .. } => {
|
|
395
|
-
peer_count += 1;
|
|
396
|
-
had_peers = true;
|
|
397
|
-
if !peer_connected_fired {
|
|
398
|
-
peer_connected_fired = true;
|
|
399
|
-
let _ = peer_ready_sock.connections().await;
|
|
400
|
-
peer_connected_notify.force_wake();
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
omq_tokio::MonitorEvent::Disconnected { .. } => {
|
|
404
|
-
peer_count = peer_count.saturating_sub(1);
|
|
405
|
-
if had_peers && peer_count == 0 {
|
|
406
|
-
all_peers_gone_notify.force_wake();
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
omq_tokio::MonitorEvent::SubscribeReceived { .. }
|
|
410
|
-
| omq_tokio::MonitorEvent::JoinReceived { .. }
|
|
411
|
-
if !subscriber_joined_fired =>
|
|
412
|
-
{
|
|
413
|
-
subscriber_joined_fired = true;
|
|
414
|
-
subscriber_joined_notify.force_wake();
|
|
415
|
-
}
|
|
416
|
-
_ => {}
|
|
467
|
+
omq_tokio::MonitorEvent::Disconnected { .. } => {
|
|
468
|
+
peer_count = peer_count.saturating_sub(1);
|
|
469
|
+
if had_peers && peer_count == 0 {
|
|
470
|
+
sinks.all_peers_gone.force_wake();
|
|
417
471
|
}
|
|
418
|
-
|
|
419
|
-
let data = convert_monitor_event(&event);
|
|
420
|
-
let _ = monitor_tx.try_send(data);
|
|
421
|
-
monitor_notify.notify();
|
|
422
472
|
}
|
|
423
|
-
|
|
424
|
-
|
|
473
|
+
omq_tokio::MonitorEvent::SubscribeReceived { .. }
|
|
474
|
+
| omq_tokio::MonitorEvent::JoinReceived { .. }
|
|
475
|
+
if !subscriber_joined_fired =>
|
|
476
|
+
{
|
|
477
|
+
subscriber_joined_fired = true;
|
|
478
|
+
sinks.subscriber_joined.force_wake();
|
|
479
|
+
}
|
|
480
|
+
_ => {}
|
|
425
481
|
}
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
482
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
483
|
+
let data = convert_monitor_event(&event);
|
|
484
|
+
let _ = sinks.events.try_send(data);
|
|
485
|
+
sinks.notify.notify();
|
|
486
|
+
}
|
|
487
|
+
Err(omq_tokio::MonitorRecvError::Lagged(_)) => {}
|
|
488
|
+
Err(_) => break,
|
|
489
|
+
}
|
|
490
|
+
}
|
|
434
491
|
}
|
|
435
492
|
|
|
436
493
|
async fn watch_rep_send_capacity(
|
|
@@ -454,15 +511,21 @@ async fn watch_rep_send_capacity(
|
|
|
454
511
|
}
|
|
455
512
|
}
|
|
456
513
|
|
|
514
|
+
/// Stop the pumps and close the socket, giving queued sends up to the
|
|
515
|
+
/// linger time (30 s when unset, at least 10 ms) to flush.
|
|
457
516
|
pub fn destroy_socket(
|
|
458
517
|
io_threads: usize,
|
|
459
518
|
sock: Arc<InnerSocket>,
|
|
460
519
|
send_prod: Mutex<yring::AsyncProducer<omq_tokio::Message>>,
|
|
461
|
-
|
|
462
|
-
recv_pump: JoinHandle<()>,
|
|
463
|
-
monitor_pump: JoinHandle<()>,
|
|
520
|
+
tasks: SocketTasks,
|
|
464
521
|
linger: Option<Duration>,
|
|
465
522
|
) {
|
|
523
|
+
let SocketTasks {
|
|
524
|
+
send: mut send_pump,
|
|
525
|
+
recv: recv_pump,
|
|
526
|
+
monitor: monitor_pump,
|
|
527
|
+
} = tasks;
|
|
528
|
+
|
|
466
529
|
recv_pump.abort();
|
|
467
530
|
monitor_pump.abort();
|
|
468
531
|
if sock.socket_type() == omq_tokio::SocketType::Rep {
|
|
@@ -344,7 +344,18 @@ fn rust_socket_materialize_impl(rb_self: &RustSocket) -> RbResult<()> {
|
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
#[cfg(feature = "plain")]
|
|
347
|
-
|
|
347
|
+
ensure_plain_auth_configured(rb_self)?;
|
|
348
|
+
|
|
349
|
+
let opts = rb_self.options.lock().unwrap().take().unwrap_or_default();
|
|
350
|
+
*slot = Some(spawn_materialized(rb_self.socket_type, opts));
|
|
351
|
+
Ok(())
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/// A PLAIN server without an installed authenticator would reject every
|
|
355
|
+
/// client; fail loudly at materialization instead.
|
|
356
|
+
#[cfg(feature = "plain")]
|
|
357
|
+
fn ensure_plain_auth_configured(rb_self: &RustSocket) -> RbResult<()> {
|
|
358
|
+
let plain_server = rb_self
|
|
348
359
|
.options
|
|
349
360
|
.lock()
|
|
350
361
|
.unwrap()
|
|
@@ -354,15 +365,21 @@ fn rust_socket_materialize_impl(rb_self: &RustSocket) -> RbResult<()> {
|
|
|
354
365
|
options.mechanism,
|
|
355
366
|
omq_proto::MechanismSetup::PlainServer { .. }
|
|
356
367
|
)
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
{
|
|
368
|
+
});
|
|
369
|
+
if plain_server && !rb_self.plain_auth_configured.load(Ordering::Acquire) {
|
|
360
370
|
return Err(RubyErr::runtime(
|
|
361
371
|
"PLAIN server requires explicit authentication via set_plain_auth",
|
|
362
372
|
));
|
|
363
373
|
}
|
|
374
|
+
Ok(())
|
|
375
|
+
}
|
|
364
376
|
|
|
365
|
-
|
|
377
|
+
/// Build the Ruby-facing rings and signals, then create the socket and
|
|
378
|
+
/// its pumps on the runtime.
|
|
379
|
+
fn spawn_materialized(
|
|
380
|
+
socket_type: omq_tokio::SocketType,
|
|
381
|
+
opts: omq_tokio::Options,
|
|
382
|
+
) -> Materialized {
|
|
366
383
|
let send_cap = opts.send_hwm.max(1) as usize;
|
|
367
384
|
let recv_cap = opts.recv_hwm.max(1) as usize;
|
|
368
385
|
let (send_prod, send_cons) = yring::async_spsc(send_cap);
|
|
@@ -378,41 +395,38 @@ fn rust_socket_materialize_impl(rb_self: &RustSocket) -> RbResult<()> {
|
|
|
378
395
|
let all_peers_gone_notify = Arc::new(PipeNotify::new());
|
|
379
396
|
let subscriber_joined_notify = Arc::new(PipeNotify::new());
|
|
380
397
|
|
|
381
|
-
let
|
|
382
|
-
io_threads(),
|
|
383
|
-
rb_self.socket_type,
|
|
384
|
-
opts,
|
|
398
|
+
let pumps = runtime::PumpChannels {
|
|
385
399
|
send_cons,
|
|
400
|
+
send_notify: send_notify.clone(),
|
|
401
|
+
send_waiting: send_waiting.clone(),
|
|
386
402
|
recv_prod,
|
|
387
|
-
recv_notify.clone(),
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
monitor_tx,
|
|
392
|
-
monitor_notify.clone(),
|
|
393
|
-
peer_connected_notify.clone(),
|
|
394
|
-
all_peers_gone_notify.clone(),
|
|
395
|
-
subscriber_joined_notify.clone(),
|
|
396
|
-
|
|
403
|
+
recv_notify: recv_notify.clone(),
|
|
404
|
+
recv_space: recv_space.clone(),
|
|
405
|
+
};
|
|
406
|
+
let monitor = runtime::MonitorSinks {
|
|
407
|
+
events: monitor_tx,
|
|
408
|
+
notify: monitor_notify.clone(),
|
|
409
|
+
peer_connected: peer_connected_notify.clone(),
|
|
410
|
+
all_peers_gone: all_peers_gone_notify.clone(),
|
|
411
|
+
subscriber_joined: subscriber_joined_notify.clone(),
|
|
412
|
+
};
|
|
413
|
+
let (socket, tasks) = runtime::materialize(io_threads(), socket_type, opts, pumps, monitor);
|
|
397
414
|
|
|
398
|
-
|
|
415
|
+
Materialized {
|
|
399
416
|
socket,
|
|
400
417
|
send_prod: Mutex::new(send_prod),
|
|
401
418
|
recv_cons: Mutex::new(recv_cons),
|
|
402
419
|
recv_notify,
|
|
403
420
|
send_notify,
|
|
404
421
|
recv_space,
|
|
405
|
-
send_pump,
|
|
406
422
|
send_waiting,
|
|
407
|
-
recv_pump,
|
|
408
423
|
monitor_rx,
|
|
409
424
|
monitor_notify,
|
|
410
425
|
peer_connected_notify,
|
|
411
426
|
all_peers_gone_notify,
|
|
412
427
|
subscriber_joined_notify,
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
Ok(())
|
|
428
|
+
tasks,
|
|
429
|
+
}
|
|
416
430
|
}
|
|
417
431
|
|
|
418
432
|
unsafe extern "C" fn rust_socket_materialize(rb_self: VALUE) -> VALUE {
|
|
@@ -931,15 +945,7 @@ fn rust_socket_close_impl(rb_self: &RustSocket, wait_for_auth_worker: bool) {
|
|
|
931
945
|
m.subscriber_joined_notify.force_wake();
|
|
932
946
|
m.monitor_notify.force_wake();
|
|
933
947
|
let linger = *rb_self.linger.lock().unwrap();
|
|
934
|
-
runtime::destroy_socket(
|
|
935
|
-
io_threads(),
|
|
936
|
-
m.socket,
|
|
937
|
-
m.send_prod,
|
|
938
|
-
m.send_pump,
|
|
939
|
-
m.recv_pump,
|
|
940
|
-
m.monitor_pump,
|
|
941
|
-
linger,
|
|
942
|
-
);
|
|
948
|
+
runtime::destroy_socket(io_threads(), m.socket, m.send_prod, m.tasks, linger);
|
|
943
949
|
}
|
|
944
950
|
}
|
|
945
951
|
|
|
@@ -1085,33 +1091,55 @@ pub fn register(native: VALUE) -> RbResult<()> {
|
|
|
1085
1091
|
rb::undef_alloc_func(class)?;
|
|
1086
1092
|
rb::define_singleton_method_1(class, c"new", rust_socket_new)?;
|
|
1087
1093
|
rb::define_method_1(class, c"set_options", rust_socket_set_options)?;
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
#[cfg(feature = "curve")]
|
|
1097
|
+
register_curve_auth_methods(class)?;
|
|
1098
|
+
#[cfg(feature = "plain")]
|
|
1099
|
+
register_plain_auth_methods(class)?;
|
|
1100
|
+
register_io_methods(class)?;
|
|
1101
|
+
|
|
1102
|
+
Ok(())
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
#[cfg(feature = "curve")]
|
|
1106
|
+
fn register_curve_auth_methods(class: VALUE) -> RbResult<()> {
|
|
1107
|
+
unsafe {
|
|
1108
|
+
rb::define_method_1(
|
|
1109
|
+
class,
|
|
1110
|
+
c"set_curve_auth_keys",
|
|
1111
|
+
rust_socket_set_curve_auth_keys,
|
|
1112
|
+
)?;
|
|
1113
|
+
rb::define_method_1(
|
|
1114
|
+
class,
|
|
1115
|
+
c"set_curve_auth_callback",
|
|
1116
|
+
rust_socket_set_curve_auth_callback,
|
|
1117
|
+
)?;
|
|
1118
|
+
rb::define_method_0(class, c"clear_curve_auth", rust_socket_clear_curve_auth)?;
|
|
1119
|
+
}
|
|
1120
|
+
Ok(())
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
#[cfg(feature = "plain")]
|
|
1124
|
+
fn register_plain_auth_methods(class: VALUE) -> RbResult<()> {
|
|
1125
|
+
unsafe {
|
|
1126
|
+
rb::define_method_1(
|
|
1127
|
+
class,
|
|
1128
|
+
c"set_plain_auth_credentials",
|
|
1129
|
+
rust_socket_set_plain_auth_credentials,
|
|
1130
|
+
)?;
|
|
1131
|
+
rb::define_method_1(
|
|
1132
|
+
class,
|
|
1133
|
+
c"set_plain_auth_callback",
|
|
1134
|
+
rust_socket_set_plain_auth_callback,
|
|
1135
|
+
)?;
|
|
1136
|
+
}
|
|
1137
|
+
Ok(())
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/// Lifecycle, endpoint, message, readiness fd, and subscription methods.
|
|
1141
|
+
fn register_io_methods(class: VALUE) -> RbResult<()> {
|
|
1142
|
+
unsafe {
|
|
1115
1143
|
rb::define_method_0(class, c"materialize", rust_socket_materialize)?;
|
|
1116
1144
|
rb::define_method_1(class, c"bind", rust_socket_bind)?;
|
|
1117
1145
|
rb::define_method_1(class, c"connect", rust_socket_connect)?;
|
data/lib/omq/rs/socket.rb
CHANGED
|
@@ -671,11 +671,13 @@ module OMQ
|
|
|
671
671
|
#
|
|
672
672
|
# @return [nil]
|
|
673
673
|
def close
|
|
674
|
-
|
|
674
|
+
@materialize_lock.synchronize do
|
|
675
|
+
return if closed?
|
|
675
676
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
677
|
+
@native.close
|
|
678
|
+
close_wrapper(@recv_io)
|
|
679
|
+
close_wrapper(@send_io)
|
|
680
|
+
end
|
|
679
681
|
nil
|
|
680
682
|
end
|
|
681
683
|
|
data/lib/omq/rs/version.rb
CHANGED