omq-backend-rust 0.1.1

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,470 @@
1
+ use std::str::FromStr;
2
+ use std::sync::atomic::{AtomicBool, Ordering};
3
+ use std::sync::{Arc, Mutex, RwLock};
4
+
5
+ use bytes::Bytes;
6
+ use magnus::r_hash::RHash;
7
+ use magnus::{Error, Ruby, function, method, prelude::*, r_array::RArray, r_string::RString};
8
+
9
+ use crate::error::map_err;
10
+ use crate::notify::PipeNotify;
11
+ use crate::runtime::{self, Materialized};
12
+
13
+ static IO_THREADS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(1);
14
+
15
+ pub fn set_io_threads(n: usize) {
16
+ IO_THREADS.store(n, Ordering::Relaxed);
17
+ }
18
+
19
+ fn io_threads() -> usize {
20
+ IO_THREADS.load(Ordering::Relaxed)
21
+ }
22
+
23
+ #[magnus::wrap(class = "OMQ::Rust::Native::RustSocket", free_immediately, size)]
24
+ pub struct RustSocket {
25
+ socket_type: omq_tokio::SocketType,
26
+ options: Mutex<Option<omq_tokio::Options>>,
27
+ materialized: RwLock<Option<Materialized>>,
28
+ closed: AtomicBool,
29
+ linger: Mutex<Option<std::time::Duration>>,
30
+ }
31
+
32
+ unsafe impl Send for RustSocket {}
33
+ unsafe impl Sync for RustSocket {}
34
+
35
+ fn parse_socket_type(s: &str) -> Result<omq_tokio::SocketType, String> {
36
+ match s {
37
+ "REQ" => Ok(omq_tokio::SocketType::Req),
38
+ "REP" => Ok(omq_tokio::SocketType::Rep),
39
+ "PUB" => Ok(omq_tokio::SocketType::Pub),
40
+ "SUB" => Ok(omq_tokio::SocketType::Sub),
41
+ "XPUB" => Ok(omq_tokio::SocketType::XPub),
42
+ "XSUB" => Ok(omq_tokio::SocketType::XSub),
43
+ "PUSH" => Ok(omq_tokio::SocketType::Push),
44
+ "PULL" => Ok(omq_tokio::SocketType::Pull),
45
+ "DEALER" => Ok(omq_tokio::SocketType::Dealer),
46
+ "ROUTER" => Ok(omq_tokio::SocketType::Router),
47
+ "PAIR" => Ok(omq_tokio::SocketType::Pair),
48
+ "CLIENT" => Ok(omq_tokio::SocketType::Client),
49
+ "SERVER" => Ok(omq_tokio::SocketType::Server),
50
+ "RADIO" => Ok(omq_tokio::SocketType::Radio),
51
+ "DISH" => Ok(omq_tokio::SocketType::Dish),
52
+ "SCATTER" => Ok(omq_tokio::SocketType::Scatter),
53
+ "GATHER" => Ok(omq_tokio::SocketType::Gather),
54
+ "CHANNEL" => Ok(omq_tokio::SocketType::Channel),
55
+ "PEER" => Ok(omq_tokio::SocketType::Peer),
56
+ _ => Err(format!("unknown socket type: {s}")),
57
+ }
58
+ }
59
+
60
+ fn rust_socket_new(ruby: &Ruby, type_str: String) -> Result<RustSocket, Error> {
61
+ let st = parse_socket_type(&type_str).map_err(|e| Error::new(ruby.exception_arg_error(), e))?;
62
+ Ok(RustSocket {
63
+ socket_type: st,
64
+ options: Mutex::new(None),
65
+ materialized: RwLock::new(None),
66
+ closed: AtomicBool::new(false),
67
+ linger: Mutex::new(None),
68
+ })
69
+ }
70
+
71
+ fn rust_socket_set_options(ruby: &Ruby, rb_self: &RustSocket, hash: RHash) -> Result<(), Error> {
72
+ let opts = crate::options::build_options(ruby, hash)?;
73
+ *rb_self.linger.lock().unwrap() = opts.linger;
74
+ *rb_self.options.lock().unwrap() = Some(opts);
75
+ Ok(())
76
+ }
77
+
78
+ fn rust_socket_materialize(ruby: &Ruby, rb_self: &RustSocket) -> Result<(), Error> {
79
+ if rb_self.closed.load(Ordering::Relaxed) {
80
+ return Err(Error::new(ruby.exception_io_error(), "socket closed"));
81
+ }
82
+ {
83
+ let slot = rb_self.materialized.read().unwrap();
84
+ if slot.is_some() {
85
+ return Ok(());
86
+ }
87
+ }
88
+ let mut slot = rb_self.materialized.write().unwrap();
89
+ if slot.is_some() {
90
+ return Ok(());
91
+ }
92
+
93
+ let opts = rb_self.options.lock().unwrap().take().unwrap_or_default();
94
+ let send_cap = opts.send_hwm.max(1) as usize;
95
+ let recv_cap = opts.recv_hwm.max(1) as usize;
96
+ let (send_prod, send_cons) = yring::async_spsc(send_cap);
97
+ let (recv_prod, recv_cons) = yring::spsc(recv_cap);
98
+ let recv_notify = Arc::new(PipeNotify::new());
99
+ let send_notify = Arc::new(PipeNotify::new());
100
+ let recv_space = Arc::new(tokio::sync::Notify::new());
101
+
102
+ let (monitor_tx, monitor_rx) = flume::bounded(64);
103
+ let monitor_notify = Arc::new(PipeNotify::new());
104
+ let peer_connected_notify = Arc::new(PipeNotify::new());
105
+ let all_peers_gone_notify = Arc::new(PipeNotify::new());
106
+ let subscriber_joined_notify = Arc::new(PipeNotify::new());
107
+
108
+ let (socket, send_pump, recv_pump, monitor_pump) = runtime::materialize(
109
+ io_threads(),
110
+ rb_self.socket_type,
111
+ opts,
112
+ send_cons,
113
+ recv_prod,
114
+ recv_notify.clone(),
115
+ send_notify.clone(),
116
+ recv_space.clone(),
117
+ monitor_tx,
118
+ monitor_notify.clone(),
119
+ peer_connected_notify.clone(),
120
+ all_peers_gone_notify.clone(),
121
+ subscriber_joined_notify.clone(),
122
+ );
123
+
124
+ *slot = Some(Materialized {
125
+ socket,
126
+ send_prod: Mutex::new(send_prod),
127
+ recv_cons: Mutex::new(recv_cons),
128
+ recv_notify,
129
+ send_notify,
130
+ recv_space,
131
+ send_pump,
132
+ recv_pump,
133
+ monitor_rx,
134
+ monitor_notify,
135
+ peer_connected_notify,
136
+ all_peers_gone_notify,
137
+ subscriber_joined_notify,
138
+ monitor_pump,
139
+ });
140
+ Ok(())
141
+ }
142
+
143
+ fn rust_socket_bind(ruby: &Ruby, rb_self: &RustSocket, endpoint: String) -> Result<String, Error> {
144
+ let sock = ensure_socket(ruby, rb_self)?;
145
+ let ep = omq_tokio::Endpoint::from_str(&endpoint).map_err(|e| map_err(ruby, e))?;
146
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.bind(ep).await });
147
+ result
148
+ .map(|ep| ep.to_string())
149
+ .map_err(|e| map_err(ruby, e))
150
+ }
151
+
152
+ fn rust_socket_connect(ruby: &Ruby, rb_self: &RustSocket, endpoint: String) -> Result<(), Error> {
153
+ let sock = ensure_socket(ruby, rb_self)?;
154
+ let ep = omq_tokio::Endpoint::from_str(&endpoint).map_err(|e| map_err(ruby, e))?;
155
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.connect(ep).await });
156
+ result.map_err(|e| map_err(ruby, e))
157
+ }
158
+
159
+ fn rust_socket_disconnect(
160
+ ruby: &Ruby,
161
+ rb_self: &RustSocket,
162
+ endpoint: String,
163
+ ) -> Result<(), Error> {
164
+ let sock = ensure_socket(ruby, rb_self)?;
165
+ let ep = omq_tokio::Endpoint::from_str(&endpoint).map_err(|e| map_err(ruby, e))?;
166
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.disconnect(ep).await });
167
+ result.map_err(|e| map_err(ruby, e))
168
+ }
169
+
170
+ fn rust_socket_unbind(ruby: &Ruby, rb_self: &RustSocket, endpoint: String) -> Result<(), Error> {
171
+ let sock = ensure_socket(ruby, rb_self)?;
172
+ let ep = omq_tokio::Endpoint::from_str(&endpoint).map_err(|e| map_err(ruby, e))?;
173
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.unbind(ep).await });
174
+ result.map_err(|e| map_err(ruby, e))
175
+ }
176
+
177
+ fn rust_socket_enqueue_send(
178
+ ruby: &Ruby,
179
+ rb_self: &RustSocket,
180
+ parts: RArray,
181
+ ) -> Result<magnus::Symbol, Error> {
182
+ let mat_guard = rb_self.materialized.read().unwrap();
183
+ let mat = mat_guard
184
+ .as_ref()
185
+ .ok_or_else(|| Error::new(ruby.exception_runtime_error(), "socket not materialized"))?;
186
+
187
+ let msg = ruby_parts_to_message(ruby, parts)?;
188
+ let mut prod = mat.send_prod.lock().unwrap();
189
+ match prod.push(msg) {
190
+ Ok(()) => {
191
+ prod.flush();
192
+ Ok(ruby.to_symbol("ok"))
193
+ }
194
+ Err(returned) => {
195
+ prod.flush();
196
+ match prod.push(returned) {
197
+ Ok(()) => {
198
+ prod.flush();
199
+ Ok(ruby.to_symbol("ok"))
200
+ }
201
+ Err(_) => Ok(ruby.to_symbol("full")),
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ fn rust_socket_try_recv(ruby: &Ruby, rb_self: &RustSocket) -> Result<Option<RArray>, Error> {
208
+ let mat_guard = rb_self.materialized.read().unwrap();
209
+ let mat = match mat_guard.as_ref() {
210
+ Some(m) => m,
211
+ None => return Ok(None),
212
+ };
213
+
214
+ let mut cons = mat.recv_cons.lock().unwrap();
215
+ match cons.prefetch_and_pop() {
216
+ Some(msg) => {
217
+ mat.recv_space.notify_one();
218
+ Ok(Some(message_to_ruby_parts(ruby, msg)?))
219
+ }
220
+ None => Ok(None),
221
+ }
222
+ }
223
+
224
+ fn rust_socket_try_recv_batch(ruby: &Ruby, rb_self: &RustSocket) -> Result<Option<RArray>, Error> {
225
+ let mat_guard = rb_self.materialized.read().unwrap();
226
+ let mat = match mat_guard.as_ref() {
227
+ Some(m) => m,
228
+ None => return Ok(None),
229
+ };
230
+
231
+ let mut cons = mat.recv_cons.lock().unwrap();
232
+ let count = cons.prefetch();
233
+ if count == 0 {
234
+ return Ok(None);
235
+ }
236
+
237
+ let batch = ruby.ary_new_capa(count);
238
+ let mut popped = 0usize;
239
+ while let Some(msg) = cons.pop() {
240
+ batch.push(message_to_ruby_parts(ruby, msg)?)?;
241
+ popped += 1;
242
+ }
243
+ cons.release();
244
+
245
+ if popped > 0 {
246
+ mat.recv_space.notify_one();
247
+ Ok(Some(batch))
248
+ } else {
249
+ Ok(None)
250
+ }
251
+ }
252
+
253
+ fn rust_socket_recv_fd(ruby: &Ruby, rb_self: &RustSocket) -> Result<i32, Error> {
254
+ let mat_guard = rb_self.materialized.read().unwrap();
255
+ let mat = mat_guard
256
+ .as_ref()
257
+ .ok_or_else(|| Error::new(ruby.exception_runtime_error(), "socket not materialized"))?;
258
+ mat.recv_notify.park_begin();
259
+ Ok(mat.recv_notify.read_fd())
260
+ }
261
+
262
+ fn rust_socket_send_fd(ruby: &Ruby, rb_self: &RustSocket) -> Result<i32, Error> {
263
+ let mat_guard = rb_self.materialized.read().unwrap();
264
+ let mat = mat_guard
265
+ .as_ref()
266
+ .ok_or_else(|| Error::new(ruby.exception_runtime_error(), "socket not materialized"))?;
267
+ mat.send_notify.park_begin();
268
+ Ok(mat.send_notify.read_fd())
269
+ }
270
+
271
+ fn rust_socket_peer_connected_fd(ruby: &Ruby, rb_self: &RustSocket) -> Result<i32, Error> {
272
+ let mat_guard = rb_self.materialized.read().unwrap();
273
+ let mat = mat_guard
274
+ .as_ref()
275
+ .ok_or_else(|| Error::new(ruby.exception_runtime_error(), "socket not materialized"))?;
276
+ Ok(mat.peer_connected_notify.read_fd())
277
+ }
278
+
279
+ fn rust_socket_all_peers_gone_fd(ruby: &Ruby, rb_self: &RustSocket) -> Result<i32, Error> {
280
+ let mat_guard = rb_self.materialized.read().unwrap();
281
+ let mat = mat_guard
282
+ .as_ref()
283
+ .ok_or_else(|| Error::new(ruby.exception_runtime_error(), "socket not materialized"))?;
284
+ Ok(mat.all_peers_gone_notify.read_fd())
285
+ }
286
+
287
+ fn rust_socket_subscriber_joined_fd(ruby: &Ruby, rb_self: &RustSocket) -> Result<i32, Error> {
288
+ let mat_guard = rb_self.materialized.read().unwrap();
289
+ let mat = mat_guard
290
+ .as_ref()
291
+ .ok_or_else(|| Error::new(ruby.exception_runtime_error(), "socket not materialized"))?;
292
+ Ok(mat.subscriber_joined_notify.read_fd())
293
+ }
294
+
295
+ fn rust_socket_monitor_fd(ruby: &Ruby, rb_self: &RustSocket) -> Result<i32, Error> {
296
+ let mat_guard = rb_self.materialized.read().unwrap();
297
+ let mat = mat_guard
298
+ .as_ref()
299
+ .ok_or_else(|| Error::new(ruby.exception_runtime_error(), "socket not materialized"))?;
300
+ mat.monitor_notify.park_begin();
301
+ Ok(mat.monitor_notify.read_fd())
302
+ }
303
+
304
+ fn rust_socket_try_recv_monitor(ruby: &Ruby, rb_self: &RustSocket) -> Result<Option<RHash>, Error> {
305
+ let mat_guard = rb_self.materialized.read().unwrap();
306
+ let mat = match mat_guard.as_ref() {
307
+ Some(m) => m,
308
+ None => return Ok(None),
309
+ };
310
+
311
+ match mat.monitor_rx.try_recv() {
312
+ Ok(data) => {
313
+ let hash = ruby.hash_new();
314
+ hash.aset(ruby.to_symbol("type"), ruby.to_symbol(data.event_type))?;
315
+ if let Some(ep) = data.endpoint {
316
+ hash.aset(ruby.to_symbol("endpoint"), ruby.str_new(&ep))?;
317
+ }
318
+ if !data.detail.is_empty() {
319
+ let detail = ruby.hash_new();
320
+ for (k, v) in &data.detail {
321
+ detail.aset(ruby.to_symbol(k), ruby.str_new(v))?;
322
+ }
323
+ hash.aset(ruby.to_symbol("detail"), detail)?;
324
+ }
325
+ Ok(Some(hash))
326
+ }
327
+ Err(_) => Ok(None),
328
+ }
329
+ }
330
+
331
+ fn rust_socket_subscribe(ruby: &Ruby, rb_self: &RustSocket, prefix: RString) -> Result<(), Error> {
332
+ let sock = ensure_socket(ruby, rb_self)?;
333
+ let bytes = Bytes::from(unsafe { prefix.as_slice() }.to_vec());
334
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.subscribe(bytes).await });
335
+ result.map_err(|e| map_err(ruby, e))
336
+ }
337
+
338
+ fn rust_socket_unsubscribe(
339
+ ruby: &Ruby,
340
+ rb_self: &RustSocket,
341
+ prefix: RString,
342
+ ) -> Result<(), Error> {
343
+ let sock = ensure_socket(ruby, rb_self)?;
344
+ let bytes = Bytes::from(unsafe { prefix.as_slice() }.to_vec());
345
+ let result =
346
+ runtime::spawn_blocking(io_threads(), async move { sock.unsubscribe(bytes).await });
347
+ result.map_err(|e| map_err(ruby, e))
348
+ }
349
+
350
+ fn rust_socket_join(ruby: &Ruby, rb_self: &RustSocket, group: RString) -> Result<(), Error> {
351
+ let sock = ensure_socket(ruby, rb_self)?;
352
+ let bytes = Bytes::from(unsafe { group.as_slice() }.to_vec());
353
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.join(bytes).await });
354
+ result.map_err(|e| map_err(ruby, e))
355
+ }
356
+
357
+ fn rust_socket_leave(ruby: &Ruby, rb_self: &RustSocket, group: RString) -> Result<(), Error> {
358
+ let sock = ensure_socket(ruby, rb_self)?;
359
+ let bytes = Bytes::from(unsafe { group.as_slice() }.to_vec());
360
+ let result = runtime::spawn_blocking(io_threads(), async move { sock.leave(bytes).await });
361
+ result.map_err(|e| map_err(ruby, e))
362
+ }
363
+
364
+ fn rust_socket_close(rb_self: &RustSocket) {
365
+ rb_self.closed.store(true, Ordering::Relaxed);
366
+ let mat = rb_self.materialized.write().unwrap().take();
367
+ if let Some(m) = mat {
368
+ m.recv_notify.force_wake();
369
+ m.send_notify.force_wake();
370
+ m.peer_connected_notify.force_wake();
371
+ m.all_peers_gone_notify.force_wake();
372
+ m.subscriber_joined_notify.force_wake();
373
+ m.monitor_notify.force_wake();
374
+ let linger = *rb_self.linger.lock().unwrap();
375
+ runtime::destroy_socket(
376
+ io_threads(),
377
+ m.socket,
378
+ m.send_prod,
379
+ m.send_pump,
380
+ m.recv_pump,
381
+ m.monitor_pump,
382
+ linger,
383
+ );
384
+ }
385
+ }
386
+
387
+ fn rust_socket_closed(rb_self: &RustSocket) -> bool {
388
+ rb_self.closed.load(Ordering::Relaxed)
389
+ }
390
+
391
+ fn rust_socket_type_name(rb_self: &RustSocket) -> &'static str {
392
+ rb_self.socket_type.as_str()
393
+ }
394
+
395
+ fn ensure_socket(ruby: &Ruby, rb_self: &RustSocket) -> Result<Arc<omq_tokio::Socket>, Error> {
396
+ let slot = rb_self.materialized.read().unwrap();
397
+ slot.as_ref()
398
+ .map(|m| m.socket.clone())
399
+ .ok_or_else(|| Error::new(ruby.exception_runtime_error(), "socket not materialized"))
400
+ }
401
+
402
+ fn ruby_parts_to_message(_ruby: &Ruby, parts: RArray) -> Result<omq_tokio::Message, Error> {
403
+ let len = parts.len();
404
+ if len == 1 {
405
+ let part: RString = parts.entry(0)?;
406
+ let data = unsafe { part.as_slice() }.to_vec();
407
+ Ok(omq_tokio::Message::from_slice(&data))
408
+ } else {
409
+ let mut frames: Vec<Bytes> = Vec::with_capacity(len);
410
+ for i in 0..len {
411
+ let part: RString = parts.entry(i as isize)?;
412
+ let data = unsafe { part.as_slice() }.to_vec();
413
+ frames.push(Bytes::from(data));
414
+ }
415
+ Ok(omq_tokio::Message::multipart(frames))
416
+ }
417
+ }
418
+
419
+ fn message_to_ruby_parts(ruby: &Ruby, msg: omq_tokio::Message) -> Result<RArray, Error> {
420
+ let arr = ruby.ary_new();
421
+ for part in msg.iter() {
422
+ let s = ruby.str_from_slice(&part);
423
+ s.freeze();
424
+ arr.push(s)?;
425
+ }
426
+ Ok(arr)
427
+ }
428
+
429
+ pub fn register(ruby: &Ruby) -> Result<(), Error> {
430
+ let omq = ruby.define_module("OMQ")?;
431
+ let rust = omq.define_module("Rust")?;
432
+ let native = rust.define_module("Native")?;
433
+
434
+ let class = native.define_class("RustSocket", ruby.class_object())?;
435
+ class.define_singleton_method("new", function!(rust_socket_new, 1))?;
436
+ class.define_method("set_options", method!(rust_socket_set_options, 1))?;
437
+ class.define_method("materialize", method!(rust_socket_materialize, 0))?;
438
+ class.define_method("bind", method!(rust_socket_bind, 1))?;
439
+ class.define_method("connect", method!(rust_socket_connect, 1))?;
440
+ class.define_method("disconnect", method!(rust_socket_disconnect, 1))?;
441
+ class.define_method("unbind", method!(rust_socket_unbind, 1))?;
442
+ class.define_method("enqueue_send", method!(rust_socket_enqueue_send, 1))?;
443
+ class.define_method("try_recv", method!(rust_socket_try_recv, 0))?;
444
+ class.define_method("try_recv_batch", method!(rust_socket_try_recv_batch, 0))?;
445
+ class.define_method("recv_fd", method!(rust_socket_recv_fd, 0))?;
446
+ class.define_method("send_fd", method!(rust_socket_send_fd, 0))?;
447
+ class.define_method(
448
+ "peer_connected_fd",
449
+ method!(rust_socket_peer_connected_fd, 0),
450
+ )?;
451
+ class.define_method(
452
+ "all_peers_gone_fd",
453
+ method!(rust_socket_all_peers_gone_fd, 0),
454
+ )?;
455
+ class.define_method(
456
+ "subscriber_joined_fd",
457
+ method!(rust_socket_subscriber_joined_fd, 0),
458
+ )?;
459
+ class.define_method("monitor_fd", method!(rust_socket_monitor_fd, 0))?;
460
+ class.define_method("try_recv_monitor", method!(rust_socket_try_recv_monitor, 0))?;
461
+ class.define_method("subscribe", method!(rust_socket_subscribe, 1))?;
462
+ class.define_method("unsubscribe", method!(rust_socket_unsubscribe, 1))?;
463
+ class.define_method("join", method!(rust_socket_join, 1))?;
464
+ class.define_method("leave", method!(rust_socket_leave, 1))?;
465
+ class.define_method("close", method!(rust_socket_close, 0))?;
466
+ class.define_method("closed?", method!(rust_socket_closed, 0))?;
467
+ class.define_method("socket_type_name", method!(rust_socket_type_name, 0))?;
468
+
469
+ Ok(())
470
+ }