omq-backend-rust 0.1.7 → 0.2.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,433 @@
1
+ use std::ffi::{CStr, CString, c_char, c_long, c_void};
2
+ use std::panic::{AssertUnwindSafe, catch_unwind};
3
+ use std::ptr;
4
+
5
+ use rb_sys::{VALUE, rb_data_type_t};
6
+
7
+ pub type RbResult<T = VALUE> = Result<T, RubyErr>;
8
+
9
+ #[derive(Debug)]
10
+ pub enum RubyErr {
11
+ Exception(VALUE),
12
+ Error { class: VALUE, message: String },
13
+ }
14
+
15
+ impl RubyErr {
16
+ pub fn new(class: VALUE, message: impl Into<String>) -> Self {
17
+ Self::Error {
18
+ class,
19
+ message: message.into(),
20
+ }
21
+ }
22
+
23
+ pub fn arg(message: impl Into<String>) -> Self {
24
+ Self::new(unsafe { rb_sys::rb_eArgError }, message)
25
+ }
26
+
27
+ pub fn io(message: impl Into<String>) -> Self {
28
+ Self::new(unsafe { rb_sys::rb_eIOError }, message)
29
+ }
30
+
31
+ pub fn runtime(message: impl Into<String>) -> Self {
32
+ Self::new(unsafe { rb_sys::rb_eRuntimeError }, message)
33
+ }
34
+
35
+ pub fn type_error(message: impl Into<String>) -> Self {
36
+ Self::new(unsafe { rb_sys::rb_eTypeError }, message)
37
+ }
38
+
39
+ fn current_exception() -> Self {
40
+ let err = unsafe { rb_sys::rb_errinfo() };
41
+ if err == qnil() {
42
+ Self::runtime("Ruby exception")
43
+ } else {
44
+ Self::Exception(err)
45
+ }
46
+ }
47
+ }
48
+
49
+ pub fn wrap<F>(f: F) -> VALUE
50
+ where
51
+ F: FnOnce() -> RbResult<VALUE>,
52
+ {
53
+ match catch_unwind(AssertUnwindSafe(f)) {
54
+ Ok(Ok(value)) => value,
55
+ Ok(Err(err)) => raise(err),
56
+ Err(_) => raise(RubyErr::runtime("native Rust panic")),
57
+ }
58
+ }
59
+
60
+ pub fn wrap_init<F>(f: F)
61
+ where
62
+ F: FnOnce() -> RbResult<()>,
63
+ {
64
+ match catch_unwind(AssertUnwindSafe(f)) {
65
+ Ok(Ok(())) => {}
66
+ Ok(Err(err)) => raise(err),
67
+ Err(_) => raise(RubyErr::runtime("native Rust panic")),
68
+ }
69
+ }
70
+
71
+ pub fn raise(err: RubyErr) -> ! {
72
+ match err {
73
+ RubyErr::Exception(exc) => unsafe { rb_sys::rb_exc_raise(exc) },
74
+ RubyErr::Error { class, message } => {
75
+ let message = message.replace('\0', "\\0");
76
+ let c_message =
77
+ CString::new(message).unwrap_or_else(|_| CString::new("Ruby error").unwrap());
78
+ let exc = unsafe { rb_sys::rb_exc_new_cstr(class, c_message.as_ptr()) };
79
+ unsafe { rb_sys::rb_exc_raise(exc) }
80
+ }
81
+ }
82
+ }
83
+
84
+ struct ProtectData<F> {
85
+ func: Option<F>,
86
+ panicked: bool,
87
+ }
88
+
89
+ pub fn protect_value<F>(func: F) -> RbResult<VALUE>
90
+ where
91
+ F: FnOnce() -> VALUE,
92
+ {
93
+ unsafe extern "C" fn call<F>(arg: VALUE) -> VALUE
94
+ where
95
+ F: FnOnce() -> VALUE,
96
+ {
97
+ let data = unsafe { &mut *(arg as *mut ProtectData<F>) };
98
+ let Some(func) = data.func.take() else {
99
+ data.panicked = true;
100
+ return qnil();
101
+ };
102
+
103
+ match catch_unwind(AssertUnwindSafe(func)) {
104
+ Ok(value) => value,
105
+ Err(_) => {
106
+ data.panicked = true;
107
+ qnil()
108
+ }
109
+ }
110
+ }
111
+
112
+ let mut data = ProtectData {
113
+ func: Some(func),
114
+ panicked: false,
115
+ };
116
+ let mut state = 0;
117
+ let value = unsafe {
118
+ rb_sys::rb_protect(
119
+ Some(call::<F>),
120
+ &mut data as *mut ProtectData<F> as VALUE,
121
+ &mut state,
122
+ )
123
+ };
124
+
125
+ if state != 0 {
126
+ Err(RubyErr::current_exception())
127
+ } else if data.panicked {
128
+ Err(RubyErr::runtime("native Rust panic"))
129
+ } else {
130
+ Ok(value)
131
+ }
132
+ }
133
+
134
+ pub fn protect_unit<F>(func: F) -> RbResult<()>
135
+ where
136
+ F: FnOnce(),
137
+ {
138
+ protect_value(|| {
139
+ func();
140
+ qnil()
141
+ })?;
142
+ Ok(())
143
+ }
144
+
145
+ pub const fn qnil() -> VALUE {
146
+ rb_sys::ruby_special_consts::RUBY_Qnil as VALUE
147
+ }
148
+
149
+ pub const fn qtrue() -> VALUE {
150
+ rb_sys::ruby_special_consts::RUBY_Qtrue as VALUE
151
+ }
152
+
153
+ pub const fn qfalse() -> VALUE {
154
+ rb_sys::ruby_special_consts::RUBY_Qfalse as VALUE
155
+ }
156
+
157
+ pub const fn qundef() -> VALUE {
158
+ rb_sys::ruby_special_consts::RUBY_Qundef as VALUE
159
+ }
160
+
161
+ pub fn bool_value(value: bool) -> VALUE {
162
+ if value { qtrue() } else { qfalse() }
163
+ }
164
+
165
+ pub fn check_hash(value: VALUE) -> RbResult<()> {
166
+ let is_hash = unsafe { rb_sys::rb_obj_is_kind_of(value, rb_sys::rb_cHash) };
167
+ if is_hash == qtrue() {
168
+ Ok(())
169
+ } else {
170
+ Err(RubyErr::type_error("expected Hash"))
171
+ }
172
+ }
173
+
174
+ pub fn check_array(value: VALUE) -> RbResult<()> {
175
+ let is_array = unsafe { rb_sys::rb_obj_is_kind_of(value, rb_sys::rb_cArray) };
176
+ if is_array == qtrue() {
177
+ Ok(())
178
+ } else {
179
+ Err(RubyErr::type_error("expected Array"))
180
+ }
181
+ }
182
+
183
+ pub fn hash_get(hash: VALUE, key: &str) -> RbResult<Option<VALUE>> {
184
+ check_hash(hash)?;
185
+ let key = new_utf8_string(key)?;
186
+ let value = protect_value(|| unsafe { rb_sys::rb_hash_lookup2(hash, key, qundef()) })?;
187
+ if value == qundef() {
188
+ Ok(None)
189
+ } else {
190
+ Ok(Some(value))
191
+ }
192
+ }
193
+
194
+ pub fn hash_new() -> RbResult<VALUE> {
195
+ protect_value(|| unsafe { rb_sys::rb_hash_new() })
196
+ }
197
+
198
+ pub fn hash_aset(hash: VALUE, key: VALUE, value: VALUE) -> RbResult<()> {
199
+ protect_value(|| unsafe { rb_sys::rb_hash_aset(hash, key, value) })?;
200
+ Ok(())
201
+ }
202
+
203
+ pub fn array_new() -> RbResult<VALUE> {
204
+ protect_value(|| unsafe { rb_sys::rb_ary_new() })
205
+ }
206
+
207
+ pub fn array_new_capa(capacity: usize) -> RbResult<VALUE> {
208
+ let capacity = c_long_len(capacity)?;
209
+ protect_value(|| unsafe { rb_sys::rb_ary_new_capa(capacity) })
210
+ }
211
+
212
+ pub fn array_len(array: VALUE) -> RbResult<usize> {
213
+ check_array(array)?;
214
+ let len = unsafe { rb_sys::RARRAY_LEN(array) };
215
+ if len < 0 {
216
+ Err(RubyErr::runtime("negative Array length"))
217
+ } else {
218
+ Ok(len as usize)
219
+ }
220
+ }
221
+
222
+ pub fn array_entry(array: VALUE, index: usize) -> RbResult<VALUE> {
223
+ check_array(array)?;
224
+ let index = c_long_len(index)?;
225
+ protect_value(|| unsafe { rb_sys::rb_ary_entry(array, index) })
226
+ }
227
+
228
+ pub fn array_push(array: VALUE, value: VALUE) -> RbResult<()> {
229
+ protect_value(|| unsafe { rb_sys::rb_ary_push(array, value) })?;
230
+ Ok(())
231
+ }
232
+
233
+ pub fn symbol(name: &str) -> RbResult<VALUE> {
234
+ let len = c_long_len(name.len())?;
235
+ protect_value(|| unsafe {
236
+ let id = rb_sys::rb_intern2(name.as_ptr() as *const c_char, len);
237
+ rb_sys::rb_id2sym(id)
238
+ })
239
+ }
240
+
241
+ pub fn new_binary_string(bytes: &[u8]) -> RbResult<VALUE> {
242
+ let len = c_long_len(bytes.len())?;
243
+ let ptr = if bytes.is_empty() {
244
+ ptr::null()
245
+ } else {
246
+ bytes.as_ptr() as *const c_char
247
+ };
248
+ let value = protect_value(|| unsafe { rb_sys::rb_str_new(ptr, len) })?;
249
+ protect_unit(|| unsafe { rb_sys::RB_OBJ_FREEZE(value) })?;
250
+ Ok(value)
251
+ }
252
+
253
+ pub fn new_utf8_string(text: &str) -> RbResult<VALUE> {
254
+ let len = c_long_len(text.len())?;
255
+ protect_value(|| unsafe { rb_sys::rb_utf8_str_new(text.as_ptr() as *const c_char, len) })
256
+ }
257
+
258
+ pub fn string_value(value: VALUE) -> RbResult<VALUE> {
259
+ protect_value(|| unsafe { rb_sys::rb_str_to_str(value) })
260
+ }
261
+
262
+ pub fn value_to_bytes(value: VALUE) -> RbResult<Vec<u8>> {
263
+ let string = string_value(value)?;
264
+ let len = unsafe { rb_sys::RSTRING_LEN(string) };
265
+ if len < 0 {
266
+ return Err(RubyErr::runtime("negative String length"));
267
+ }
268
+ if len == 0 {
269
+ return Ok(Vec::new());
270
+ }
271
+
272
+ let ptr = unsafe { rb_sys::RSTRING_PTR(string) };
273
+ if ptr.is_null() {
274
+ return Err(RubyErr::runtime("null String pointer"));
275
+ }
276
+
277
+ let bytes = unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) };
278
+ Ok(bytes.to_vec())
279
+ }
280
+
281
+ pub fn value_to_string(value: VALUE) -> RbResult<String> {
282
+ let bytes = value_to_bytes(value)?;
283
+ String::from_utf8(bytes).map_err(|_| RubyErr::type_error("expected UTF-8 String"))
284
+ }
285
+
286
+ pub fn value_to_i64(value: VALUE) -> RbResult<i64> {
287
+ let mut out = 0i64;
288
+ protect_value(|| {
289
+ out = unsafe { rb_sys::rb_num2long(value) as i64 };
290
+ qnil()
291
+ })?;
292
+ Ok(out)
293
+ }
294
+
295
+ pub fn value_to_f64(value: VALUE) -> RbResult<f64> {
296
+ let mut out = 0.0f64;
297
+ protect_value(|| {
298
+ out = unsafe { rb_sys::rb_num2dbl(value) };
299
+ qnil()
300
+ })?;
301
+ Ok(out)
302
+ }
303
+
304
+ pub fn value_to_bool(value: VALUE) -> RbResult<bool> {
305
+ if value == qtrue() {
306
+ Ok(true)
307
+ } else if value == qfalse() {
308
+ Ok(false)
309
+ } else {
310
+ Err(RubyErr::type_error("expected true or false"))
311
+ }
312
+ }
313
+
314
+ pub fn int_value(value: i32) -> VALUE {
315
+ unsafe { rb_sys::rb_int2inum(value as isize) }
316
+ }
317
+
318
+ pub unsafe fn wrap_typed_data<T>(
319
+ class: VALUE,
320
+ value: Box<T>,
321
+ data_type: *const rb_data_type_t,
322
+ ) -> RbResult<VALUE> {
323
+ let raw = Box::into_raw(value);
324
+ match protect_value(|| unsafe {
325
+ rb_sys::rb_data_typed_object_wrap(class, raw as *mut c_void, data_type)
326
+ }) {
327
+ Ok(value) => Ok(value),
328
+ Err(err) => {
329
+ unsafe { drop(Box::from_raw(raw)) };
330
+ Err(err)
331
+ }
332
+ }
333
+ }
334
+
335
+ pub unsafe fn typed_data_ref<T>(
336
+ value: VALUE,
337
+ data_type: *const rb_data_type_t,
338
+ type_name: &str,
339
+ ) -> RbResult<&'static T> {
340
+ let mut ptr = std::ptr::null_mut();
341
+ protect_unit(|| unsafe {
342
+ ptr = rb_sys::rb_check_typeddata(value, data_type);
343
+ })
344
+ .map_err(|_| RubyErr::type_error(format!("expected {type_name}")))?;
345
+ if ptr.is_null() {
346
+ return Err(RubyErr::runtime(format!(
347
+ "{type_name} data pointer is null"
348
+ )));
349
+ }
350
+
351
+ Ok(unsafe { &*(ptr as *const T) })
352
+ }
353
+
354
+ pub unsafe fn define_module(name: &CStr) -> RbResult<VALUE> {
355
+ protect_value(|| unsafe { rb_sys::rb_define_module(name.as_ptr()) })
356
+ }
357
+
358
+ pub unsafe fn define_module_under(outer: VALUE, name: &CStr) -> RbResult<VALUE> {
359
+ protect_value(|| unsafe { rb_sys::rb_define_module_under(outer, name.as_ptr()) })
360
+ }
361
+
362
+ pub unsafe fn define_class_under(outer: VALUE, name: &CStr, superclass: VALUE) -> RbResult<VALUE> {
363
+ protect_value(|| unsafe { rb_sys::rb_define_class_under(outer, name.as_ptr(), superclass) })
364
+ }
365
+
366
+ pub unsafe fn undef_alloc_func(class: VALUE) -> RbResult<()> {
367
+ protect_unit(|| unsafe { rb_sys::rb_undef_alloc_func(class) })
368
+ }
369
+
370
+ pub unsafe fn define_module_function_1(
371
+ module: VALUE,
372
+ name: &CStr,
373
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
374
+ ) -> RbResult<()> {
375
+ protect_unit(|| unsafe {
376
+ rb_sys::rb_define_module_function(module, name.as_ptr(), Some(transmute_1(func)), 1)
377
+ })
378
+ }
379
+
380
+ pub unsafe fn define_singleton_method_1(
381
+ object: VALUE,
382
+ name: &CStr,
383
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
384
+ ) -> RbResult<()> {
385
+ protect_unit(|| unsafe {
386
+ rb_sys::rb_define_singleton_method(object, name.as_ptr(), Some(transmute_1(func)), 1)
387
+ })
388
+ }
389
+
390
+ pub unsafe fn define_method_0(
391
+ class: VALUE,
392
+ name: &CStr,
393
+ func: unsafe extern "C" fn(VALUE) -> VALUE,
394
+ ) -> RbResult<()> {
395
+ protect_unit(|| unsafe {
396
+ rb_sys::rb_define_method(class, name.as_ptr(), Some(transmute_0(func)), 0)
397
+ })
398
+ }
399
+
400
+ pub unsafe fn define_method_1(
401
+ class: VALUE,
402
+ name: &CStr,
403
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
404
+ ) -> RbResult<()> {
405
+ protect_unit(|| unsafe {
406
+ rb_sys::rb_define_method(class, name.as_ptr(), Some(transmute_1(func)), 1)
407
+ })
408
+ }
409
+
410
+ unsafe fn transmute_0(
411
+ func: unsafe extern "C" fn(VALUE) -> VALUE,
412
+ ) -> unsafe extern "C" fn() -> VALUE {
413
+ unsafe {
414
+ std::mem::transmute::<unsafe extern "C" fn(VALUE) -> VALUE, unsafe extern "C" fn() -> VALUE>(
415
+ func,
416
+ )
417
+ }
418
+ }
419
+
420
+ unsafe fn transmute_1(
421
+ func: unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
422
+ ) -> unsafe extern "C" fn() -> VALUE {
423
+ unsafe {
424
+ std::mem::transmute::<
425
+ unsafe extern "C" fn(VALUE, VALUE) -> VALUE,
426
+ unsafe extern "C" fn() -> VALUE,
427
+ >(func)
428
+ }
429
+ }
430
+
431
+ fn c_long_len(len: usize) -> RbResult<c_long> {
432
+ c_long::try_from(len).map_err(|_| RubyErr::arg("length too large"))
433
+ }
@@ -27,10 +27,10 @@ pub fn ensure_runtime(io_threads: usize) -> Handle {
27
27
  }
28
28
  let mut guard = RUNTIME.lock().unwrap();
29
29
  let pid = std::process::id();
30
- if let Some(ref rt) = *guard {
31
- if rt.pid == pid {
32
- return rt.handle.clone();
33
- }
30
+ if let Some(ref rt) = *guard
31
+ && rt.pid == pid
32
+ {
33
+ return rt.handle.clone();
34
34
  }
35
35
  let (tx, rx) = flume::unbounded::<Job>();
36
36
  let (handle_tx, handle_rx) = flume::bounded::<Handle>(1);
@@ -69,28 +69,18 @@ pub fn ensure_runtime(io_threads: usize) -> Handle {
69
69
 
70
70
  fn submit_job(io_threads: usize) -> flume::Sender<Job> {
71
71
  let guard = RUNTIME.lock().unwrap();
72
- if let Some(ref rt) = *guard {
73
- if rt.pid == std::process::id() {
74
- return rt.submit.clone();
75
- }
72
+ if let Some(ref rt) = *guard
73
+ && rt.pid == std::process::id()
74
+ {
75
+ return rt.submit.clone();
76
76
  }
77
77
  drop(guard);
78
78
  ensure_runtime(io_threads);
79
79
  RUNTIME.lock().unwrap().as_ref().unwrap().submit.clone()
80
80
  }
81
81
 
82
- pub fn spawn_blocking<F, T>(io_threads: usize, fut: F) -> T
83
- where
84
- F: Future<Output = T> + Send + 'static,
85
- T: Send + 'static,
86
- {
87
- let handle = ensure_runtime(io_threads);
88
- let (otx, orx) = flume::bounded::<T>(1);
89
- handle.spawn(async move {
90
- let out = fut.await;
91
- let _ = otx.send(out);
92
- });
93
-
82
+ #[cfg(ruby_engine = "mri")]
83
+ fn recv_blocking<T>(rx: flume::Receiver<T>, missing: &'static str) -> T {
94
84
  struct RecvBox<U> {
95
85
  rx: flume::Receiver<U>,
96
86
  result: Option<U>,
@@ -102,10 +92,7 @@ where
102
92
  std::ptr::null_mut()
103
93
  }
104
94
 
105
- let mut rd = RecvBox {
106
- rx: orx,
107
- result: None,
108
- };
95
+ let mut rd = RecvBox { rx, result: None };
109
96
  unsafe {
110
97
  rb_sys::rb_thread_call_without_gvl(
111
98
  Some(blocking_recv::<T>),
@@ -114,7 +101,27 @@ where
114
101
  std::ptr::null_mut(),
115
102
  );
116
103
  }
117
- rd.result.expect("omq-backend-rust: runtime dropped result")
104
+ rd.result.expect(missing)
105
+ }
106
+
107
+ #[cfg(not(ruby_engine = "mri"))]
108
+ fn recv_blocking<T>(rx: flume::Receiver<T>, missing: &'static str) -> T {
109
+ rx.recv().expect(missing)
110
+ }
111
+
112
+ pub fn spawn_blocking<F, T>(io_threads: usize, fut: F) -> T
113
+ where
114
+ F: Future<Output = T> + Send + 'static,
115
+ T: Send + 'static,
116
+ {
117
+ let handle = ensure_runtime(io_threads);
118
+ let (otx, orx) = flume::bounded::<T>(1);
119
+ handle.spawn(async move {
120
+ let out = fut.await;
121
+ let _ = otx.send(out);
122
+ });
123
+
124
+ recv_blocking(orx, "omq-backend-rust: runtime dropped result")
118
125
  }
119
126
 
120
127
  pub struct Materialized {
@@ -171,10 +178,10 @@ fn convert_monitor_event(event: &omq_tokio::MonitorEvent) -> MonitorEventData {
171
178
  },
172
179
  HandshakeSucceeded { endpoint, peer } => {
173
180
  let mut detail = vec![("connection_id", peer.connection_id.to_string())];
174
- if let Some(ref ident) = peer.peer_identity {
175
- if !ident.is_empty() {
176
- detail.push(("identity", format!("{:?}", ident)));
177
- }
181
+ if let Some(ref ident) = peer.peer_identity
182
+ && !ident.is_empty()
183
+ {
184
+ detail.push(("identity", format!("{:?}", ident)));
178
185
  }
179
186
  MonitorEventData {
180
187
  event_type: "handshake_succeeded",
@@ -365,17 +372,12 @@ pub fn materialize(
365
372
  all_peers_gone_notify.force_wake();
366
373
  }
367
374
  }
368
- omq_tokio::MonitorEvent::SubscribeReceived { .. } => {
369
- if !subscriber_joined_fired {
370
- subscriber_joined_fired = true;
371
- subscriber_joined_notify.force_wake();
372
- }
373
- }
374
- omq_tokio::MonitorEvent::JoinReceived { .. } => {
375
- if !subscriber_joined_fired {
376
- subscriber_joined_fired = true;
377
- subscriber_joined_notify.force_wake();
378
- }
375
+ omq_tokio::MonitorEvent::SubscribeReceived { .. }
376
+ | omq_tokio::MonitorEvent::JoinReceived { .. }
377
+ if !subscriber_joined_fired =>
378
+ {
379
+ subscriber_joined_fired = true;
380
+ subscriber_joined_notify.force_wake();
379
381
  }
380
382
  _ => {}
381
383
  }
@@ -395,40 +397,7 @@ pub fn materialize(
395
397
  });
396
398
  tx.send(job).expect("omq-backend-rust: tokio runtime gone");
397
399
 
398
- struct RecvBox {
399
- rx: flume::Receiver<(
400
- Arc<InnerSocket>,
401
- JoinHandle<()>,
402
- JoinHandle<()>,
403
- JoinHandle<()>,
404
- )>,
405
- result: Option<(
406
- Arc<InnerSocket>,
407
- JoinHandle<()>,
408
- JoinHandle<()>,
409
- JoinHandle<()>,
410
- )>,
411
- }
412
-
413
- extern "C" fn blocking_recv(data: *mut libc::c_void) -> *mut libc::c_void {
414
- let rd = unsafe { &mut *(data as *mut RecvBox) };
415
- rd.result = rd.rx.recv().ok();
416
- std::ptr::null_mut()
417
- }
418
-
419
- let mut rd = RecvBox {
420
- rx: orx,
421
- result: None,
422
- };
423
- unsafe {
424
- rb_sys::rb_thread_call_without_gvl(
425
- Some(blocking_recv),
426
- &mut rd as *mut RecvBox as *mut libc::c_void,
427
- None,
428
- std::ptr::null_mut(),
429
- );
430
- }
431
- rd.result.expect("omq-backend-rust: materialize failed")
400
+ recv_blocking(orx, "omq-backend-rust: materialize failed")
432
401
  }
433
402
 
434
403
  pub fn destroy_socket(
@@ -442,12 +411,7 @@ pub fn destroy_socket(
442
411
  ) {
443
412
  recv_pump.abort();
444
413
  monitor_pump.abort();
445
- let Ok(handle) = (|| -> std::result::Result<Handle, ()> { Ok(ensure_runtime(io_threads)) })()
446
- else {
447
- send_pump.abort();
448
- drop(send_prod);
449
- return;
450
- };
414
+ let handle = ensure_runtime(io_threads);
451
415
  let close_timeout = linger
452
416
  .unwrap_or(Duration::from_secs(30))
453
417
  .max(Duration::from_millis(10));
@@ -471,23 +435,5 @@ pub fn destroy_socket(
471
435
  let _ = otx.send(());
472
436
  });
473
437
 
474
- struct RecvBox {
475
- rx: flume::Receiver<()>,
476
- }
477
-
478
- extern "C" fn blocking_recv(data: *mut libc::c_void) -> *mut libc::c_void {
479
- let rd = unsafe { &mut *(data as *mut RecvBox) };
480
- let _ = rd.rx.recv();
481
- std::ptr::null_mut()
482
- }
483
-
484
- let mut rd = RecvBox { rx: orx };
485
- unsafe {
486
- rb_sys::rb_thread_call_without_gvl(
487
- Some(blocking_recv),
488
- &mut rd as *mut RecvBox as *mut libc::c_void,
489
- None,
490
- std::ptr::null_mut(),
491
- );
492
- }
438
+ recv_blocking(orx, "omq-backend-rust: close failed")
493
439
  }