magnus/thread.rs
1use std::{fmt, mem::size_of, os::raw::c_void, slice, time::Duration};
2
3#[allow(deprecated)]
4use rb_sys::rb_thread_fd_close;
5use rb_sys::{
6 rb_data_typed_object_wrap, rb_thread_alone, rb_thread_check_ints, rb_thread_create,
7 rb_thread_current, rb_thread_fd_writable, rb_thread_interrupted, rb_thread_kill,
8 rb_thread_local_aref, rb_thread_local_aset, rb_thread_main, rb_thread_run, rb_thread_schedule,
9 rb_thread_sleep_deadly, rb_thread_sleep_forever, rb_thread_wait_fd, rb_thread_wait_for,
10 rb_thread_wakeup, rb_thread_wakeup_alive, timeval, VALUE,
11};
12
13use crate::{
14 api::Ruby,
15 data_type_builder,
16 error::{protect, Error},
17 gc,
18 into_value::IntoValue,
19 method::{BlockReturn, Thread as _},
20 object::Object,
21 r_file::fd::AsRawFd,
22 r_typed_data::RTypedData,
23 try_convert::TryConvert,
24 typed_data::{DataType, DataTypeFunctions},
25 value::{
26 private::{self, ReprValue as _},
27 IntoId, ReprValue, Value,
28 },
29};
30
31/// # `Thread`
32///
33/// Functions to create and work with Ruby `Thread`s.
34///
35/// See also the [`Thread`] type.
36impl Ruby {
37 /// Create a Ruby thread.
38 ///
39 /// As `func` is a function pointer, only functions and closures that do
40 /// not capture any variables are permitted. For more flexibility (at the
41 /// cost of allocating) see
42 /// [`thread_create_from_fn`](Ruby::thread_create_from_fn).
43 ///
44 /// # Examples
45 ///
46 /// ```
47 /// use magnus::{rb_assert, Error, Ruby};
48 ///
49 /// fn example(ruby: &Ruby) -> Result<(), Error> {
50 /// let t = ruby.thread_create(|_ruby| 1 + 2);
51 /// rb_assert!("t.value == 3", t);
52 ///
53 /// Ok(())
54 /// }
55 /// # Ruby::init(example).unwrap()
56 /// ```
57 pub fn thread_create<R>(&self, func: fn(&Ruby) -> R) -> Thread
58 where
59 R: BlockReturn,
60 {
61 unsafe extern "C" fn call<R>(arg: *mut c_void) -> VALUE
62 where
63 R: BlockReturn,
64 {
65 let func = std::mem::transmute::<*mut c_void, fn(&Ruby) -> R>(arg);
66 func.call_handle_error().as_rb_value()
67 }
68
69 let call_func = call::<R> as unsafe extern "C" fn(arg: *mut c_void) -> VALUE;
70
71 unsafe {
72 protect(|| {
73 Thread::from_rb_value_unchecked(rb_thread_create(
74 Some(call_func),
75 func as *mut c_void,
76 ))
77 })
78 .unwrap()
79 }
80 }
81
82 /// Create a Ruby thread.
83 ///
84 /// See also [`thread_create`](Ruby::thread_create), which is more
85 /// efficient when `func` is a function or closure that does not
86 /// capture any variables.
87 ///
88 /// # Examples
89 ///
90 /// ```
91 /// use magnus::{rb_assert, Error, Ruby};
92 ///
93 /// fn example(ruby: &Ruby) -> Result<(), Error> {
94 /// let i = 1;
95 /// let t = ruby.thread_create_from_fn(move |_ruby| i + 2);
96 /// rb_assert!("t.value == 3", t);
97 ///
98 /// Ok(())
99 /// }
100 /// # Ruby::init(example).unwrap()
101 /// ```
102 pub fn thread_create_from_fn<F, R>(&self, func: F) -> Thread
103 where
104 F: 'static + Send + FnOnce(&Ruby) -> R,
105 R: BlockReturn,
106 {
107 unsafe extern "C" fn call<F, R>(arg: *mut c_void) -> VALUE
108 where
109 F: FnOnce(&Ruby) -> R,
110 R: BlockReturn,
111 {
112 let closure = (*(arg as *mut Option<F>)).take().unwrap();
113 closure.call_handle_error().as_rb_value()
114 }
115
116 let (closure, keepalive) = wrap_closure(func);
117 let call_func = call::<F, R> as unsafe extern "C" fn(arg: *mut c_void) -> VALUE;
118
119 let t = unsafe {
120 protect(|| {
121 Thread::from_rb_value_unchecked(rb_thread_create(
122 Some(call_func),
123 closure as *mut c_void,
124 ))
125 })
126 .unwrap()
127 };
128 // ivar without @ prefix is invisible from Ruby
129 t.ivar_set("__rust_closure", keepalive).unwrap();
130 t
131 }
132
133 /// Return the currently executing thread.
134 ///
135 /// # Examples
136 ///
137 /// ```
138 /// use magnus::{prelude::*, Error, Ruby};
139 ///
140 /// fn example(ruby: &Ruby) -> Result<(), Error> {
141 /// let t = ruby.thread_current();
142 /// t.is_kind_of(ruby.class_thread());
143 ///
144 /// Ok(())
145 /// }
146 /// # Ruby::init(example).unwrap()
147 /// ```
148 pub fn thread_current(&self) -> Thread {
149 unsafe { Thread::from_rb_value_unchecked(rb_thread_current()) }
150 }
151
152 /// Return the 'main' thread.
153 ///
154 /// # Examples
155 ///
156 /// ```
157 /// use magnus::{prelude::*, Error, Ruby};
158 ///
159 /// fn example(ruby: &Ruby) -> Result<(), Error> {
160 /// let t = ruby.thread_main();
161 /// t.is_kind_of(ruby.class_thread());
162 ///
163 /// Ok(())
164 /// }
165 /// # Ruby::init(example).unwrap()
166 /// ```
167 pub fn thread_main(&self) -> Thread {
168 unsafe { Thread::from_rb_value_unchecked(rb_thread_main()) }
169 }
170
171 /// Attempt to schedule another thread.
172 ///
173 /// This function blocks until the current thread is re-scheduled.
174 pub fn thread_schedule(&self) {
175 unsafe { rb_thread_schedule() };
176 }
177
178 /// Blocks until the given file descriptor is readable.
179 ///
180 /// # Examples
181 ///
182 /// ```
183 /// # #[cfg(unix)]
184 /// # {
185 /// use std::{
186 /// io::{Read, Write},
187 /// net::Shutdown,
188 /// os::unix::net::UnixStream,
189 /// };
190 ///
191 /// use magnus::{Error, Ruby};
192 ///
193 /// fn example(ruby: &Ruby) -> Result<(), Error> {
194 /// let (mut a, mut b) = UnixStream::pair().unwrap();
195 /// a.write_all(b"hello, world!").unwrap();
196 /// a.shutdown(Shutdown::Both).unwrap();
197 ///
198 /// b.set_nonblocking(true).unwrap();
199 /// ruby.thread_wait_fd(&b)?;
200 ///
201 /// let mut s = String::new();
202 /// b.read_to_string(&mut s).unwrap();
203 /// assert_eq!(s, "hello, world!");
204 ///
205 /// Ok(())
206 /// }
207 /// # Ruby::init(example).unwrap()
208 /// # }
209 /// ```
210 pub fn thread_wait_fd<T>(&self, fd: &T) -> Result<(), Error>
211 where
212 T: AsRawFd,
213 {
214 let fd = fd.as_raw_fd();
215 protect(|| {
216 unsafe { rb_thread_wait_fd(fd) };
217 self.qnil()
218 })?;
219 Ok(())
220 }
221
222 /// Blocks until the given file descriptor is writable.
223 ///
224 /// # Examples
225 ///
226 /// ```
227 /// # #[cfg(unix)]
228 /// # {
229 /// use std::{
230 /// io::{Read, Write},
231 /// net::Shutdown,
232 /// os::unix::net::UnixStream,
233 /// };
234 ///
235 /// use magnus::{Error, Ruby};
236 ///
237 /// fn example(ruby: &Ruby) -> Result<(), Error> {
238 /// let (mut a, mut b) = UnixStream::pair().unwrap();
239 ///
240 /// a.set_nonblocking(true).unwrap();
241 /// ruby.thread_fd_writable(&a)?;
242 /// a.write_all(b"hello, world!").unwrap();
243 /// a.shutdown(Shutdown::Both).unwrap();
244 ///
245 /// let mut s = String::new();
246 /// b.read_to_string(&mut s).unwrap();
247 /// assert_eq!(s, "hello, world!");
248 ///
249 /// Ok(())
250 /// }
251 /// # Ruby::init(example).unwrap()
252 /// # }
253 /// ```
254 pub fn thread_fd_writable<T>(&self, fd: &T) -> Result<(), Error>
255 where
256 T: AsRawFd,
257 {
258 let fd = fd.as_raw_fd();
259 protect(|| {
260 unsafe { rb_thread_fd_writable(fd) };
261 self.qnil()
262 })?;
263 Ok(())
264 }
265
266 /// Schedules any Ruby threads waiting on `fd`, notifying them that `fd`
267 /// has been closed.
268 ///
269 /// Blocks until all threads waiting on `fd` have woken up.
270 #[deprecated(note = "No-op as of Ruby 3.5")]
271 pub fn thread_fd_close<T>(&self, fd: &T) -> Result<(), Error>
272 where
273 T: AsRawFd,
274 {
275 let fd = fd.as_raw_fd();
276 protect(|| {
277 unsafe {
278 #[allow(deprecated)]
279 rb_thread_fd_close(fd)
280 };
281 self.qnil()
282 })?;
283 Ok(())
284 }
285
286 /// Checks if the current thread is the only thread currently alive.
287 ///
288 /// # Examples
289 ///
290 /// ```
291 /// use std::time::Duration;
292 ///
293 /// use magnus::{Error, Ruby};
294 ///
295 /// fn example(ruby: &Ruby) -> Result<(), Error> {
296 /// assert!(ruby.thread_alone());
297 ///
298 /// ruby.thread_create_from_fn(|ruby| ruby.thread_sleep(Duration::from_secs(1)));
299 ///
300 /// assert!(!ruby.thread_alone());
301 ///
302 /// Ok(())
303 /// }
304 /// # Ruby::init(example).unwrap()
305 /// ```
306 pub fn thread_alone(&self) -> bool {
307 unsafe { rb_thread_alone() != 0 }
308 }
309
310 /// Blocks for the given period of time.
311 ///
312 /// Returns an error if sleep is interrupted by a signal.
313 ///
314 /// # Examples
315 ///
316 /// ```
317 /// use std::time::{Duration, Instant};
318 ///
319 /// use magnus::{Error, Ruby};
320 ///
321 /// fn example(ruby: &Ruby) -> Result<(), Error> {
322 /// let now = Instant::now();
323 /// ruby.thread_sleep(Duration::from_millis(100))?;
324 /// let elapsed = now.elapsed();
325 /// assert!(elapsed.as_millis() > 90);
326 /// assert!(elapsed.as_secs() < 1);
327 ///
328 /// Ok(())
329 /// }
330 /// # Ruby::init(example).unwrap()
331 /// ```
332 pub fn thread_sleep(&self, duration: Duration) -> Result<(), Error> {
333 let t = timeval {
334 tv_sec: duration.as_secs() as _,
335 tv_usec: duration.subsec_micros() as _,
336 };
337 protect(|| {
338 unsafe { rb_thread_wait_for(t) };
339 self.qnil()
340 })?;
341 Ok(())
342 }
343
344 /// Blocks indefinitely.
345 ///
346 /// Returns an error if sleep is interrupted by a signal.
347 pub fn thread_sleep_forever(&self) -> Result<(), Error> {
348 protect(|| {
349 unsafe { rb_thread_sleep_forever() };
350 self.qnil()
351 })?;
352 Ok(())
353 }
354
355 /// Blocks indefinitely.
356 ///
357 /// The thread calling this function is considered "dead" when Ruby's
358 /// deadlock checker is triggered.
359 /// See also [`thread_sleep_forever`](Ruby::thread_sleep_forever).
360 ///
361 /// Returns an error if sleep is interrupted by a signal.
362 pub fn thread_sleep_deadly(&self) -> Result<(), Error> {
363 protect(|| {
364 unsafe { rb_thread_sleep_deadly() };
365 self.qnil()
366 })?;
367 Ok(())
368 }
369
370 /// Stop the current thread.
371 ///
372 /// The thread can later be woken up, see [`Thread::wakeup`].
373 ///
374 /// Returns an error if stopping the current thread would deadlock.
375 pub fn thread_stop(&self) -> Result<(), Error> {
376 protect(|| {
377 unsafe { rb_thread_sleep_forever() };
378 self.qnil()
379 })?;
380 Ok(())
381 }
382
383 /// Check for, and run, pending interrupts.
384 ///
385 /// While Ruby is running a native extension function (such as one written
386 /// in Rust with Magnus) it can't process interrupts (e.g. signals or
387 /// `Thread#raise` called from another thread). Periodically calling this
388 /// function in any long running function will check for *and run* any
389 /// queued interrupts. This will allow your long running function to be
390 /// interrupted with say ctrl-c or `Timeout::timeout`.
391 ///
392 /// If any interrupt raises an error it will be returned as `Err`.
393 ///
394 /// Calling this function may execute code on another thread.
395 pub fn thread_check_ints(&self) -> Result<(), Error> {
396 protect(|| {
397 unsafe { rb_thread_check_ints() };
398 self.qnil()
399 })?;
400 Ok(())
401 }
402}
403
404/// Wrapper type for a Value known to be an instance of Ruby's Thread class.
405///
406/// See the [`ReprValue`] and [`Object`] traits for additional methods
407/// available on this type. See [`Ruby`](Ruby#thread) for methods to create a
408/// `Thread`.
409#[derive(Clone, Copy)]
410#[repr(transparent)]
411pub struct Thread(RTypedData);
412
413impl Thread {
414 /// Return `Some(Thread)` if `val` is a `Thread`, `None` otherwise.
415 ///
416 /// # Examples
417 ///
418 /// ```
419 /// use magnus::eval;
420 /// # let _cleanup = unsafe { magnus::embed::init() };
421 ///
422 /// assert!(magnus::Thread::from_value(eval("Thread.new {1 + 2}").unwrap()).is_some());
423 /// assert!(magnus::Thread::from_value(eval("Proc.new {1 + 2}").unwrap()).is_none());
424 /// ```
425 #[inline]
426 pub fn from_value(val: Value) -> Option<Self> {
427 RTypedData::from_value(val)
428 .filter(|_| val.is_kind_of(Ruby::get_with(val).class_thread()))
429 .map(Self)
430 }
431
432 #[inline]
433 pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self {
434 Self(RTypedData::from_rb_value_unchecked(val))
435 }
436
437 /// Mark `self` as eligible for scheduling.
438 ///
439 /// See also [`Thread::wakeup_alive`] and [`Thread::run`].
440 ///
441 /// The thread is not scheduled immediately, simply marked as available.
442 /// The thread may also remain blocked on IO.
443 ///
444 /// Returns an error `self` is dead.
445 pub fn wakeup(self) -> Result<(), Error> {
446 let ruby = Ruby::get_with(self);
447 protect(|| {
448 unsafe { rb_thread_wakeup(self.as_rb_value()) };
449 ruby.qnil()
450 })?;
451 Ok(())
452 }
453
454 /// Mark `self` as eligible for scheduling.
455 ///
456 /// See also [`Thread::wakeup`] and [`Thread::run`].
457 ///
458 /// The thread is not scheduled immediately, simply marked as available.
459 /// The thread may also remain blocked on IO.
460 pub fn wakeup_alive(self) {
461 unsafe { rb_thread_wakeup_alive(self.as_rb_value()) };
462 }
463
464 /// Mark `self` as eligible for scheduling and invoke the thread schedular.
465 ///
466 /// See also [`Thread::wakeup`] and [`Thread::wakeup_alive`].
467 ///
468 /// There is no guarantee that `self` will be the next thread scheduled.
469 ///
470 /// Returns an error `self` is dead.
471 pub fn run(self) -> Result<(), Error> {
472 let ruby = Ruby::get_with(self);
473 protect(|| {
474 unsafe { rb_thread_run(self.as_rb_value()) };
475 ruby.qnil()
476 })?;
477 Ok(())
478 }
479
480 /// Terminates `self`.
481 ///
482 /// Returns an error if the `self` is the current or main thread, returning
483 /// this error to Ruby will end the process.
484 pub fn kill(self) -> Result<(), Error> {
485 let ruby = Ruby::get_with(self);
486 protect(|| {
487 unsafe { rb_thread_kill(self.as_rb_value()) };
488 ruby.qnil()
489 })?;
490 Ok(())
491 }
492
493 /// Get the value for `key` from the Fiber-local storage of the Fiber
494 /// currently executing on the thread `self`.
495 ///
496 /// When Fibers were added to Ruby this method became Fiber-local. If only
497 /// a single Fiber is run on a thread then this acts exactly like
498 /// thread-local storage. Ruby's C API does not expose true thread local
499 /// storage.
500 ///
501 /// # Examples
502 ///
503 /// ```
504 /// use magnus::{Error, Ruby};
505 ///
506 /// fn example(ruby: &Ruby) -> Result<(), Error> {
507 /// let current = ruby.thread_current();
508 /// let val: Option<String> = current.local_aref("example")?;
509 /// assert!(val.is_none());
510 ///
511 /// let other = ruby.thread_create(|ruby| {
512 /// ruby.thread_stop()?;
513 ///
514 /// let val: String = ruby.thread_current().local_aref("example")?;
515 /// assert_eq!(val, "test");
516 ///
517 /// Ok(())
518 /// });
519 ///
520 /// current.local_aset("example", "foo")?;
521 /// other.local_aset("example", "test")?;
522 ///
523 /// let val: String = current.local_aref("example")?;
524 /// assert_eq!(val, "foo");
525 ///
526 /// other.run()?;
527 ///
528 /// Ok(())
529 /// }
530 /// # Ruby::init(example).unwrap()
531 /// ```
532 pub fn local_aref<I, T>(self, key: I) -> Result<T, Error>
533 where
534 I: IntoId,
535 T: TryConvert,
536 {
537 T::try_convert(Value::new(unsafe {
538 rb_thread_local_aref(
539 self.as_rb_value(),
540 key.into_id_with(&Ruby::get_with(self)).as_rb_id(),
541 )
542 }))
543 }
544
545 /// Set the value for `key` from the Fiber-local storage of the Fiber
546 /// currently executing on the thread `self`.
547 ///
548 /// Returns `Err` if `self` is frozen.
549 ///
550 /// When Fibers were added to Ruby this method became Fiber-local. If only
551 /// a single Fiber is run on a thread then this acts exactly like
552 /// thread-local storage. Ruby's C API does not expose true thread local
553 /// storage.
554 ///
555 /// # Examples
556 ///
557 /// ```
558 /// use magnus::{Error, Ruby};
559 ///
560 /// fn example(ruby: &Ruby) -> Result<(), Error> {
561 /// let current = ruby.thread_current();
562 /// let val: Option<String> = current.local_aref("example")?;
563 /// assert!(val.is_none());
564 ///
565 /// let other = ruby.thread_create(|ruby| {
566 /// ruby.thread_stop()?;
567 ///
568 /// let val: String = ruby.thread_current().local_aref("example")?;
569 /// assert_eq!(val, "test");
570 ///
571 /// Ok(())
572 /// });
573 ///
574 /// current.local_aset("example", "foo")?;
575 /// other.local_aset("example", "test")?;
576 ///
577 /// let val: String = current.local_aref("example")?;
578 /// assert_eq!(val, "foo");
579 ///
580 /// other.run()?;
581 ///
582 /// Ok(())
583 /// }
584 /// # Ruby::init(example).unwrap()
585 /// ```
586 pub fn local_aset<I, T>(self, key: I, val: T) -> Result<(), Error>
587 where
588 I: IntoId,
589 T: IntoValue,
590 {
591 let ruby = Ruby::get_with(self);
592 let key = key.into_id_with(&ruby);
593 let val = val.into_value_with(&ruby);
594 protect(|| {
595 unsafe { rb_thread_local_aset(self.as_rb_value(), key.as_rb_id(), val.as_rb_value()) };
596 ruby.qnil()
597 })?;
598 Ok(())
599 }
600
601 /// Check if `self` has been interrupted.
602 ///
603 /// Returns true if the thread was interrupted, false otherwise. This can
604 /// be used to detect spurious wakeups.
605 pub fn interrupted(self) -> bool {
606 unsafe { rb_thread_interrupted(self.as_rb_value()) != 0 }
607 }
608}
609
610impl fmt::Display for Thread {
611 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
612 write!(f, "{}", unsafe { self.to_s_infallible() })
613 }
614}
615
616impl fmt::Debug for Thread {
617 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
618 write!(f, "{}", self.inspect())
619 }
620}
621
622impl IntoValue for Thread {
623 #[inline]
624 fn into_value_with(self, _: &Ruby) -> Value {
625 self.0.as_value()
626 }
627}
628
629impl Object for Thread {}
630
631unsafe impl private::ReprValue for Thread {}
632
633impl ReprValue for Thread {}
634
635impl TryConvert for Thread {
636 fn try_convert(val: Value) -> Result<Self, Error> {
637 Self::from_value(val).ok_or_else(|| {
638 Error::new(
639 Ruby::get_with(val).exception_type_error(),
640 format!("no implicit conversion of {} into Thread", unsafe {
641 val.classname()
642 },),
643 )
644 })
645 }
646}
647
648/// Wrap a closure in a Ruby object with no class.
649///
650/// This effectively makes the closure's lifetime managed by Ruby. It will be
651/// dropped when the returned `Value` is garbage collected.
652fn wrap_closure<F, R>(func: F) -> (*mut Option<F>, Value)
653where
654 F: FnOnce(&Ruby) -> R,
655 R: BlockReturn,
656{
657 struct Closure<F>(Option<F>, DataType);
658 unsafe impl<F> Send for Closure<F> {}
659 impl<F> DataTypeFunctions for Closure<F> {
660 fn mark(&self, marker: &gc::Marker) {
661 // Attempt to mark any Ruby values captured in a closure.
662 // Rust's closures are structs that contain all the values they
663 // have captured. This reads that struct as a slice of VALUEs and
664 // calls rb_gc_mark_locations which calls gc_mark_maybe which
665 // marks VALUEs and ignores non-VALUEs
666 marker.mark_slice(unsafe {
667 slice::from_raw_parts(
668 &self.0 as *const _ as *const Value,
669 size_of::<F>() / size_of::<Value>(),
670 )
671 });
672 }
673 }
674
675 let data_type = data_type_builder!(Closure<F>, "rust closure")
676 .free_immediately()
677 .mark()
678 .build();
679
680 let boxed = Box::new(Closure(Some(func), data_type));
681 let ptr = Box::into_raw(boxed);
682 let value = unsafe {
683 Value::new(rb_data_typed_object_wrap(
684 0, // using 0 for the class will hide the object from ObjectSpace
685 ptr as *mut _,
686 (*ptr).1.as_rb_data_type() as *const _,
687 ))
688 };
689 unsafe { (&mut (*ptr).0 as *mut Option<F>, value) }
690}