Skip to content

Commit

Permalink
implement Mutex
Browse files Browse the repository at this point in the history
  • Loading branch information
matsadler committed Dec 5, 2023
1 parent 146ef21 commit 7f13c7b
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased]
### Added
- `Thread`, `Ruby::thread_create`/`thread_create_from_fn` and other thread APIs.
- `Mutex`, `Ruby::mutex_new` and other mutex APIs.
- `Fiber`, `Ruby::fiber_new`/`fiber_new_from_fn` and other fiber APIs
(requires Ruby >= 3.1).
- `Ruby::ary_try_from_iter` is an efficient way to create a Ruby array from a
Expand Down
1 change: 1 addition & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ impl RubyGvlState {
/// as calling the current `super` method.
/// * [`Id`](#id) - low-level Symbol representation
/// * [`Integer`](#integer)
/// * [`Mutex`](#mutex)
/// * [`nil`](#nil)
/// * [`Proc`](#proc) - Ruby's blocks as objects
/// * [`Process`](#process) - external processes
Expand Down
16 changes: 9 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1085,13 +1085,13 @@
// * `rb_mod_sys_fail`:
// * `rb_mod_sys_fail_str`:
// * `rb_must_asciicompat`:
// * `rb_mutex_lock`:
// * `rb_mutex_locked_p`:
// * `rb_mutex_new`:
// * `rb_mutex_sleep`:
// * `rb_mutex_synchronize`:
// * `rb_mutex_trylock`:
// * `rb_mutex_unlock`:
//! * `rb_mutex_lock`: [`Mutex::lock`].
//! * `rb_mutex_locked_p`: [`Mutex::is_locked`].
//! * `rb_mutex_new`: [`Ruby::mutex_new`].
//! * `rb_mutex_sleep`: [`Mutex::sleep`].
//! * `rb_mutex_synchronize`: [`Mutex::synchronize`].
//! * `rb_mutex_trylock`: [`Mutex::trylock`].
//! * `rb_mutex_unlock`: [`Mutex::unlock`].
//!
//! ## `rb_n`
// * `rb_name_error`:
Expand Down Expand Up @@ -1810,6 +1810,7 @@ mod integer;
mod into_value;
pub mod method;
pub mod module;
mod mutex;
pub mod numeric;
mod object;
pub mod process;
Expand Down Expand Up @@ -1870,6 +1871,7 @@ pub use crate::{
integer::Integer,
into_value::{ArgList, IntoValue, IntoValueFromNative, KwArgs, RArrayArgList},
module::{Attr, Module, RModule},
mutex::Mutex,
numeric::Numeric,
object::Object,
r_array::RArray,
Expand Down
33 changes: 33 additions & 0 deletions src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,39 @@ where
{
}

/// Helper trait for wrapping a function with type conversions and error
/// handling, when calling [`Mutex::synchronize`](crate::Mutext::synchronize).
#[doc(hidden)]
pub trait Synchronize<Res>
where
Self: Sized + FnOnce() -> Res,
Res: BlockReturn,
{
#[inline]
unsafe fn call_convert_value(self) -> Result<Value, Error> {
(self)().into_block_return()
}

#[inline]
unsafe fn call_handle_error(self) -> Value {
let res = match std::panic::catch_unwind(AssertUnwindSafe(|| self.call_convert_value())) {
Ok(v) => v,
Err(e) => Err(Error::from_panic(e)),
};
match res {
Ok(v) => v,
Err(e) => raise(e),
}
}
}

impl<Func, Res> Synchronize<Res> for Func
where
Func: FnOnce() -> Res,
Res: BlockReturn,
{
}

/// Helper trait for wrapping a function as a Ruby method taking self and a
/// Ruby array of arguments, with type conversions and error handling.
///
Expand Down
304 changes: 304 additions & 0 deletions src/mutex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
//! Types for working with Ruby mutexes.

use std::{fmt, time::Duration};

use rb_sys::{
rb_mutex_lock, rb_mutex_locked_p, rb_mutex_new, rb_mutex_sleep, rb_mutex_synchronize,
rb_mutex_trylock, rb_mutex_unlock, VALUE,
};

use crate::{
class::RClass,
error::{protect, Error},
into_value::IntoValue,
method::{BlockReturn, Synchronize},
object::Object,
r_typed_data::RTypedData,
try_convert::TryConvert,
value::{
private::{self, ReprValue as _},
ReprValue, Value,
},
Ruby,
};

/// # `Mutex`
///
/// Functions that can be used to create Ruby `Mutex`s.
///
/// See also the [`Mutex`] type.
impl Ruby {
/// Create a Ruby Mutex.
///
/// # Examples
///
/// ```
/// use magnus::{Error, Ruby};
///
/// fn example(ruby: &Ruby) -> Result<(), Error> {
/// let lock = ruby.mutex_new();
/// assert!(!lock.is_locked());
///
/// Ok(())
/// }
/// # Ruby::init(example).unwrap()
/// ```
pub fn mutex_new(&self) -> Mutex {
unsafe { Mutex::from_rb_value_unchecked(rb_mutex_new()) }
}
}

/// Wrapper type for a Value known to be an instance of Ruby's Mutex class.
///
/// See the [`ReprValue`] and [`Object`] traits for additional methods
/// available on this type. See [`Ruby`](Ruby#mutex) for methods to create a
/// `Mutex`.
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Mutex(RTypedData);

impl Mutex {
/// Return `Some(Mutex)` if `val` is a `Mutex`, `None` otherwise.
///
/// # Examples
///
/// ```
/// use magnus::eval;
/// # let _cleanup = unsafe { magnus::embed::init() };
///
/// assert!(magnus::Mutex::from_value(eval("Mutex.new").unwrap()).is_some());
/// assert!(magnus::Mutex::from_value(eval("true").unwrap()).is_none());
/// ```
#[inline]
pub fn from_value(val: Value) -> Option<Self> {
let mutex_class: RClass = Ruby::get_with(val)
.class_object()
.funcall("const_get", ("Mutex",))
.ok()?;
RTypedData::from_value(val)
.filter(|_| val.is_kind_of(mutex_class))
.map(Self)
}

#[inline]
pub(crate) unsafe fn from_rb_value_unchecked(val: VALUE) -> Self {
Self(RTypedData::from_rb_value_unchecked(val))
}

/// Returns whether any threads currently hold the lock.
///
/// # Examples
///
/// ```
/// use magnus::{Error, Ruby};
///
/// fn example(ruby: &Ruby) -> Result<(), Error> {
/// let lock = ruby.mutex_new();
/// assert!(!lock.is_locked());
///
/// lock.lock()?;
/// assert!(lock.is_locked());
///
/// Ok(())
/// }
/// # Ruby::init(example).unwrap()
/// ```
pub fn is_locked(self) -> bool {
unsafe { Value::new(rb_mutex_locked_p(self.as_rb_value())).to_bool() }
}

/// Attempts to aquire the lock.
///
/// This method does not block. Returns true if the lock can be acquired,
/// false otherwise.
///
/// # Examples
///
/// ```
/// use magnus::{Error, Ruby};
///
/// fn example(ruby: &Ruby) -> Result<(), Error> {
/// let lock = ruby.mutex_new();
///
/// assert!(lock.trylock());
/// assert!(lock.is_locked());
///
/// assert!(!lock.trylock());
/// assert!(lock.is_locked());
///
/// Ok(())
/// }
/// # Ruby::init(example).unwrap()
/// ```
pub fn trylock(self) -> bool {
unsafe { Value::new(rb_mutex_trylock(self.as_rb_value())).to_bool() }
}

/// Acquires the lock.
///
/// This method will block the current thread until the lock can be
/// acquired. Returns `Err` on deadlock.
///
/// # Examples
///
/// ```
/// use magnus::{Error, Ruby};
///
/// fn example(ruby: &Ruby) -> Result<(), Error> {
/// let lock = ruby.mutex_new();
///
/// lock.lock()?;
/// assert!(lock.is_locked());
///
/// assert!(lock.lock().is_err());
///
/// Ok(())
/// }
/// # Ruby::init(example).unwrap()
/// ```
pub fn lock(self) -> Result<(), Error> {
protect(|| unsafe { Value::new(rb_mutex_lock(self.as_rb_value())) })?;
Ok(())
}

/// Release the lock.
///
/// Returns `Err` if the current thread does not own the lock.
///
/// # Examples
///
/// ```
/// use magnus::{Error, Ruby};
///
/// fn example(ruby: &Ruby) -> Result<(), Error> {
/// let lock = ruby.mutex_new();
/// lock.lock()?;
/// assert!(lock.is_locked());
///
/// lock.unlock()?;
/// assert!(!lock.is_locked());
///
/// Ok(())
/// }
/// # Ruby::init(example).unwrap()
/// ```
pub fn unlock(self) -> Result<(), Error> {
protect(|| unsafe { Value::new(rb_mutex_unlock(self.as_rb_value())) })?;
Ok(())
}

/// Release the lock for `timeout`, reaquiring it on wakeup.
///
/// Returns `Err` if the current thread does not own the lock.
///
/// # Examples
///
/// ```
/// use std::time::Duration;
///
/// use magnus::{Error, Ruby};
///
/// fn example(ruby: &Ruby) -> Result<(), Error> {
/// let lock = ruby.mutex_new();
/// lock.lock()?;
/// lock.sleep(Some(Duration::from_millis(10)))?;
/// lock.unlock()?;
///
/// Ok(())
/// }
/// # Ruby::init(example).unwrap()
/// ```
pub fn sleep(self, timeout: Option<Duration>) -> Result<(), Error> {
let ruby = Ruby::get_with(self);
protect(|| unsafe {
Value::new(rb_mutex_sleep(
self.as_rb_value(),
ruby.into_value(timeout.map(|d| d.as_secs_f64()))
.as_rb_value(),
))
})?;
Ok(())
}

/// Acquires the lock, runs `func`, then releases the lock.
///
/// # Examples
///
/// ```
/// use magnus::{Error, Ruby, Value};
///
/// fn example(ruby: &Ruby) -> Result<(), Error> {
/// let lock = ruby.mutex_new();
/// let mut i = 0;
/// let _: Value = lock.synchronize(|| i += 1)?;
/// assert_eq!(1, i);
///
/// Ok(())
/// }
/// # Ruby::init(example).unwrap()
/// ```
pub fn synchronize<F, R, T>(self, func: F) -> Result<T, Error>
where
F: FnOnce() -> R,
R: BlockReturn,
T: TryConvert,
{
unsafe extern "C" fn call<F, R>(arg: VALUE) -> VALUE
where
F: FnOnce() -> R,
R: BlockReturn,
{
let closure = (*(arg as *mut Option<F>)).take().unwrap();
closure.call_handle_error().as_rb_value()
}

protect(|| unsafe {
let mut some_func = Some(func);
let closure = &mut some_func as *mut Option<F> as VALUE;
Value::new(rb_mutex_synchronize(
self.as_rb_value(),
Some(call::<F, R>),
closure,
))
})
.and_then(TryConvert::try_convert)
}
}

impl fmt::Display for Mutex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", unsafe { self.to_s_infallible() })
}
}

impl fmt::Debug for Mutex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.inspect())
}
}

impl IntoValue for Mutex {
#[inline]
fn into_value_with(self, _: &Ruby) -> Value {
self.0.as_value()
}
}

impl Object for Mutex {}

unsafe impl private::ReprValue for Mutex {}

impl ReprValue for Mutex {}

impl TryConvert for Mutex {
fn try_convert(val: Value) -> Result<Self, Error> {
Self::from_value(val).ok_or_else(|| {
Error::new(
Ruby::get_with(val).exception_type_error(),
format!("no implicit conversion of {} into Mutex", unsafe {
val.classname()
},),
)
})
}
}

0 comments on commit 7f13c7b

Please sign in to comment.