GC-safety
All the examples we've seen involve trivial code that takes almost no time to run. In realistic scenario's, though, there's a good chance we want to call a function that doesn't call into Julia and takes a long time to run. If we don't call into Julia we won't hit any safepoints. This is a problem if we use multiple threads; if the GC needs to run, any thread that hits a safepoint will wait there until the GC is done, so if one thread is not hitting any safepoints, the other threads will quickly be blocked.
A thread that can call into Julia is normally in a GC-unsafe state, the unsafe here means that the GC must wait until the thread has reached a safepoint before it can run. We can also put it in a GC-safe state where the GC can assume the thread won't call into Julia and doesn't need to wait until that thread has reached a safepoint.
If an exported function doesn't need to call into Julia at all, we can ensure it's called in a GC-safe state by annotating the export with #[gc_safe]
. To simulate a long-running function we're going to sleep for a few seconds.
use std::{thread::sleep, time::Duration};
use jlrs::prelude::*;
fn add(a: f64, b: f64) -> f64 {
sleep(Duration::from_secs(5));
a + b
}
julia_module! {
become julia_module_tutorial_init_fn;
#[gc_safe]
fn add(a: f64, b: f64) -> f64;
}
We can manually create gc-safe blocks.
use std::{thread::sleep, time::Duration};
use jlrs::{data::managed::array::TypedVectorRet, memory::gc::gc_safe, prelude::*, weak_handle};
fn some_operation(len: usize) -> TypedVectorRet<f64> {
match weak_handle!() {
Ok(handle) => {
// Safety: we don't call into Julia in this GC-safe block
let data = unsafe {
gc_safe(|| {
sleep(Duration::from_secs(5));
vec![0.0f64; len]
})
};
TypedVector::from_vec(handle, data, len)
.expect("size invalid")
.expect("caught exception")
.leak()
}
_ => panic!("not called from Julia"),
}
}
julia_module! {
become julia_module_tutorial_init_fn;
fn some_operation(len: usize) -> TypedVectorRet<f64>;
}
It's possible to revert to a GC-unsafe state in a GC-safe block by inserting a GC-unsafe block with gc_unsafe
.
use std::{thread::sleep, time::Duration};
use jlrs::{
data::managed::array::TypedVectorRet,
memory::gc::{gc_safe, gc_unsafe},
prelude::*,
};
fn some_operation(len: usize) -> TypedVectorRet<f64> {
// Safety: we don't call into Julia in this GC-safe block except in the GC-unsafe block
unsafe {
gc_safe(|| {
sleep(Duration::from_secs(5));
let data = vec![0.0f64; len];
gc_unsafe(|unrooted| {
TypedVector::from_vec(unrooted, data, len)
.expect("size invalid")
.expect("caught exception")
.leak()
})
})
}
}
julia_module! {
become julia_module_tutorial_init_fn;
fn some_operation(len: usize) -> TypedVectorRet<f64>;
}