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,972 @@
1
+ use std::ffi::c_void;
2
+ use std::panic::{AssertUnwindSafe, catch_unwind};
3
+ use std::str::FromStr;
4
+ use std::sync::atomic::{AtomicBool, Ordering};
5
+ use std::sync::{Arc, Mutex, OnceLock, RwLock};
6
+
7
+ use bytes::Bytes;
8
+ use rb_sys::{VALUE, rb_data_type_struct__bindgen_ty_1, rb_data_type_t, size_t};
9
+
10
+ use crate::error::map_err;
11
+ use crate::notify::PipeNotify;
12
+ use crate::rb::{self, RbResult, RubyErr};
13
+ use crate::runtime::{self, Materialized};
14
+
15
+ static IO_THREADS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(1);
16
+
17
+ pub fn set_io_threads(n: usize) {
18
+ IO_THREADS.store(n, Ordering::Relaxed);
19
+ }
20
+
21
+ pub(crate) fn io_threads() -> usize {
22
+ IO_THREADS.load(Ordering::Relaxed)
23
+ }
24
+
25
+ pub struct RustSocket {
26
+ socket_type: omq_tokio::SocketType,
27
+ options: Mutex<Option<omq_tokio::Options>>,
28
+ materialized: RwLock<Option<Materialized>>,
29
+ closed: AtomicBool,
30
+ linger: Mutex<Option<std::time::Duration>>,
31
+ #[cfg(feature = "curve")]
32
+ auth_worker: Mutex<Option<crate::auth::AuthWorker>>,
33
+ }
34
+
35
+ unsafe impl Send for RustSocket {}
36
+ unsafe impl Sync for RustSocket {}
37
+
38
+ struct SocketDataType(rb_data_type_t);
39
+
40
+ unsafe impl Send for SocketDataType {}
41
+ unsafe impl Sync for SocketDataType {}
42
+
43
+ static RUST_SOCKET_DATA_TYPE: OnceLock<SocketDataType> = OnceLock::new();
44
+
45
+ fn rust_socket_data_type() -> *const rb_data_type_t {
46
+ let data_type = &RUST_SOCKET_DATA_TYPE
47
+ .get_or_init(|| SocketDataType(make_rust_socket_data_type()))
48
+ .0;
49
+ std::ptr::from_ref(data_type)
50
+ }
51
+
52
+ fn make_rust_socket_data_type() -> rb_data_type_t {
53
+ rb_data_type_t {
54
+ wrap_struct_name: c"omqrs_native_socket".as_ptr(),
55
+ function: rb_data_type_struct__bindgen_ty_1 {
56
+ dmark: Some(rust_socket_mark),
57
+ dfree: Some(rust_socket_free),
58
+ dsize: Some(rust_socket_size),
59
+ dcompact: None,
60
+ reserved: [std::ptr::null_mut(); 1],
61
+ },
62
+ parent: std::ptr::null(),
63
+ data: std::ptr::null_mut(),
64
+ flags: 1,
65
+ }
66
+ }
67
+
68
+ unsafe extern "C" fn rust_socket_mark(ptr: *mut c_void) {
69
+ if ptr.is_null() {
70
+ return;
71
+ }
72
+ let _ = catch_unwind(AssertUnwindSafe(|| {
73
+ let socket = unsafe { &*ptr.cast::<RustSocket>() };
74
+ #[cfg(feature = "curve")]
75
+ if let Some(worker) = socket.auth_worker.lock().unwrap().as_ref() {
76
+ unsafe {
77
+ rb_sys::rb_gc_mark(worker.callback());
78
+ rb_sys::rb_gc_mark(worker.thread());
79
+ }
80
+ }
81
+ }));
82
+ }
83
+
84
+ unsafe extern "C" fn rust_socket_free(ptr: *mut c_void) {
85
+ if ptr.is_null() {
86
+ return;
87
+ }
88
+
89
+ let _ = catch_unwind(AssertUnwindSafe(|| unsafe {
90
+ let socket = Box::from_raw(ptr.cast::<RustSocket>());
91
+ rust_socket_close_impl(&socket, false);
92
+ drop(socket);
93
+ }));
94
+ }
95
+
96
+ unsafe extern "C" fn rust_socket_size(_ptr: *const c_void) -> size_t {
97
+ std::mem::size_of::<RustSocket>() as size_t
98
+ }
99
+
100
+ unsafe fn rust_socket_ref(value: VALUE) -> RbResult<&'static RustSocket> {
101
+ unsafe { rb::typed_data_ref(value, rust_socket_data_type(), "OMQ::Rust::Native::Socket") }
102
+ }
103
+
104
+ fn parse_socket_type(s: &str) -> Result<omq_tokio::SocketType, String> {
105
+ match s {
106
+ "REQ" => Ok(omq_tokio::SocketType::Req),
107
+ "REP" => Ok(omq_tokio::SocketType::Rep),
108
+ "PUB" => Ok(omq_tokio::SocketType::Pub),
109
+ "SUB" => Ok(omq_tokio::SocketType::Sub),
110
+ "XPUB" => Ok(omq_tokio::SocketType::XPub),
111
+ "XSUB" => Ok(omq_tokio::SocketType::XSub),
112
+ "PUSH" => Ok(omq_tokio::SocketType::Push),
113
+ "PULL" => Ok(omq_tokio::SocketType::Pull),
114
+ "DEALER" => Ok(omq_tokio::SocketType::Dealer),
115
+ "ROUTER" => Ok(omq_tokio::SocketType::Router),
116
+ "PAIR" => Ok(omq_tokio::SocketType::Pair),
117
+ "CLIENT" => Ok(omq_tokio::SocketType::Client),
118
+ "SERVER" => Ok(omq_tokio::SocketType::Server),
119
+ "RADIO" => Ok(omq_tokio::SocketType::Radio),
120
+ "DISH" => Ok(omq_tokio::SocketType::Dish),
121
+ "SCATTER" => Ok(omq_tokio::SocketType::Scatter),
122
+ "GATHER" => Ok(omq_tokio::SocketType::Gather),
123
+ "CHANNEL" => Ok(omq_tokio::SocketType::Channel),
124
+ "PEER" => Ok(omq_tokio::SocketType::Peer),
125
+ "STREAM" => Ok(omq_tokio::SocketType::Stream),
126
+ _ => Err(format!("unknown socket type: {s}")),
127
+ }
128
+ }
129
+
130
+ fn rust_socket_new_impl(class: VALUE, type_str: VALUE) -> RbResult<VALUE> {
131
+ let type_str = rb::value_to_string(type_str)?;
132
+ let st = parse_socket_type(&type_str).map_err(RubyErr::arg)?;
133
+ unsafe {
134
+ rb::wrap_typed_data(
135
+ class,
136
+ Box::new(RustSocket {
137
+ socket_type: st,
138
+ options: Mutex::new(None),
139
+ materialized: RwLock::new(None),
140
+ closed: AtomicBool::new(false),
141
+ linger: Mutex::new(None),
142
+ #[cfg(feature = "curve")]
143
+ auth_worker: Mutex::new(None),
144
+ }),
145
+ rust_socket_data_type(),
146
+ )
147
+ }
148
+ }
149
+
150
+ unsafe extern "C" fn rust_socket_new(class: VALUE, type_str: VALUE) -> VALUE {
151
+ rb::wrap(|| rust_socket_new_impl(class, type_str))
152
+ }
153
+
154
+ fn rust_socket_set_options_impl(rb_self: &RustSocket, hash: VALUE) -> RbResult<()> {
155
+ let opts = crate::options::build_options(hash)?;
156
+ *rb_self.linger.lock().unwrap() = opts.linger;
157
+ *rb_self.options.lock().unwrap() = Some(opts);
158
+ Ok(())
159
+ }
160
+
161
+ unsafe extern "C" fn rust_socket_set_options(rb_self: VALUE, hash: VALUE) -> VALUE {
162
+ rb::wrap(|| {
163
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
164
+ rust_socket_set_options_impl(rb_self, hash)?;
165
+ Ok(rb::qnil())
166
+ })
167
+ }
168
+
169
+ #[cfg(feature = "curve")]
170
+ fn set_curve_authenticator(
171
+ rb_self: &RustSocket,
172
+ authenticator: Option<omq_proto::Authenticator>,
173
+ mut worker: Option<crate::auth::AuthWorker>,
174
+ ) -> RbResult<()> {
175
+ if rb_self.materialized.read().unwrap().is_some() {
176
+ if let Some(worker) = worker.take() {
177
+ worker.stop();
178
+ }
179
+ return Err(RubyErr::runtime(
180
+ "CURVE authentication must be configured before bind or connect",
181
+ ));
182
+ }
183
+
184
+ let mut options_guard = rb_self.options.lock().unwrap();
185
+ let options = options_guard
186
+ .as_mut()
187
+ .ok_or_else(|| RubyErr::runtime("socket options not configured"))?;
188
+ if let omq_proto::MechanismSetup::CurveServer { options, .. } = &mut options.mechanism {
189
+ options.authenticator = authenticator;
190
+ } else {
191
+ drop(options_guard);
192
+ if let Some(worker) = worker.take() {
193
+ worker.stop();
194
+ }
195
+ return Err(RubyErr::runtime(
196
+ "CURVE authentication requires a CURVE server socket",
197
+ ));
198
+ }
199
+ drop(options_guard);
200
+
201
+ if let Some(previous) = rb_self.auth_worker.lock().unwrap().take() {
202
+ previous.stop();
203
+ }
204
+ *rb_self.auth_worker.lock().unwrap() = worker;
205
+ Ok(())
206
+ }
207
+
208
+ #[cfg(feature = "curve")]
209
+ unsafe extern "C" fn rust_socket_set_curve_auth_keys(rb_self: VALUE, keys: VALUE) -> VALUE {
210
+ rb::wrap(|| {
211
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
212
+ let authenticator = crate::auth::allowed_keys(keys)?;
213
+ set_curve_authenticator(rb_self, Some(authenticator), None)?;
214
+ Ok(rb::qnil())
215
+ })
216
+ }
217
+
218
+ #[cfg(feature = "curve")]
219
+ unsafe extern "C" fn rust_socket_set_curve_auth_callback(rb_self: VALUE, callback: VALUE) -> VALUE {
220
+ rb::wrap(|| {
221
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
222
+ let (authenticator, worker) = crate::auth::callback(callback)?;
223
+ set_curve_authenticator(rb_self, Some(authenticator), Some(worker))?;
224
+ Ok(rb::qnil())
225
+ })
226
+ }
227
+
228
+ #[cfg(feature = "curve")]
229
+ unsafe extern "C" fn rust_socket_clear_curve_auth(rb_self: VALUE) -> VALUE {
230
+ rb::wrap(|| {
231
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
232
+ set_curve_authenticator(rb_self, None, None)?;
233
+ Ok(rb::qnil())
234
+ })
235
+ }
236
+
237
+ fn rust_socket_materialize_impl(rb_self: &RustSocket) -> RbResult<()> {
238
+ if rb_self.closed.load(Ordering::Relaxed) {
239
+ return Err(RubyErr::io("socket closed"));
240
+ }
241
+ {
242
+ let slot = rb_self.materialized.read().unwrap();
243
+ if slot.is_some() {
244
+ return Ok(());
245
+ }
246
+ }
247
+ let mut slot = rb_self.materialized.write().unwrap();
248
+ if slot.is_some() {
249
+ return Ok(());
250
+ }
251
+
252
+ let opts = rb_self.options.lock().unwrap().take().unwrap_or_default();
253
+ let send_cap = opts.send_hwm.max(1) as usize;
254
+ let recv_cap = opts.recv_hwm.max(1) as usize;
255
+ let (send_prod, send_cons) = yring::async_spsc(send_cap);
256
+ let (recv_prod, recv_cons) = yring::spsc(recv_cap);
257
+ let recv_notify = Arc::new(PipeNotify::new());
258
+ let send_notify = Arc::new(PipeNotify::new());
259
+ let recv_space = Arc::new(tokio::sync::Notify::new());
260
+
261
+ let (monitor_tx, monitor_rx) = flume::bounded(64);
262
+ let monitor_notify = Arc::new(PipeNotify::new());
263
+ let peer_connected_notify = Arc::new(PipeNotify::new());
264
+ let all_peers_gone_notify = Arc::new(PipeNotify::new());
265
+ let subscriber_joined_notify = Arc::new(PipeNotify::new());
266
+
267
+ let (socket, send_pump, recv_pump, monitor_pump) = runtime::materialize(
268
+ io_threads(),
269
+ rb_self.socket_type,
270
+ opts,
271
+ send_cons,
272
+ recv_prod,
273
+ recv_notify.clone(),
274
+ send_notify.clone(),
275
+ recv_space.clone(),
276
+ monitor_tx,
277
+ monitor_notify.clone(),
278
+ peer_connected_notify.clone(),
279
+ all_peers_gone_notify.clone(),
280
+ subscriber_joined_notify.clone(),
281
+ );
282
+
283
+ *slot = Some(Materialized {
284
+ socket,
285
+ send_prod: Mutex::new(send_prod),
286
+ recv_cons: Mutex::new(recv_cons),
287
+ recv_notify,
288
+ send_notify,
289
+ recv_space,
290
+ send_pump,
291
+ recv_pump,
292
+ monitor_rx,
293
+ monitor_notify,
294
+ peer_connected_notify,
295
+ all_peers_gone_notify,
296
+ subscriber_joined_notify,
297
+ monitor_pump,
298
+ });
299
+ Ok(())
300
+ }
301
+
302
+ unsafe extern "C" fn rust_socket_materialize(rb_self: VALUE) -> VALUE {
303
+ rb::wrap(|| {
304
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
305
+ rust_socket_materialize_impl(rb_self)?;
306
+ Ok(rb::qnil())
307
+ })
308
+ }
309
+
310
+ fn rust_socket_bind_impl(rb_self: &RustSocket, endpoint: VALUE) -> RbResult<VALUE> {
311
+ let sock = ensure_socket(rb_self)?;
312
+ let endpoint = rb::value_to_string(endpoint)?;
313
+ let ep = omq_tokio::Endpoint::from_str(&endpoint).map_err(map_err)?;
314
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.bind(ep).await });
315
+ let endpoint = result.map_err(map_err)?;
316
+ rb::new_utf8_string(&endpoint.to_string())
317
+ }
318
+
319
+ unsafe extern "C" fn rust_socket_bind(rb_self: VALUE, endpoint: VALUE) -> VALUE {
320
+ rb::wrap(|| {
321
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
322
+ rust_socket_bind_impl(rb_self, endpoint)
323
+ })
324
+ }
325
+
326
+ fn rust_socket_connect_impl(rb_self: &RustSocket, endpoint: VALUE) -> RbResult<()> {
327
+ let sock = ensure_socket(rb_self)?;
328
+ let endpoint = rb::value_to_string(endpoint)?;
329
+ let ep = omq_tokio::Endpoint::from_str(&endpoint).map_err(map_err)?;
330
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.connect(ep).await });
331
+ result.map_err(map_err)
332
+ }
333
+
334
+ unsafe extern "C" fn rust_socket_connect(rb_self: VALUE, endpoint: VALUE) -> VALUE {
335
+ rb::wrap(|| {
336
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
337
+ rust_socket_connect_impl(rb_self, endpoint)?;
338
+ Ok(rb::qnil())
339
+ })
340
+ }
341
+
342
+ fn rust_socket_disconnect_impl(rb_self: &RustSocket, endpoint: VALUE) -> RbResult<()> {
343
+ let sock = ensure_socket(rb_self)?;
344
+ let endpoint = rb::value_to_string(endpoint)?;
345
+ let ep = omq_tokio::Endpoint::from_str(&endpoint).map_err(map_err)?;
346
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.disconnect(ep).await });
347
+ result.map_err(map_err)
348
+ }
349
+
350
+ unsafe extern "C" fn rust_socket_disconnect(rb_self: VALUE, endpoint: VALUE) -> VALUE {
351
+ rb::wrap(|| {
352
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
353
+ rust_socket_disconnect_impl(rb_self, endpoint)?;
354
+ Ok(rb::qnil())
355
+ })
356
+ }
357
+
358
+ fn rust_socket_unbind_impl(rb_self: &RustSocket, endpoint: VALUE) -> RbResult<()> {
359
+ let sock = ensure_socket(rb_self)?;
360
+ let endpoint = rb::value_to_string(endpoint)?;
361
+ let ep = omq_tokio::Endpoint::from_str(&endpoint).map_err(map_err)?;
362
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.unbind(ep).await });
363
+ result.map_err(map_err)
364
+ }
365
+
366
+ unsafe extern "C" fn rust_socket_unbind(rb_self: VALUE, endpoint: VALUE) -> VALUE {
367
+ rb::wrap(|| {
368
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
369
+ rust_socket_unbind_impl(rb_self, endpoint)?;
370
+ Ok(rb::qnil())
371
+ })
372
+ }
373
+
374
+ fn peer_info_to_ruby(peer: &omq_tokio::PeerInfo) -> RbResult<VALUE> {
375
+ let hash = rb::hash_new()?;
376
+ rb::hash_aset(
377
+ hash,
378
+ rb::symbol("connection_id")?,
379
+ rb::u64_value(peer.connection_id),
380
+ )?;
381
+ if let Some(address) = peer.peer_address {
382
+ rb::hash_aset(
383
+ hash,
384
+ rb::symbol("peer_address")?,
385
+ rb::new_utf8_string(&address.to_string())?,
386
+ )?;
387
+ }
388
+ if let Some(identity) = &peer.peer_identity {
389
+ rb::hash_aset(
390
+ hash,
391
+ rb::symbol("peer_identity")?,
392
+ rb::new_binary_string(identity)?,
393
+ )?;
394
+ }
395
+ if let Some(socket_type) = peer.peer_properties.socket_type {
396
+ rb::hash_aset(
397
+ hash,
398
+ rb::symbol("socket_type")?,
399
+ rb::symbol(socket_type.as_str().to_ascii_lowercase().as_str())?,
400
+ )?;
401
+ }
402
+
403
+ let version = rb::array_new_capa(2)?;
404
+ rb::array_push(version, rb::uint_value(peer.zmtp_version.0.into()))?;
405
+ rb::array_push(version, rb::uint_value(peer.zmtp_version.1.into()))?;
406
+ rb::hash_aset(hash, rb::symbol("zmtp_version")?, version)?;
407
+
408
+ let properties = rb::hash_new()?;
409
+ for (name, value) in &peer.peer_properties.other {
410
+ rb::hash_aset(
411
+ properties,
412
+ rb::new_utf8_string(name)?,
413
+ rb::new_binary_string(value)?,
414
+ )?;
415
+ }
416
+ rb::hash_aset(hash, rb::symbol("properties")?, properties)?;
417
+ Ok(hash)
418
+ }
419
+
420
+ fn rust_socket_peer_info_impl(rb_self: &RustSocket, routing_id: VALUE) -> RbResult<VALUE> {
421
+ let sock = ensure_socket(rb_self)?;
422
+ let routing_id = rb::value_to_u32(routing_id)?;
423
+ let result = runtime::spawn_blocking(
424
+ io_threads(),
425
+ async move { sock.peer_info(routing_id).await },
426
+ );
427
+ match result.map_err(map_err)? {
428
+ Some(peer) => peer_info_to_ruby(&peer),
429
+ None => Ok(rb::qnil()),
430
+ }
431
+ }
432
+
433
+ unsafe extern "C" fn rust_socket_peer_info(rb_self: VALUE, routing_id: VALUE) -> VALUE {
434
+ rb::wrap(|| {
435
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
436
+ rust_socket_peer_info_impl(rb_self, routing_id)
437
+ })
438
+ }
439
+
440
+ fn rust_socket_enqueue_send_impl(rb_self: &RustSocket, parts: VALUE) -> RbResult<VALUE> {
441
+ enqueue_message(rb_self, ruby_parts_to_message(parts)?)
442
+ }
443
+
444
+ unsafe extern "C" fn rust_socket_enqueue_send(rb_self: VALUE, parts: VALUE) -> VALUE {
445
+ rb::wrap(|| {
446
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
447
+ rust_socket_enqueue_send_impl(rb_self, parts)
448
+ })
449
+ }
450
+
451
+ fn rust_socket_enqueue_send_routed_impl(
452
+ rb_self: &RustSocket,
453
+ parts: VALUE,
454
+ routing_id: VALUE,
455
+ ) -> RbResult<VALUE> {
456
+ let message = ruby_parts_to_message(parts)?.with_routing_id(rb::value_to_u32(routing_id)?);
457
+ enqueue_message(rb_self, message)
458
+ }
459
+
460
+ unsafe extern "C" fn rust_socket_enqueue_send_routed(
461
+ rb_self: VALUE,
462
+ parts: VALUE,
463
+ routing_id: VALUE,
464
+ ) -> VALUE {
465
+ rb::wrap(|| {
466
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
467
+ rust_socket_enqueue_send_routed_impl(rb_self, parts, routing_id)
468
+ })
469
+ }
470
+
471
+ fn rust_socket_try_recv_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
472
+ let mat_guard = rb_self.materialized.read().unwrap();
473
+ let Some(mat) = mat_guard.as_ref() else {
474
+ return Ok(rb::qnil());
475
+ };
476
+
477
+ let mut cons = mat.recv_cons.lock().unwrap();
478
+ if let Some(msg) = cons.prefetch_and_pop() {
479
+ mat.recv_space.notify_one();
480
+ message_to_ruby_parts(&msg)
481
+ } else {
482
+ mat.recv_notify.park_begin();
483
+ match cons.prefetch_and_pop() {
484
+ Some(msg) => {
485
+ mat.recv_notify.cancel_park();
486
+ mat.recv_space.notify_one();
487
+ message_to_ruby_parts(&msg)
488
+ }
489
+ None => Ok(rb::qnil()),
490
+ }
491
+ }
492
+ }
493
+
494
+ unsafe extern "C" fn rust_socket_try_recv(rb_self: VALUE) -> VALUE {
495
+ rb::wrap(|| {
496
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
497
+ rust_socket_try_recv_impl(rb_self)
498
+ })
499
+ }
500
+
501
+ fn rust_socket_try_recv_routed_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
502
+ let mat_guard = rb_self.materialized.read().unwrap();
503
+ let Some(mat) = mat_guard.as_ref() else {
504
+ return Ok(rb::qnil());
505
+ };
506
+
507
+ let mut cons = mat.recv_cons.lock().unwrap();
508
+ if let Some(msg) = cons.prefetch_and_pop() {
509
+ mat.recv_space.notify_one();
510
+ routed_message_to_ruby_parts(&msg)
511
+ } else {
512
+ mat.recv_notify.park_begin();
513
+ match cons.prefetch_and_pop() {
514
+ Some(msg) => {
515
+ mat.recv_notify.cancel_park();
516
+ mat.recv_space.notify_one();
517
+ routed_message_to_ruby_parts(&msg)
518
+ }
519
+ None => Ok(rb::qnil()),
520
+ }
521
+ }
522
+ }
523
+
524
+ unsafe extern "C" fn rust_socket_try_recv_routed(rb_self: VALUE) -> VALUE {
525
+ rb::wrap(|| {
526
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
527
+ rust_socket_try_recv_routed_impl(rb_self)
528
+ })
529
+ }
530
+
531
+ fn rust_socket_try_recv_batch_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
532
+ let mat_guard = rb_self.materialized.read().unwrap();
533
+ let Some(mat) = mat_guard.as_ref() else {
534
+ return Ok(rb::qnil());
535
+ };
536
+
537
+ let mut cons = mat.recv_cons.lock().unwrap();
538
+ let mut count = cons.prefetch();
539
+ if count == 0 {
540
+ mat.recv_notify.park_begin();
541
+ count = cons.prefetch();
542
+ if count == 0 {
543
+ return Ok(rb::qnil());
544
+ }
545
+ mat.recv_notify.cancel_park();
546
+ }
547
+
548
+ let batch = rb::array_new_capa(count)?;
549
+ let mut popped = 0usize;
550
+ while let Some(msg) = cons.pop() {
551
+ rb::array_push(batch, message_to_ruby_parts(&msg)?)?;
552
+ popped += 1;
553
+ }
554
+ cons.release();
555
+
556
+ if popped > 0 {
557
+ mat.recv_space.notify_one();
558
+ Ok(batch)
559
+ } else {
560
+ Ok(rb::qnil())
561
+ }
562
+ }
563
+
564
+ unsafe extern "C" fn rust_socket_try_recv_batch(rb_self: VALUE) -> VALUE {
565
+ rb::wrap(|| {
566
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
567
+ rust_socket_try_recv_batch_impl(rb_self)
568
+ })
569
+ }
570
+
571
+ fn rust_socket_wake_recv_impl(rb_self: &RustSocket) {
572
+ let mat_guard = rb_self.materialized.read().unwrap();
573
+ if let Some(mat) = mat_guard.as_ref() {
574
+ mat.recv_notify.force_wake();
575
+ }
576
+ }
577
+
578
+ unsafe extern "C" fn rust_socket_wake_recv(rb_self: VALUE) -> VALUE {
579
+ rb::wrap(|| {
580
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
581
+ rust_socket_wake_recv_impl(rb_self);
582
+ Ok(rb::qnil())
583
+ })
584
+ }
585
+
586
+ fn rust_socket_recv_fd_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
587
+ let mat_guard = rb_self.materialized.read().unwrap();
588
+ let mat = mat_guard
589
+ .as_ref()
590
+ .ok_or_else(|| RubyErr::runtime("socket not materialized"))?;
591
+ mat.recv_notify.park_begin();
592
+ Ok(rb::int_value(mat.recv_notify.read_fd()))
593
+ }
594
+
595
+ unsafe extern "C" fn rust_socket_recv_fd(rb_self: VALUE) -> VALUE {
596
+ rb::wrap(|| {
597
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
598
+ rust_socket_recv_fd_impl(rb_self)
599
+ })
600
+ }
601
+
602
+ fn rust_socket_send_fd_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
603
+ let mat_guard = rb_self.materialized.read().unwrap();
604
+ let mat = mat_guard
605
+ .as_ref()
606
+ .ok_or_else(|| RubyErr::runtime("socket not materialized"))?;
607
+ mat.send_notify.park_begin();
608
+ Ok(rb::int_value(mat.send_notify.read_fd()))
609
+ }
610
+
611
+ unsafe extern "C" fn rust_socket_send_fd(rb_self: VALUE) -> VALUE {
612
+ rb::wrap(|| {
613
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
614
+ rust_socket_send_fd_impl(rb_self)
615
+ })
616
+ }
617
+
618
+ fn rust_socket_peer_connected_fd_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
619
+ let mat_guard = rb_self.materialized.read().unwrap();
620
+ let mat = mat_guard
621
+ .as_ref()
622
+ .ok_or_else(|| RubyErr::runtime("socket not materialized"))?;
623
+ Ok(rb::int_value(mat.peer_connected_notify.read_fd()))
624
+ }
625
+
626
+ unsafe extern "C" fn rust_socket_peer_connected_fd(rb_self: VALUE) -> VALUE {
627
+ rb::wrap(|| {
628
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
629
+ rust_socket_peer_connected_fd_impl(rb_self)
630
+ })
631
+ }
632
+
633
+ fn rust_socket_all_peers_gone_fd_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
634
+ let mat_guard = rb_self.materialized.read().unwrap();
635
+ let mat = mat_guard
636
+ .as_ref()
637
+ .ok_or_else(|| RubyErr::runtime("socket not materialized"))?;
638
+ Ok(rb::int_value(mat.all_peers_gone_notify.read_fd()))
639
+ }
640
+
641
+ unsafe extern "C" fn rust_socket_all_peers_gone_fd(rb_self: VALUE) -> VALUE {
642
+ rb::wrap(|| {
643
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
644
+ rust_socket_all_peers_gone_fd_impl(rb_self)
645
+ })
646
+ }
647
+
648
+ fn rust_socket_subscriber_joined_fd_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
649
+ let mat_guard = rb_self.materialized.read().unwrap();
650
+ let mat = mat_guard
651
+ .as_ref()
652
+ .ok_or_else(|| RubyErr::runtime("socket not materialized"))?;
653
+ Ok(rb::int_value(mat.subscriber_joined_notify.read_fd()))
654
+ }
655
+
656
+ unsafe extern "C" fn rust_socket_subscriber_joined_fd(rb_self: VALUE) -> VALUE {
657
+ rb::wrap(|| {
658
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
659
+ rust_socket_subscriber_joined_fd_impl(rb_self)
660
+ })
661
+ }
662
+
663
+ fn rust_socket_monitor_fd_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
664
+ let mat_guard = rb_self.materialized.read().unwrap();
665
+ let mat = mat_guard
666
+ .as_ref()
667
+ .ok_or_else(|| RubyErr::runtime("socket not materialized"))?;
668
+ mat.monitor_notify.park_begin();
669
+ Ok(rb::int_value(mat.monitor_notify.read_fd()))
670
+ }
671
+
672
+ unsafe extern "C" fn rust_socket_monitor_fd(rb_self: VALUE) -> VALUE {
673
+ rb::wrap(|| {
674
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
675
+ rust_socket_monitor_fd_impl(rb_self)
676
+ })
677
+ }
678
+
679
+ fn rust_socket_try_recv_monitor_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
680
+ let mat_guard = rb_self.materialized.read().unwrap();
681
+ let Some(mat) = mat_guard.as_ref() else {
682
+ return Ok(rb::qnil());
683
+ };
684
+
685
+ match mat.monitor_rx.try_recv() {
686
+ Ok(data) => {
687
+ let hash = rb::hash_new()?;
688
+ rb::hash_aset(hash, rb::symbol("event")?, rb::symbol(data.event_type)?)?;
689
+ if let Some(ep) = data.endpoint {
690
+ rb::hash_aset(hash, rb::symbol("endpoint")?, rb::new_utf8_string(&ep)?)?;
691
+ }
692
+ for (key, value) in &data.detail {
693
+ let value = match value {
694
+ runtime::MonitorValue::Bytes(value) => rb::new_binary_string(value)?,
695
+ runtime::MonitorValue::Integer(value) => rb::u64_value(*value),
696
+ runtime::MonitorValue::Text(value) => rb::new_utf8_string(value)?,
697
+ };
698
+ rb::hash_aset(hash, rb::symbol(key)?, value)?;
699
+ }
700
+ Ok(hash)
701
+ }
702
+ Err(_) => Ok(rb::qnil()),
703
+ }
704
+ }
705
+
706
+ unsafe extern "C" fn rust_socket_try_recv_monitor(rb_self: VALUE) -> VALUE {
707
+ rb::wrap(|| {
708
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
709
+ rust_socket_try_recv_monitor_impl(rb_self)
710
+ })
711
+ }
712
+
713
+ fn rust_socket_subscribe_impl(rb_self: &RustSocket, prefix: VALUE) -> RbResult<()> {
714
+ let sock = ensure_socket(rb_self)?;
715
+ let bytes = Bytes::from(rb::value_to_bytes(prefix)?);
716
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.subscribe(bytes).await });
717
+ result.map_err(map_err)
718
+ }
719
+
720
+ unsafe extern "C" fn rust_socket_subscribe(rb_self: VALUE, prefix: VALUE) -> VALUE {
721
+ rb::wrap(|| {
722
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
723
+ rust_socket_subscribe_impl(rb_self, prefix)?;
724
+ Ok(rb::qnil())
725
+ })
726
+ }
727
+
728
+ fn rust_socket_unsubscribe_impl(rb_self: &RustSocket, prefix: VALUE) -> RbResult<()> {
729
+ let sock = ensure_socket(rb_self)?;
730
+ let bytes = Bytes::from(rb::value_to_bytes(prefix)?);
731
+ let result =
732
+ runtime::spawn_blocking(io_threads(), async move { sock.unsubscribe(bytes).await });
733
+ result.map_err(map_err)
734
+ }
735
+
736
+ unsafe extern "C" fn rust_socket_unsubscribe(rb_self: VALUE, prefix: VALUE) -> VALUE {
737
+ rb::wrap(|| {
738
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
739
+ rust_socket_unsubscribe_impl(rb_self, prefix)?;
740
+ Ok(rb::qnil())
741
+ })
742
+ }
743
+
744
+ fn rust_socket_join_impl(rb_self: &RustSocket, group: VALUE) -> RbResult<()> {
745
+ let sock = ensure_socket(rb_self)?;
746
+ let bytes = Bytes::from(rb::value_to_bytes(group)?);
747
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.join(bytes).await });
748
+ result.map_err(map_err)
749
+ }
750
+
751
+ unsafe extern "C" fn rust_socket_join(rb_self: VALUE, group: VALUE) -> VALUE {
752
+ rb::wrap(|| {
753
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
754
+ rust_socket_join_impl(rb_self, group)?;
755
+ Ok(rb::qnil())
756
+ })
757
+ }
758
+
759
+ fn rust_socket_leave_impl(rb_self: &RustSocket, group: VALUE) -> RbResult<()> {
760
+ let sock = ensure_socket(rb_self)?;
761
+ let bytes = Bytes::from(rb::value_to_bytes(group)?);
762
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.leave(bytes).await });
763
+ result.map_err(map_err)
764
+ }
765
+
766
+ unsafe extern "C" fn rust_socket_leave(rb_self: VALUE, group: VALUE) -> VALUE {
767
+ rb::wrap(|| {
768
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
769
+ rust_socket_leave_impl(rb_self, group)?;
770
+ Ok(rb::qnil())
771
+ })
772
+ }
773
+
774
+ fn rust_socket_close_impl(rb_self: &RustSocket, wait_for_auth_worker: bool) {
775
+ rb_self.closed.store(true, Ordering::Relaxed);
776
+ #[cfg(feature = "curve")]
777
+ if let Some(worker) = rb_self.auth_worker.lock().unwrap().take() {
778
+ if wait_for_auth_worker {
779
+ worker.stop();
780
+ } else {
781
+ worker.request_stop();
782
+ }
783
+ }
784
+ let mat = rb_self.materialized.write().unwrap().take();
785
+ if let Some(m) = mat {
786
+ m.recv_notify.force_wake();
787
+ m.send_notify.force_wake();
788
+ m.peer_connected_notify.force_wake();
789
+ m.all_peers_gone_notify.force_wake();
790
+ m.subscriber_joined_notify.force_wake();
791
+ m.monitor_notify.force_wake();
792
+ let linger = *rb_self.linger.lock().unwrap();
793
+ runtime::destroy_socket(
794
+ io_threads(),
795
+ m.socket,
796
+ m.send_prod,
797
+ m.send_pump,
798
+ m.recv_pump,
799
+ m.monitor_pump,
800
+ linger,
801
+ );
802
+ }
803
+ }
804
+
805
+ unsafe extern "C" fn rust_socket_close(rb_self: VALUE) -> VALUE {
806
+ rb::wrap(|| {
807
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
808
+ rust_socket_close_impl(rb_self, true);
809
+ Ok(rb::qnil())
810
+ })
811
+ }
812
+
813
+ fn rust_socket_closed_impl(rb_self: &RustSocket) -> bool {
814
+ rb_self.closed.load(Ordering::Relaxed)
815
+ }
816
+
817
+ unsafe extern "C" fn rust_socket_closed(rb_self: VALUE) -> VALUE {
818
+ rb::wrap(|| {
819
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
820
+ Ok(rb::bool_value(rust_socket_closed_impl(rb_self)))
821
+ })
822
+ }
823
+
824
+ fn rust_socket_type_name_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
825
+ rb::new_utf8_string(rb_self.socket_type.as_str())
826
+ }
827
+
828
+ unsafe extern "C" fn rust_socket_type_name(rb_self: VALUE) -> VALUE {
829
+ rb::wrap(|| {
830
+ let rb_self = unsafe { rust_socket_ref(rb_self)? };
831
+ rust_socket_type_name_impl(rb_self)
832
+ })
833
+ }
834
+
835
+ fn ensure_socket(rb_self: &RustSocket) -> RbResult<Arc<omq_tokio::Socket>> {
836
+ let slot = rb_self.materialized.read().unwrap();
837
+ slot.as_ref()
838
+ .map(|m| m.socket.clone())
839
+ .ok_or_else(|| RubyErr::runtime("socket not materialized"))
840
+ }
841
+
842
+ fn ruby_parts_to_message(parts: VALUE) -> RbResult<omq_tokio::Message> {
843
+ let len = rb::array_len(parts)?;
844
+ if len == 1 {
845
+ let part = rb::array_entry(parts, 0)?;
846
+ let data = rb::value_to_bytes(part)?;
847
+ Ok(omq_tokio::Message::from_slice(&data))
848
+ } else {
849
+ let mut frames: Vec<Bytes> = Vec::with_capacity(len);
850
+ for i in 0..len {
851
+ let part = rb::array_entry(parts, i)?;
852
+ let data = rb::value_to_bytes(part)?;
853
+ frames.push(Bytes::from(data));
854
+ }
855
+ Ok(omq_tokio::Message::multipart(frames))
856
+ }
857
+ }
858
+
859
+ fn enqueue_message(rb_self: &RustSocket, msg: omq_tokio::Message) -> RbResult<VALUE> {
860
+ let mat_guard = rb_self.materialized.read().unwrap();
861
+ let mat = mat_guard
862
+ .as_ref()
863
+ .ok_or_else(|| RubyErr::runtime("socket not materialized"))?;
864
+
865
+ let mut prod = mat.send_prod.lock().unwrap();
866
+ let returned = match prod.push(msg) {
867
+ Ok(()) => {
868
+ prod.flush();
869
+ return rb::symbol("ok");
870
+ }
871
+ Err(returned) => returned,
872
+ };
873
+ prod.flush();
874
+ let returned = match prod.push(returned) {
875
+ Ok(()) => {
876
+ prod.flush();
877
+ return rb::symbol("ok");
878
+ }
879
+ Err(returned) => returned,
880
+ };
881
+
882
+ mat.send_notify.park_begin();
883
+ match prod.push(returned) {
884
+ Ok(()) => {
885
+ mat.send_notify.cancel_park();
886
+ prod.flush();
887
+ rb::symbol("ok")
888
+ }
889
+ Err(_) => rb::symbol("full"),
890
+ }
891
+ }
892
+
893
+ fn message_to_ruby_parts(msg: &omq_tokio::Message) -> RbResult<VALUE> {
894
+ let arr = rb::array_new()?;
895
+ for part in msg {
896
+ let s = rb::new_binary_string(&part)?;
897
+ rb::array_push(arr, s)?;
898
+ }
899
+ Ok(arr)
900
+ }
901
+
902
+ fn routed_message_to_ruby_parts(msg: &omq_tokio::Message) -> RbResult<VALUE> {
903
+ let routing_id = msg
904
+ .routing_id()
905
+ .ok_or_else(|| RubyErr::runtime("received message has no routing ID"))?;
906
+ let arr = rb::array_new_capa(msg.len() + 1)?;
907
+ rb::array_push(arr, rb::uint_value(routing_id))?;
908
+ for part in msg {
909
+ rb::array_push(arr, rb::new_binary_string(&part)?)?;
910
+ }
911
+ Ok(arr)
912
+ }
913
+
914
+ pub fn register(native: VALUE) -> RbResult<()> {
915
+ let class = unsafe { rb::define_class_under(native, c"Socket", rb_sys::rb_cObject)? };
916
+
917
+ unsafe {
918
+ rb::undef_alloc_func(class)?;
919
+ rb::define_singleton_method_1(class, c"new", rust_socket_new)?;
920
+ rb::define_method_1(class, c"set_options", rust_socket_set_options)?;
921
+ #[cfg(feature = "curve")]
922
+ {
923
+ rb::define_method_1(
924
+ class,
925
+ c"set_curve_auth_keys",
926
+ rust_socket_set_curve_auth_keys,
927
+ )?;
928
+ rb::define_method_1(
929
+ class,
930
+ c"set_curve_auth_callback",
931
+ rust_socket_set_curve_auth_callback,
932
+ )?;
933
+ rb::define_method_0(class, c"clear_curve_auth", rust_socket_clear_curve_auth)?;
934
+ }
935
+ rb::define_method_0(class, c"materialize", rust_socket_materialize)?;
936
+ rb::define_method_1(class, c"bind", rust_socket_bind)?;
937
+ rb::define_method_1(class, c"connect", rust_socket_connect)?;
938
+ rb::define_method_1(class, c"disconnect", rust_socket_disconnect)?;
939
+ rb::define_method_1(class, c"unbind", rust_socket_unbind)?;
940
+ rb::define_method_1(class, c"peer_info", rust_socket_peer_info)?;
941
+ rb::define_method_1(class, c"enqueue_send", rust_socket_enqueue_send)?;
942
+ rb::define_method_2(
943
+ class,
944
+ c"enqueue_send_routed",
945
+ rust_socket_enqueue_send_routed,
946
+ )?;
947
+ rb::define_method_0(class, c"try_recv", rust_socket_try_recv)?;
948
+ rb::define_method_0(class, c"try_recv_routed", rust_socket_try_recv_routed)?;
949
+ rb::define_method_0(class, c"try_recv_batch", rust_socket_try_recv_batch)?;
950
+ rb::define_method_0(class, c"wake_recv", rust_socket_wake_recv)?;
951
+ rb::define_method_0(class, c"recv_fd", rust_socket_recv_fd)?;
952
+ rb::define_method_0(class, c"send_fd", rust_socket_send_fd)?;
953
+ rb::define_method_0(class, c"peer_connected_fd", rust_socket_peer_connected_fd)?;
954
+ rb::define_method_0(class, c"all_peers_gone_fd", rust_socket_all_peers_gone_fd)?;
955
+ rb::define_method_0(
956
+ class,
957
+ c"subscriber_joined_fd",
958
+ rust_socket_subscriber_joined_fd,
959
+ )?;
960
+ rb::define_method_0(class, c"monitor_fd", rust_socket_monitor_fd)?;
961
+ rb::define_method_0(class, c"try_recv_monitor", rust_socket_try_recv_monitor)?;
962
+ rb::define_method_1(class, c"subscribe", rust_socket_subscribe)?;
963
+ rb::define_method_1(class, c"unsubscribe", rust_socket_unsubscribe)?;
964
+ rb::define_method_1(class, c"join", rust_socket_join)?;
965
+ rb::define_method_1(class, c"leave", rust_socket_leave)?;
966
+ rb::define_method_0(class, c"close", rust_socket_close)?;
967
+ rb::define_method_0(class, c"closed?", rust_socket_closed)?;
968
+ rb::define_method_0(class, c"socket_type_name", rust_socket_type_name)?;
969
+ }
970
+
971
+ Ok(())
972
+ }