//! Functions for working with Ruby's Garbage Collector. //! //! See also [`Ruby`](Ruby#gc) for more GC related methods. use std::{marker::PhantomData, ops::Range}; use rb_sys::{ rb_gc_adjust_memory_usage, rb_gc_count, rb_gc_disable, rb_gc_enable, rb_gc_location, rb_gc_mark, rb_gc_mark_locations, rb_gc_mark_movable, rb_gc_register_address, rb_gc_register_mark_object, rb_gc_start, rb_gc_stat, rb_gc_unregister_address, VALUE, }; use crate::{ error::{protect, Error}, r_hash::RHash, symbol::IntoSymbol, value::{private::ReprValue as _, ReprValue, Value}, Ruby, }; pub(crate) mod private { use super::*; pub trait Mark { fn raw(self) -> VALUE; } pub trait Locate { fn raw(self) -> VALUE; fn from_raw(val: VALUE) -> Self; } } /// Trait indicating types that can be passed to GC marking functions. /// /// See [`Marker`]. pub trait Mark: private::Mark {} impl private::Mark for T where T: ReprValue, { fn raw(self) -> VALUE { self.as_rb_value() } } impl Mark for T where T: ReprValue {} impl private::Mark for crate::value::Opaque where T: ReprValue, { fn raw(self) -> VALUE { unsafe { Ruby::get_unchecked() } .get_inner(self) .as_rb_value() } } impl Mark for crate::value::Opaque where T: ReprValue {} /// A handle to GC marking functions. /// /// See also /// [`DataTypeFunctions::mark`](`crate::typed_data::DataTypeFunctions::mark`). pub struct Marker(PhantomData<*mut ()>); impl Marker { pub(crate) fn new() -> Self { Self(PhantomData) } /// Mark an Object. /// /// Used to mark any stored Ruby objects when implementing /// [`DataTypeFunctions::mark`](`crate::typed_data::DataTypeFunctions::mark`). pub fn mark(&self, value: T) where T: Mark, { unsafe { rb_gc_mark(value.raw()) }; } /// Mark multiple Objects. /// /// Used to mark any stored Ruby objects when implementing /// [`DataTypeFunctions::mark`](`crate::typed_data::DataTypeFunctions::mark`). pub fn mark_slice(&self, values: &[T]) where T: Mark, { let Range { start, end } = values.as_ptr_range(); unsafe { rb_gc_mark_locations(start as *const VALUE, end as *const VALUE) } } /// Mark an Object and let Ruby know it is moveable. /// /// The [`Value`] type is effectly a pointer to a Ruby object. Ruby's /// garbage collector will avoid moving objects exposed to extensions, /// unless you use this function to mark them during the GC marking phase. /// /// Used to mark any stored Ruby objects when implementing /// [`DataTypeFunctions::mark`](`crate::typed_data::DataTypeFunctions::mark`) /// and you have also implemented /// [`DataTypeFunctions::compact`](`crate::typed_data::DataTypeFunctions::compact`). /// /// Beware that any Ruby object passed to this function may later become /// invalid to use from Rust when GC is run, you must update any stored /// objects with [`Compactor::location`] inside your implementation of /// [`DataTypeFunctions::compact`](`crate::typed_data::DataTypeFunctions::compact`). pub fn mark_movable(&self, value: T) where T: Mark, { unsafe { rb_gc_mark_movable(value.raw()) }; } } /// Trait indicating types that can given to [`Compactor::location`]. pub trait Locate: private::Locate {} impl private::Locate for T where T: ReprValue, { fn raw(self) -> VALUE { self.as_rb_value() } fn from_raw(val: VALUE) -> Self { unsafe { Self::from_value_unchecked(Value::new(val)) } } } impl Locate for T where T: ReprValue {} impl private::Locate for crate::value::Opaque where T: ReprValue, { fn raw(self) -> VALUE { unsafe { Ruby::get_unchecked() } .get_inner(self) .as_rb_value() } fn from_raw(val: VALUE) -> Self { unsafe { T::from_value_unchecked(Value::new(val)) }.into() } } impl Locate for crate::value::Opaque where T: ReprValue {} /// A handle to functions relating to GC compaction. /// /// See also /// [`DataTypeFunctions::compact`](`crate::typed_data::DataTypeFunctions::compact`). pub struct Compactor(PhantomData<*mut ()>); impl Compactor { pub(crate) fn new() -> Self { Self(PhantomData) } /// Get the new location of an object. /// /// The [`Value`] type is effectly a pointer to a Ruby object. Ruby's /// garbage collector will avoid moving objects exposed to extensions, /// unless the object has been marked with /// [`mark_movable`](Marker::mark_movable). When implementing /// [`DataTypeFunctions::compact`](`crate::typed_data::DataTypeFunctions::compact`) /// you will need to update any Ruby objects you are storing. /// /// Returns a new `T` that is pointing to the object that `value` used to /// point to. If `value` hasn't moved, simply returns `value`. pub fn location(&self, value: T) -> T where T: Locate, { unsafe { T::from_raw(rb_gc_location(value.raw())) } } } /// Registers `value` to never be garbage collected. /// /// This is essentially a deliberate memory leak. /// /// # Examples /// /// ``` /// use magnus::{gc, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// // will never be collected /// let root = ruby.ary_new(); /// gc::register_mark_object(root); /// /// // won't be collected while it is in our `root` array /// let s = ruby.str_new("example"); /// root.push(s).unwrap(); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn register_mark_object(value: T) where T: Mark, { unsafe { rb_gc_register_mark_object(value.raw()) } } /// Inform Ruby's garbage collector that `valref` points to a live Ruby object. /// /// Prevents Ruby moving or collecting `valref`. This should be used on /// `static` items to prevent them being collected instead of relying on Ruby /// constants/globals to allways refrence the value. /// /// See also [`BoxValue`](crate::value::BoxValue). /// /// # Examples /// /// ``` /// use magnus::{gc, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// /// // s won't be collected even though it's on the heap /// let boxed = Box::new(s); /// gc::register_address(&*boxed); /// /// // ... /// /// // allow s to be collected /// gc::unregister_address(&*boxed); /// drop(boxed); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn register_address(valref: &T) where T: Mark, { unsafe { rb_gc_register_address(valref as *const _ as *mut VALUE) } } /// Inform Ruby's garbage collector that `valref` that was previously /// registered with [`register_address`] no longer points to a live Ruby /// object. /// /// # Examples /// /// ``` /// use magnus::{gc, Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let s = ruby.str_new("example"); /// /// // s won't be collected even though it's on the heap /// let boxed = Box::new(s); /// gc::register_address(&*boxed); /// /// // ... /// /// // allow s to be collected /// gc::unregister_address(&*boxed); /// drop(boxed); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn unregister_address(valref: &T) where T: Mark, { unsafe { rb_gc_unregister_address(valref as *const _ as *mut VALUE) } } /// # GC /// /// Functions for working with Ruby's Garbage Collector. /// /// See also the [`gc`](self) module. impl Ruby { /// Disable automatic GC runs. /// /// This could result in other Ruby api functions unexpectedly raising /// `NoMemError`. /// /// Returns `true` if GC was already disabled, `false` otherwise. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let was_disabled = ruby.gc_disable(); /// /// // GC is off /// /// // return GC to previous state /// if !was_disabled { /// ruby.gc_enable(); /// } /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_disable(&self) -> bool { unsafe { Value::new(rb_gc_disable()).to_bool() } } /// Enable automatic GC run. /// /// Garbage Collection is enabled by default, calling this function only /// makes sense if [`disable`] was previously called. /// /// Returns `true` if GC was previously disabled, `false` otherwise. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let was_disabled = ruby.gc_enable(); /// /// // GC is on /// /// // return GC to previous state /// if was_disabled { /// ruby.gc_disable(); /// } /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_enable(&self) -> bool { unsafe { Value::new(rb_gc_enable()).to_bool() } } /// Trigger a "full" GC run. /// /// This will perform a full mark phase and a complete sweep phase, but may /// not run every single proceess associated with garbage collection. /// /// Finalisers will be deferred to run later. /// /// Currently (with versions of Ruby that support compaction) it will not /// trigger compaction. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// ruby.gc_start(); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_start(&self) { unsafe { rb_gc_start() }; } /// Inform Ruby of external memory usage. /// /// The Ruby GC is run when Ruby thinks it's running out of memory, but /// won't take into account any memory allocated outside of Ruby api /// functions. This function can be used to give Ruby a more accurate idea /// of how much memory the process is using. /// /// Pass negative numbers to indicate memory has been freed. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let buf = Vec::::with_capacity(1024 * 1024); /// let mem_size = buf.capacity() * std::mem::size_of::(); /// ruby.gc_adjust_memory_usage(mem_size as isize); /// /// // ... /// /// drop(buf); /// ruby.gc_adjust_memory_usage(-(mem_size as isize)); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_adjust_memory_usage(&self, diff: isize) { unsafe { rb_gc_adjust_memory_usage(diff as _) }; } /// Returns the number of garbage collections that have been run since the /// start of the process. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let before = ruby.gc_count(); /// ruby.gc_start(); /// assert!(ruby.gc_count() > before); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_count(&self) -> usize { unsafe { rb_gc_count() as usize } } /// Returns the GC profiling value for `key`. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// assert!(ruby.gc_stat("heap_live_slots")? > 1); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_stat(&self, key: T) -> Result where T: IntoSymbol, { let sym = key.into_symbol_with(self); let mut res = 0; protect(|| { res = unsafe { rb_gc_stat(sym.as_rb_value()) as usize }; self.qnil() })?; Ok(res) } /// Returns all possible key/value pairs for [`stat`] as a Ruby Hash. /// /// # Examples /// /// ``` /// use magnus::{Error, Ruby}; /// /// fn example(ruby: &Ruby) -> Result<(), Error> { /// let stats = ruby.gc_all_stats(); /// let live_slots: usize = stats.fetch(ruby.to_symbol("heap_live_slots"))?; /// assert!(live_slots > 1); /// /// Ok(()) /// } /// # Ruby::init(example).unwrap() /// ``` pub fn gc_all_stats(&self) -> RHash { let res = self.hash_new(); unsafe { rb_gc_stat(res.as_rb_value()) }; res } } /// Disable automatic GC runs. /// /// This could result in other Ruby api functions unexpectedly raising /// `NoMemError`. /// /// Returns `true` if GC was already disabled, `false` otherwise. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_disable`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let was_disabled = gc::disable(); /// /// // GC is off /// /// // return GC to previous state /// if !was_disabled { /// gc::enable(); /// } /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_disable` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn disable() -> bool { get_ruby!().gc_disable() } /// Enable automatic GC run. /// /// Garbage Collection is enabled by default, calling this function only makes /// sense if [`disable`] was previously called. /// /// Returns `true` if GC was previously disabled, `false` otherwise. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_enable`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let was_disabled = gc::enable(); /// /// // GC is on /// /// // return GC to previous state /// if was_disabled { /// gc::disable(); /// } /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_enable` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn enable() -> bool { get_ruby!().gc_enable() } /// Trigger a "full" GC run. /// /// This will perform a full mark phase and a complete sweep phase, but may not /// run every single proceess associated with garbage collection. /// /// Finalisers will be deferred to run later. /// /// Currently (with versions of Ruby that support compaction) it will not /// trigger compaction. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_start`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// gc::start(); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_start` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn start() { get_ruby!().gc_start() } /// Inform Ruby of external memory usage. /// /// The Ruby GC is run when Ruby thinks it's running out of memory, but won't /// take into account any memory allocated outside of Ruby api functions. This /// function can be used to give Ruby a more accurate idea of how much memory /// the process is using. /// /// Pass negative numbers to indicate memory has been freed. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See /// [`Ruby::gc_adjust_memory_usage`] for the non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let buf = Vec::::with_capacity(1024 * 1024); /// let mem_size = buf.capacity() * std::mem::size_of::(); /// gc::adjust_memory_usage(mem_size as isize); /// /// // ... /// /// drop(buf); /// gc::adjust_memory_usage(-(mem_size as isize)); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_adjust_memory_usage` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn adjust_memory_usage(diff: isize) { get_ruby!().gc_adjust_memory_usage(diff) } /// Returns the number of garbage collections that have been run since the /// start of the process. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_count`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let before = gc::count(); /// gc::start(); /// assert!(gc::count() > before); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_count` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn count() -> usize { get_ruby!().gc_count() } /// Returns the GC profiling value for `key`. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_stat`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::gc; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// assert!(gc::stat("heap_live_slots").unwrap() > 1); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_stat` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn stat(key: T) -> Result where T: IntoSymbol, { let handle = get_ruby!(); handle.gc_stat(key.into_symbol_with(&handle)) } /// Returns all possible key/value pairs for [`stat`] as a Ruby Hash. /// /// # Panics /// /// Panics if called from a non-Ruby thread. See [`Ruby::gc_all_stats`] for the /// non-panicking version. /// /// # Examples /// /// ``` /// # #![allow(deprecated)] /// use magnus::{gc, Symbol}; /// # let _cleanup = unsafe { magnus::embed::init() }; /// /// let stats = gc::all_stats(); /// let live_slots: usize = stats.fetch(Symbol::new("heap_live_slots")).unwrap(); /// assert!(live_slots > 1); /// ``` #[cfg_attr( not(feature = "old-api"), deprecated(note = "please use `Ruby::gc_all_stats` instead") )] #[cfg_attr(docsrs, doc(cfg(feature = "old-api")))] #[inline] pub fn all_stats() -> RHash { get_ruby!().gc_all_stats() }