omq-rs 0.1.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.
@@ -0,0 +1,459 @@
1
+ use std::future::Future;
2
+ use std::sync::atomic::{AtomicBool, Ordering};
3
+ use std::sync::{Arc, Mutex};
4
+ use std::thread;
5
+ use std::time::Duration;
6
+
7
+ use bytes::Bytes;
8
+ use omq_tokio::Socket as InnerSocket;
9
+ use tokio::runtime::Handle;
10
+ use tokio::task::JoinHandle;
11
+
12
+ use crate::notify::PipeNotify;
13
+
14
+ type Job = Box<dyn FnOnce() + Send + 'static>;
15
+
16
+ struct RuntimeState {
17
+ pid: u32,
18
+ handle: Handle,
19
+ submit: flume::Sender<Job>,
20
+ }
21
+
22
+ static RUNTIME: Mutex<Option<RuntimeState>> = Mutex::new(None);
23
+ static TERMINATED: AtomicBool = AtomicBool::new(false);
24
+
25
+ pub fn ensure_runtime(io_threads: usize) -> Handle {
26
+ assert!(
27
+ !TERMINATED.load(Ordering::Acquire),
28
+ "omq-rs: runtime terminated"
29
+ );
30
+ let mut guard = RUNTIME.lock().unwrap();
31
+ let pid = std::process::id();
32
+ if let Some(ref rt) = *guard
33
+ && rt.pid == pid
34
+ {
35
+ return rt.handle.clone();
36
+ }
37
+ let (tx, rx) = flume::unbounded::<Job>();
38
+ let (handle_tx, handle_rx) = flume::bounded::<Handle>(1);
39
+ let n = io_threads.max(1);
40
+ thread::Builder::new()
41
+ .name("omq-rust-tokio".into())
42
+ .spawn(move || {
43
+ let rt = if n <= 1 {
44
+ tokio::runtime::Builder::new_current_thread()
45
+ .enable_all()
46
+ .build()
47
+ .expect("omq-rs: tokio runtime build")
48
+ } else {
49
+ tokio::runtime::Builder::new_multi_thread()
50
+ .worker_threads(n)
51
+ .enable_all()
52
+ .build()
53
+ .expect("omq-rs: tokio runtime build")
54
+ };
55
+ let _ = handle_tx.send(rt.handle().clone());
56
+ rt.block_on(async move {
57
+ while let Ok(job) = rx.recv_async().await {
58
+ job();
59
+ }
60
+ });
61
+ })
62
+ .expect("omq-rs: spawn tokio thread");
63
+ let handle = handle_rx.recv().expect("omq-rs: runtime handle");
64
+ *guard = Some(RuntimeState {
65
+ pid,
66
+ handle: handle.clone(),
67
+ submit: tx,
68
+ });
69
+ handle
70
+ }
71
+
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
+ fn recv_blocking<T>(rx: flume::Receiver<T>, missing: &'static str) -> T {
85
+ struct RecvBox<U> {
86
+ rx: flume::Receiver<U>,
87
+ result: Option<U>,
88
+ }
89
+
90
+ extern "C" fn blocking_recv<U>(data: *mut libc::c_void) -> *mut libc::c_void {
91
+ let rd = unsafe { &mut *data.cast::<RecvBox<U>>() };
92
+ rd.result = rd.rx.recv().ok();
93
+ std::ptr::null_mut()
94
+ }
95
+
96
+ let mut rd = RecvBox { rx, result: None };
97
+ unsafe {
98
+ rb_sys::rb_thread_call_without_gvl(
99
+ Some(blocking_recv::<T>),
100
+ (&raw mut rd).cast::<libc::c_void>(),
101
+ None,
102
+ std::ptr::null_mut(),
103
+ );
104
+ }
105
+ rd.result.expect(missing)
106
+ }
107
+
108
+ pub fn spawn_blocking<F, T>(io_threads: usize, fut: F) -> T
109
+ where
110
+ F: Future<Output = T> + Send + 'static,
111
+ T: Send + 'static,
112
+ {
113
+ let handle = ensure_runtime(io_threads);
114
+ let (otx, orx) = flume::bounded::<T>(1);
115
+ handle.spawn(async move {
116
+ let out = fut.await;
117
+ let _ = otx.send(out);
118
+ });
119
+
120
+ recv_blocking(orx, "omq-rs: runtime dropped result")
121
+ }
122
+
123
+ pub struct Materialized {
124
+ pub socket: Arc<InnerSocket>,
125
+
126
+ pub send_prod: Mutex<yring::AsyncProducer<omq_tokio::Message>>,
127
+ pub recv_cons: Mutex<yring::Consumer<omq_tokio::Message>>,
128
+ pub recv_notify: Arc<PipeNotify>,
129
+ pub send_notify: Arc<PipeNotify>,
130
+ pub recv_space: Arc<tokio::sync::Notify>,
131
+ pub send_pump: JoinHandle<()>,
132
+ pub recv_pump: JoinHandle<()>,
133
+
134
+ pub monitor_rx: flume::Receiver<MonitorEventData>,
135
+ pub monitor_notify: Arc<PipeNotify>,
136
+ pub peer_connected_notify: Arc<PipeNotify>,
137
+ pub all_peers_gone_notify: Arc<PipeNotify>,
138
+ pub subscriber_joined_notify: Arc<PipeNotify>,
139
+ pub monitor_pump: JoinHandle<()>,
140
+ }
141
+
142
+ #[derive(Clone)]
143
+ pub struct MonitorEventData {
144
+ pub event_type: &'static str,
145
+ pub endpoint: Option<String>,
146
+ pub detail: Vec<(&'static str, MonitorValue)>,
147
+ }
148
+
149
+ #[derive(Clone)]
150
+ pub enum MonitorValue {
151
+ Bytes(Bytes),
152
+ Integer(u64),
153
+ Text(String),
154
+ }
155
+
156
+ #[expect(
157
+ clippy::too_many_lines,
158
+ reason = "one exhaustive match keeps monitor event conversion auditable"
159
+ )]
160
+ fn convert_monitor_event(event: &omq_tokio::MonitorEvent) -> MonitorEventData {
161
+ use omq_tokio::MonitorEvent::{
162
+ Accepted, Closed, ConnectDelayed, Connected, Disconnected, HandshakeFailed,
163
+ HandshakeSucceeded, JoinReceived, LeaveReceived, Listening, SubscribeReceived,
164
+ UnsubscribeReceived,
165
+ };
166
+ match event {
167
+ Listening { endpoint } => MonitorEventData {
168
+ event_type: "listening",
169
+ endpoint: Some(endpoint.to_string()),
170
+ detail: vec![],
171
+ },
172
+ Accepted {
173
+ endpoint,
174
+ connection_id,
175
+ ..
176
+ } => MonitorEventData {
177
+ event_type: "accepted",
178
+ endpoint: Some(endpoint.to_string()),
179
+ detail: vec![("connection_id", MonitorValue::Integer(*connection_id))],
180
+ },
181
+ Connected {
182
+ endpoint,
183
+ connection_id,
184
+ ..
185
+ } => MonitorEventData {
186
+ event_type: "connected",
187
+ endpoint: Some(endpoint.to_string()),
188
+ detail: vec![("connection_id", MonitorValue::Integer(*connection_id))],
189
+ },
190
+ HandshakeSucceeded { endpoint, peer } => {
191
+ let mut detail = vec![("connection_id", MonitorValue::Integer(peer.connection_id))];
192
+ if let Some(ref ident) = peer.peer_identity
193
+ && !ident.is_empty()
194
+ {
195
+ detail.push(("peer_identity", MonitorValue::Bytes(ident.clone())));
196
+ }
197
+ MonitorEventData {
198
+ event_type: "handshake_succeeded",
199
+ endpoint: Some(endpoint.to_string()),
200
+ detail,
201
+ }
202
+ }
203
+ HandshakeFailed {
204
+ endpoint, reason, ..
205
+ } => MonitorEventData {
206
+ event_type: "handshake_failed",
207
+ endpoint: Some(endpoint.to_string()),
208
+ detail: vec![("reason", MonitorValue::Text(reason.clone()))],
209
+ },
210
+ ConnectDelayed {
211
+ endpoint,
212
+ retry_in,
213
+ attempt,
214
+ } => MonitorEventData {
215
+ event_type: "connect_delayed",
216
+ endpoint: Some(endpoint.to_string()),
217
+ detail: vec![
218
+ (
219
+ "interval",
220
+ MonitorValue::Text(format!("{:.3}", retry_in.as_secs_f64())),
221
+ ),
222
+ ("attempt", MonitorValue::Integer(u64::from(*attempt))),
223
+ ],
224
+ },
225
+ Disconnected {
226
+ endpoint,
227
+ peer,
228
+ reason,
229
+ } => MonitorEventData {
230
+ event_type: "disconnected",
231
+ endpoint: Some(endpoint.to_string()),
232
+ detail: vec![
233
+ ("reason", MonitorValue::Text(format!("{reason:?}"))),
234
+ ("connection_id", MonitorValue::Integer(peer.connection_id)),
235
+ ],
236
+ },
237
+ SubscribeReceived { prefix } => MonitorEventData {
238
+ event_type: "subscribe_received",
239
+ endpoint: None,
240
+ detail: vec![("prefix", MonitorValue::Bytes(prefix.clone()))],
241
+ },
242
+ UnsubscribeReceived { prefix } => MonitorEventData {
243
+ event_type: "unsubscribe_received",
244
+ endpoint: None,
245
+ detail: vec![("prefix", MonitorValue::Bytes(prefix.clone()))],
246
+ },
247
+ JoinReceived { group } => MonitorEventData {
248
+ event_type: "join_received",
249
+ endpoint: None,
250
+ detail: vec![("group", MonitorValue::Bytes(group.clone()))],
251
+ },
252
+ LeaveReceived { group } => MonitorEventData {
253
+ event_type: "leave_received",
254
+ endpoint: None,
255
+ detail: vec![("group", MonitorValue::Bytes(group.clone()))],
256
+ },
257
+ Closed => MonitorEventData {
258
+ event_type: "closed",
259
+ endpoint: None,
260
+ detail: vec![],
261
+ },
262
+ _ => MonitorEventData {
263
+ event_type: "unknown",
264
+ endpoint: None,
265
+ detail: vec![],
266
+ },
267
+ }
268
+ }
269
+
270
+ async fn push_to_ring(
271
+ recv_prod: &mut yring::Producer<omq_tokio::Message>,
272
+ msg: omq_tokio::Message,
273
+ recv_space: &tokio::sync::Notify,
274
+ ) {
275
+ let mut m = msg;
276
+ loop {
277
+ match recv_prod.push(m) {
278
+ Ok(()) => break,
279
+ Err(returned) => {
280
+ recv_prod.flush();
281
+ m = returned;
282
+ let notified = recv_space.notified();
283
+ tokio::pin!(notified);
284
+ notified.as_mut().enable();
285
+ match recv_prod.push(m) {
286
+ Ok(()) => break,
287
+ Err(r2) => {
288
+ m = r2;
289
+ notified.await;
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ #[expect(clippy::too_many_arguments)]
298
+ pub fn materialize(
299
+ io_threads: usize,
300
+ socket_type: omq_tokio::SocketType,
301
+ options: omq_tokio::Options,
302
+ send_cons: yring::AsyncConsumer<omq_tokio::Message>,
303
+ mut recv_prod: yring::Producer<omq_tokio::Message>,
304
+ recv_notify: Arc<PipeNotify>,
305
+ send_notify: Arc<PipeNotify>,
306
+ recv_space: Arc<tokio::sync::Notify>,
307
+ monitor_tx: flume::Sender<MonitorEventData>,
308
+ monitor_notify: Arc<PipeNotify>,
309
+ peer_connected_notify: Arc<PipeNotify>,
310
+ all_peers_gone_notify: Arc<PipeNotify>,
311
+ subscriber_joined_notify: Arc<PipeNotify>,
312
+ ) -> (
313
+ Arc<InnerSocket>,
314
+ JoinHandle<()>,
315
+ JoinHandle<()>,
316
+ JoinHandle<()>,
317
+ ) {
318
+ let (otx, orx) = flume::bounded(1);
319
+ let tx = submit_job(io_threads);
320
+ let job: Job = Box::new(move || {
321
+ let sock = Arc::new(InnerSocket::new(socket_type, options));
322
+
323
+ let s = sock.clone();
324
+ let sn = send_notify.clone();
325
+ let send_pump = tokio::spawn(async move {
326
+ let mut send_cons = send_cons;
327
+ let mut budget = omq_proto::flow::DrainBudget::new(256, 1024 * 1024);
328
+ while let Some(msg) = futures::StreamExt::next(&mut send_cons).await {
329
+ let byte_len = msg.byte_len();
330
+ send_cons.release();
331
+ sn.notify();
332
+ let _ = s.send(msg).await;
333
+ if !budget.account(byte_len) {
334
+ budget.reset();
335
+ tokio::task::yield_now().await;
336
+ }
337
+ }
338
+ sn.notify();
339
+ });
340
+
341
+ let s = sock.clone();
342
+ let rn = recv_notify.clone();
343
+ let rs = recv_space.clone();
344
+ let recv_pump = tokio::spawn(async move {
345
+ loop {
346
+ match s.recv().await {
347
+ Ok(msg) => {
348
+ push_to_ring(&mut recv_prod, msg, &rs).await;
349
+
350
+ while !recv_prod.is_full() {
351
+ match s.try_recv() {
352
+ Ok(msg) => push_to_ring(&mut recv_prod, msg, &rs).await,
353
+ Err(_) => break,
354
+ }
355
+ }
356
+
357
+ recv_prod.flush();
358
+ rn.force_wake();
359
+ }
360
+ Err(omq_tokio::Error::Closed) => break,
361
+ Err(_) => {}
362
+ }
363
+ }
364
+ });
365
+
366
+ let monitor_sock = sock.clone();
367
+ let peer_ready_sock = sock.clone();
368
+ let monitor_pump = tokio::spawn(async move {
369
+ let mut stream = monitor_sock.monitor();
370
+ let mut peer_count: u32 = 0;
371
+ let mut had_peers = false;
372
+ let mut peer_connected_fired = false;
373
+ let mut subscriber_joined_fired = false;
374
+
375
+ loop {
376
+ match stream.recv().await {
377
+ Ok(event) => {
378
+ match &event {
379
+ omq_tokio::MonitorEvent::HandshakeSucceeded { .. } => {
380
+ peer_count += 1;
381
+ had_peers = true;
382
+ if !peer_connected_fired {
383
+ peer_connected_fired = true;
384
+ let _ = peer_ready_sock.connections().await;
385
+ peer_connected_notify.force_wake();
386
+ }
387
+ }
388
+ omq_tokio::MonitorEvent::Disconnected { .. } => {
389
+ peer_count = peer_count.saturating_sub(1);
390
+ if had_peers && peer_count == 0 {
391
+ all_peers_gone_notify.force_wake();
392
+ }
393
+ }
394
+ omq_tokio::MonitorEvent::SubscribeReceived { .. }
395
+ | omq_tokio::MonitorEvent::JoinReceived { .. }
396
+ if !subscriber_joined_fired =>
397
+ {
398
+ subscriber_joined_fired = true;
399
+ subscriber_joined_notify.force_wake();
400
+ }
401
+ _ => {}
402
+ }
403
+
404
+ let data = convert_monitor_event(&event);
405
+ let _ = monitor_tx.try_send(data);
406
+ monitor_notify.notify();
407
+ }
408
+ Err(omq_tokio::MonitorRecvError::Lagged(_)) => {}
409
+ Err(_) => break,
410
+ }
411
+ }
412
+ });
413
+
414
+ let _ = otx.send((sock, send_pump, recv_pump, monitor_pump));
415
+ });
416
+ tx.send(job).expect("omq-rs: tokio runtime gone");
417
+
418
+ recv_blocking(orx, "omq-rs: materialize failed")
419
+ }
420
+
421
+ pub fn destroy_socket(
422
+ io_threads: usize,
423
+ sock: Arc<InnerSocket>,
424
+ send_prod: Mutex<yring::AsyncProducer<omq_tokio::Message>>,
425
+ mut send_pump: JoinHandle<()>,
426
+ recv_pump: JoinHandle<()>,
427
+ monitor_pump: JoinHandle<()>,
428
+ linger: Option<Duration>,
429
+ ) {
430
+ recv_pump.abort();
431
+ monitor_pump.abort();
432
+ drop(recv_pump);
433
+ drop(monitor_pump);
434
+ let handle = ensure_runtime(io_threads);
435
+ let close_timeout = linger
436
+ .unwrap_or(Duration::from_secs(30))
437
+ .max(Duration::from_millis(10));
438
+ let fut = async move {
439
+ drop(send_prod);
440
+ if tokio::time::timeout(close_timeout, &mut send_pump)
441
+ .await
442
+ .is_err()
443
+ {
444
+ send_pump.abort();
445
+ let _ = send_pump.await;
446
+ }
447
+
448
+ let s = Arc::try_unwrap(sock).unwrap_or_else(|arc| (*arc).clone());
449
+ let _ = tokio::time::timeout(close_timeout, s.close()).await;
450
+ };
451
+
452
+ let (otx, orx) = flume::bounded::<()>(1);
453
+ handle.spawn(async move {
454
+ fut.await;
455
+ let _ = otx.send(());
456
+ });
457
+
458
+ recv_blocking(orx, "omq-rs: close failed");
459
+ }