Skip to content

Commit

Permalink
Update documentation and changelog
Browse files Browse the repository at this point in the history
  • Loading branch information
filmor committed Jul 7, 2024
1 parent addced2 commit 77a7405
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 60 deletions.
8 changes: 4 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ versions.
- Resource type registration has been refactored to eventually remove the
`rustler::resource!` macro (#617, necessary due to a pending deprecation of a
Rust feature, #606)
- Resources can now explicitly implement the new `Resource` trait and provide a
custom `destructor` function that is run before `drop` and receives an `Env`
parameter (#617)
- Resources can (and should) now explicitly implement the new `Resource` trait
and provide a custom `destructor` function that is run before `drop` and
receives an `Env` parameter (#617)
- Process monitoring via resources can now be used on resource types that
implement the `MonitorResource` trait (#617)
implement the `Resource::down` callback (#617)

### Fixed

Expand Down
12 changes: 6 additions & 6 deletions rustler/src/resource/handle.rs → rustler/src/resource/arc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ where
}

fn from_term(term: Term) -> Result<Self, Error> {
let (raw, inner) = unsafe { term.get_resource_ptrs::<T>() }.ok_or(Error::BadArg)?;
let (raw, inner) = unsafe { term.try_get_resource_ptrs::<T>() }.ok_or(Error::BadArg)?;
unsafe { rustler_sys::enif_keep_resource(raw) };
Ok(ResourceArc { raw, inner })
}
Expand All @@ -119,7 +119,7 @@ impl<T> ResourceArc<T>
where
T: Resource,
{
pub fn monitor(&self, caller_env: Option<&Env>, pid: &LocalPid) -> Option<Monitor> {
pub fn monitor(&self, caller_env: Option<Env>, pid: &LocalPid) -> Option<Monitor> {
if !T::IMPLEMENTS_DOWN {
return None;
}
Expand All @@ -136,7 +136,7 @@ where
}
}

pub fn demonitor(&self, caller_env: Option<&Env>, mon: &Monitor) -> bool {
pub fn demonitor(&self, caller_env: Option<Env>, mon: &Monitor) -> bool {
if !T::IMPLEMENTS_DOWN {
return false;
}
Expand Down Expand Up @@ -166,11 +166,11 @@ impl<'a> Env<'a> {
resource: &ResourceArc<T>,
pid: &LocalPid,
) -> Option<Monitor> {
resource.monitor(Some(self), pid)
resource.monitor(Some(*self), pid)
}

pub fn demonitor<T: Resource>(&self, resource: &ResourceArc<T>, mon: &Monitor) -> bool {
resource.demonitor(Some(self), mon)
resource.demonitor(Some(*self), mon)
}
}

Expand Down Expand Up @@ -238,7 +238,7 @@ where
}
}

fn maybe_env(env: Option<&Env>) -> *mut ErlNifEnv {
fn maybe_env(env: Option<Env>) -> *mut ErlNifEnv {
if is_scheduler_thread() {
let env = env.expect("Env required when calling from a scheduler thread");
// Panic if `env` is not the environment of the calling process.
Expand Down
3 changes: 3 additions & 0 deletions rustler/src/resource/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/// Indicates that a resource has not been registered successfully
#[derive(Clone, Copy, Debug)]
pub struct ResourceInitError;
54 changes: 5 additions & 49 deletions rustler/src/resource/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,63 +4,19 @@
//! NIF calls. The struct will be automatically dropped when the BEAM GC decides that there are no
//! more references to the resource.
mod handle;
mod arc;
mod error;
mod monitor;
mod registration;
mod term;
mod traits;
mod util;

use std::mem::MaybeUninit;

use super::{Decoder, Error, NifResult, Term};

pub use handle::ResourceArc;
pub use arc::ResourceArc;
pub use error::ResourceInitError;
pub use monitor::Monitor;
use rustler_sys::c_void;
pub use traits::Resource;
use traits::ResourceExt;
use util::align_alloced_mem_for_struct;

impl<'a> Term<'a> {
unsafe fn get_resource_ptrs<T: Resource>(&self) -> Option<(*const c_void, *mut T)> {
let typ = T::get_resource_type()?;
let mut ret_obj = MaybeUninit::uninit();
let res = rustler_sys::enif_get_resource(
self.get_env().as_c_arg(),
self.as_c_arg(),
typ,
ret_obj.as_mut_ptr(),
);

if res == 0 {
None
} else {
let res = ret_obj.assume_init();
Some((res, align_alloced_mem_for_struct::<T>(res) as *mut T))
}
}

pub fn get_resource<T: Resource>(&self) -> Option<&'a T> {
unsafe { self.get_resource_ptrs().map(|(_, ptr)| &*ptr) }
}

pub unsafe fn get_mut_resource<T: Resource>(&self) -> Option<&'a mut T> {
self.get_resource_ptrs().map(|(_, ptr)| &mut *ptr)
}
}

impl<'a, T> Decoder<'a> for &'a T
where
T: Resource + 'a,
{
fn decode(term: Term<'a>) -> NifResult<Self> {
term.get_resource().ok_or(Error::BadArg)
}
}

/// Indicates that a resource has not been registered successfully
#[derive(Clone, Copy, Debug)]
pub struct ResourceInitError;

#[macro_export]
macro_rules! resource {
Expand Down
4 changes: 4 additions & 0 deletions rustler/src/resource/monitor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use rustler_sys::ErlNifMonitor;

/// Handle for a monitor created using `ResourceArc<T>::monitor`.
///
/// A monitor handle can be compared to other monitor handles. It is opaque and freely copyable.
/// The monitor will not become inactive if this object is dropped.
#[derive(Copy, Clone)]
pub struct Monitor {
inner: ErlNifMonitor,
Expand Down
17 changes: 16 additions & 1 deletion rustler/src/resource/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,21 @@ struct Registration {
}

impl<'a> Env<'a> {
/// Register a resource type, see `Registration::register`.
pub fn register<T: Resource>(&self) -> Result<(), ResourceInitError> {
Registration::new::<T>().register(*self)
}
}

/// Resource registration
///
/// The type name is derived using Rust's `std::any::type_name` and the callbacks are registered
/// according to the `IMPLEMENTS_...` associated constants on the `Resource` trait implementation.
/// A destructor is always registered if the type requires explicit `drop`ping (checked using
/// `std::mem::needs_drop`). All other callbacks are only registered if `IMPLEMENTS_...` is set to
/// `true`.
impl Registration {
/// Generate a new (pending) resource type registration.
pub const fn new<T: Resource>() -> Self {
Self {
init: ErlNifResourceTypeInit {
Expand Down Expand Up @@ -72,6 +81,9 @@ impl Registration {
}
}

/// Try to register the resource type for which this registration was created. This function
/// will only succeed when called from the `load` callback and if this type has not yet been
/// registered.
pub fn register(&self, env: Env) -> Result<(), ResourceInitError> {
if !env.init {
return Err(ResourceInitError);
Expand Down Expand Up @@ -106,7 +118,10 @@ where
let aligned = align_alloced_mem_for_struct::<T>(handle);
// Destructor takes ownership, thus the resource object will be dropped after the function has
// run.
ptr::read::<T>(aligned as *mut T).destructor(env);
let obj = ptr::read::<T>(aligned as *mut T);
if T::IMPLEMENTS_DESTRUCTOR {
obj.destructor(env);
}
}

unsafe extern "C" fn resource_down<T: Resource>(
Expand Down
43 changes: 43 additions & 0 deletions rustler/src/resource/term.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use super::util::align_alloced_mem_for_struct;
use super::{Resource, ResourceExt};
use crate::{Decoder, Error, NifResult, Term};
use rustler_sys::c_void;
use std::mem::MaybeUninit;

impl<'a> Term<'a> {
/// Internal method to retrieve both the "real" resource pointer as well as a pointer to the
/// `T`-aligned region.
pub(super) unsafe fn try_get_resource_ptrs<T: Resource>(
&self,
) -> Option<(*const c_void, *mut T)> {
let typ = T::get_resource_type()?;
let mut ret_obj = MaybeUninit::uninit();
let res = rustler_sys::enif_get_resource(
self.get_env().as_c_arg(),
self.as_c_arg(),
typ,
ret_obj.as_mut_ptr(),
);

if res == 0 {
None
} else {
let res = ret_obj.assume_init();
Some((res, align_alloced_mem_for_struct::<T>(res) as *mut T))
}
}

/// Try to retrieve a reference to a resource object of type `T` from this term.
pub fn try_get_resource<T: Resource>(&self) -> Option<&'a T> {
unsafe { self.try_get_resource_ptrs().map(|(_, ptr)| &*ptr) }
}
}

impl<'a, T> Decoder<'a> for &'a T
where
T: Resource + 'a,
{
fn decode(term: Term<'a>) -> NifResult<Self> {
term.try_get_resource().ok_or(Error::BadArg)
}
}
26 changes: 26 additions & 0 deletions rustler/src/resource/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ use crate::{Env, LocalPid, Monitor};

type NifResourcePtr = *const rustler_sys::ErlNifResourceType;

/// Map from `TypeId` to the `NifResourcePtr`. To be able to store this in a `OnceLock`, the
/// pointer is type-erased and stored as a `usize`.
static mut RESOURCE_TYPES: OnceLock<HashMap<TypeId, usize>> = OnceLock::new();

/// Register an Erlang resource type handle for a particular type given by its `TypeId`
pub(crate) unsafe fn register_resource_type(type_id: TypeId, resource_type: NifResourcePtr) {
RESOURCE_TYPES.get_or_init(Default::default);
RESOURCE_TYPES
Expand All @@ -16,20 +19,43 @@ pub(crate) unsafe fn register_resource_type(type_id: TypeId, resource_type: NifR
.insert(type_id, resource_type as usize);
}

/// Trait that needs to be implemented to use a type as a NIF resource type.
///
/// The Erlang runtime provides the following guarantees:
/// - An object will be valid as long as the associated environment is valid
/// - `destructor` is the last function that is run on an object before it is freed
///
/// In particular, the type needs to handle all synchronization itself (thus we require it to
/// implement `Sync`) and callbacks or NIFs can run on arbitrary threads (thus we require `Send`).
///
/// Currently only `destructor` and `down` callbacks are possible. If a callback is implemented,
/// the respective associated constant `IMPLEMENTS_...` must be set to `true` for the registration
/// to take it into account. All callbacks provide (empty) default implementations.
pub trait Resource: Sized + Send + Sync + 'static {
const IMPLEMENTS_DESTRUCTOR: bool = false;
const IMPLEMENTS_DOWN: bool = false;

/// Callback function that is executed right before dropping a resource object.
///
/// This callback does not have to be implemented to release associated resources or run
/// constructors. For that it is enough to implement `Drop` or rely on the generated `Drop`
/// implementation which will be called in any case. The function is useful when the cleanup
/// requires access to a NIF env, e.g. to send messages.
#[allow(unused_mut, unused)]
fn destructor(mut self, env: Env<'_>) {}

/// Callback function to handle process monitoring.
///
/// This callback is called when a process monitored using `Env::monitor` terminates
/// and receives the `pid` of that process as well as the `Monitor` instance that was returned
/// by `ResourceArc<T>::monitor`.
#[allow(unused)]
fn down<'a>(&'a self, env: Env<'a>, pid: LocalPid, monitor: Monitor) {}
}

#[doc(hidden)]
pub(crate) trait ResourceExt: 'static {
/// Get the NIF resource type handle for this type if it had been registered before
fn get_resource_type() -> Option<NifResourcePtr> {
let map = unsafe { RESOURCE_TYPES.get()? };
map.get(&TypeId::of::<Self>())
Expand Down

0 comments on commit 77a7405

Please sign in to comment.