diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3d9068e..bd0de8a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [macos-latest, ubuntu-latest] - ocaml-version: ["4.14.1"] + ocaml-version: ["5.1.1", "4.14.1"] steps: - uses: actions/checkout@v3 - name: OCaml/Opam cache @@ -30,7 +30,7 @@ jobs: - name: Add Opam switch to PATH run: opam var bin >> $GITHUB_PATH - name: Setup Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@v1 with: toolchain: stable - name: Setttings for cargo in OSX diff --git a/Cargo.toml b/Cargo.toml index bf39d0a..0f97467 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,14 @@ exclude = [ ] [package.metadata.docs.rs] -features = [ "without-ocamlopt" ] +features = [ "without-ocamlopt", "caml-state" ] [dependencies] ocaml-sys = "0.23" -ocaml-boxroot-sys = "0.2" +ocaml-boxroot-sys = { git = "https://gitlab.com/ocaml-rust/ocaml-boxroot.git", rev="652d92b1" } static_assertions = "1.1.0" [features] -without-ocamlopt = ["ocaml-sys/without-ocamlopt", "ocaml-boxroot-sys/without-ocamlopt"] +without-ocamlopt = ["ocaml-sys/without-ocamlopt"] caml-state = ["ocaml-sys/caml-state"] no-caml-startup = [] diff --git a/build.rs b/build.rs index 5992b3c..b8d9faa 100644 --- a/build.rs +++ b/build.rs @@ -4,10 +4,7 @@ const OCAML_INTEROP_NO_CAML_STARTUP: &str = "OCAML_INTEROP_NO_CAML_STARTUP"; fn main() { - println!( - "cargo:rerun-if-env-changed={}", - OCAML_INTEROP_NO_CAML_STARTUP - ); + println!("cargo:rerun-if-env-changed={OCAML_INTEROP_NO_CAML_STARTUP}",); if std::env::var(OCAML_INTEROP_NO_CAML_STARTUP).is_ok() { println!("cargo:rustc-cfg=feature=\"no-caml-startup\""); } diff --git a/src/boxroot.rs b/src/boxroot.rs index 6c6b366..b3b2d99 100644 --- a/src/boxroot.rs +++ b/src/boxroot.rs @@ -1,11 +1,11 @@ // Copyright (c) Viable Systems and TezEdge Contributors // SPDX-License-Identifier: MIT -use std::{marker::PhantomData, ops::Deref, sync::Once}; +use std::{marker::PhantomData, ops::Deref}; use ocaml_boxroot_sys::{ - boxroot_create, boxroot_delete, boxroot_get, boxroot_get_ref, boxroot_modify, boxroot_setup, - BoxRoot as PrimitiveBoxRoot, + boxroot_create, boxroot_delete, boxroot_get, boxroot_get_ref, boxroot_modify, boxroot_status, + BoxRoot as PrimitiveBoxRoot, Status, }; use crate::{memory::OCamlCell, OCaml, OCamlRef, OCamlRuntime}; @@ -19,15 +19,21 @@ pub struct BoxRoot { impl BoxRoot { /// Creates a new root from an [`OCaml`]`` value. pub fn new(val: OCaml) -> BoxRoot { - static INIT: Once = Once::new(); - - INIT.call_once(|| unsafe { - boxroot_setup(); - }); - - BoxRoot { - boxroot: unsafe { boxroot_create(val.raw) }, - _marker: PhantomData, + if let Some(boxroot) = unsafe { boxroot_create(val.raw) } { + BoxRoot { + boxroot, + _marker: PhantomData, + } + } else { + let status = unsafe { boxroot_status() }; + let reason = match status { + Status::NotSetup => "NotSetup", + Status::Running => "Running", + Status::ToreDown => "ToreDown", + Status::Invalid => "Invalid", + _ => "Unknown", + }; + panic!("Failed to allocate boxroot, boxroot_status() -> {}", reason,) } } @@ -39,7 +45,17 @@ impl BoxRoot { /// Roots the OCaml value `val`, returning an [`OCamlRef`]``. pub fn keep<'tmp>(&'tmp mut self, val: OCaml) -> OCamlRef<'tmp, T> { unsafe { - boxroot_modify(&mut self.boxroot, val.raw); + if !boxroot_modify(&mut self.boxroot, val.raw) { + let status = boxroot_status(); + let reason = match status { + Status::NotSetup => "NotSetup", + Status::Running => "Running", + Status::ToreDown => "ToreDown", + Status::Invalid => "Invalid", + _ => "Unknown", + }; + panic!("Failed to modify boxroot, boxroot_status() -> {}", reason,) + } &*(boxroot_get_ref(self.boxroot) as *const OCamlCell) } } diff --git a/src/compile_ok_tests.rs b/src/compile_ok_tests.rs index 7fee57d..1c476e7 100644 --- a/src/compile_ok_tests.rs +++ b/src/compile_ok_tests.rs @@ -29,7 +29,7 @@ mod test_immediate_ocamlrefs { #[test] fn test_immediate_ocamlrefs() { - let cr = unsafe { OCamlRuntime::recover_handle() }; + let cr = unsafe { OCamlRuntime::recover_handle_mut() }; assert!(test_immediate_ocamlref(cr)); } } diff --git a/src/conv/to_ocaml.rs b/src/conv/to_ocaml.rs index 963f141..37e44a3 100644 --- a/src/conv/to_ocaml.rs +++ b/src/conv/to_ocaml.rs @@ -217,7 +217,7 @@ where A: ToOCaml, { fn to_ocaml<'a>(&self, cr: &'a mut OCamlRuntime) -> OCaml<'a, OCamlList> { - let mut result = BoxRoot::new(OCaml::nil()); + let mut result = BoxRoot::new(OCaml::nil(cr)); for elt in self.iter().rev() { let ov = elt.to_boxroot(cr); let cons = alloc_cons(cr, &ov, &result); diff --git a/src/error.rs b/src/error.rs index d191659..b3a1235 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,13 +15,11 @@ impl fmt::Display for OCamlFixnumConversionError { match self { OCamlFixnumConversionError::InputTooBig(n) => write!( f, - "Input value doesn't fit in OCaml fixnum n={} > MAX_FIXNUM={}", - n, MAX_FIXNUM + "Input value doesn't fit in OCaml fixnum n={n} > MAX_FIXNUM={MAX_FIXNUM}", ), OCamlFixnumConversionError::InputTooSmall(n) => write!( f, - "Input value doesn't fit in OCaml fixnum n={} < MIN_FIXNUM={}", - n, MIN_FIXNUM + "Input value doesn't fit in OCaml fixnum n={n} < MIN_FIXNUM={MIN_FIXNUM}", ), } } diff --git a/src/lib.rs b/src/lib.rs index c5f0c48..11a30e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -303,7 +303,7 @@ pub use crate::mlvalues::{ bigarray, DynBox, OCamlBytes, OCamlException, OCamlFloat, OCamlFloatArray, OCamlInt, OCamlInt32, OCamlInt64, OCamlList, OCamlUniformArray, RawOCaml, }; -pub use crate::runtime::OCamlRuntime; +pub use crate::runtime::{OCamlRuntime, OCamlDomainLock}; pub use crate::value::OCaml; #[doc(hidden)] @@ -312,7 +312,7 @@ pub mod internal { pub use crate::memory::{alloc_tuple, caml_alloc, store_field}; pub use crate::mlvalues::tag; pub use crate::mlvalues::UNIT; - pub use ocaml_boxroot_sys::{boxroot_setup, boxroot_teardown}; + pub use ocaml_boxroot_sys::boxroot_teardown; pub use ocaml_sys::caml_hash_variant; // To bypass ocaml_sys::int_val unsafe declaration diff --git a/src/macros.rs b/src/macros.rs index d0f0788..b8e8e91 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1577,7 +1577,7 @@ macro_rules! expand_exported_function { } => { #[no_mangle] pub extern "C" fn $name( $($arg: $typ),* ) -> $crate::expand_exported_function_return!($($rtyp)*) { - let $cr = unsafe { &mut $crate::OCamlRuntime::recover_handle() }; + let $cr = unsafe { &mut $crate::OCamlRuntime::recover_handle_mut() }; $crate::expand_rooted_args_init!($cr, $($original_args)*); $crate::expand_exported_function_body!( @body $body diff --git a/src/runtime.rs b/src/runtime.rs index 95a659d..5892441 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,8 +1,11 @@ // Copyright (c) Viable Systems and TezEdge Contributors // SPDX-License-Identifier: MIT -use ocaml_boxroot_sys::{boxroot_setup, boxroot_teardown}; -use std::marker::PhantomData; +use ocaml_boxroot_sys::boxroot_teardown; +use std::{ + marker::PhantomData, + ops::{Deref, DerefMut}, +}; use crate::{memory::OCamlRef, value::OCaml}; @@ -38,6 +41,7 @@ impl OCamlRuntime { let c_args = [arg0, core::ptr::null()]; unsafe { ocaml_sys::caml_startup(c_args.as_ptr()); + ocaml_boxroot_sys::boxroot_setup_systhreads(); } }) } @@ -45,21 +49,18 @@ impl OCamlRuntime { panic!("Rust code that is called from an OCaml program should not try to initialize the runtime."); } - /// Recover the runtime handle. - /// - /// This method is used internally, do not use directly in code, only when writing tests. - /// - /// # Safety - /// - /// This function is unsafe because the OCaml runtime handle should be obtained once - /// upon initialization of the OCaml runtime and then passed around. This method exists - /// only to ease the authoring of tests. + #[doc(hidden)] #[inline(always)] - pub unsafe fn recover_handle() -> &'static mut Self { + pub unsafe fn recover_handle_mut() -> &'static mut Self { static mut RUNTIME: OCamlRuntime = OCamlRuntime { _private: () }; &mut RUNTIME } + #[inline(always)] + unsafe fn recover_handle() -> &'static Self { + Self::recover_handle_mut() + } + /// Release the OCaml runtime lock, call `f`, and re-acquire the OCaml runtime lock. pub fn releasing_runtime(&mut self, f: F) -> T where @@ -75,6 +76,10 @@ impl OCamlRuntime { raw: unsafe { reference.get_raw() }, } } + + pub fn acquire_lock() -> OCamlDomainLock { + OCamlDomainLock::new() + } } impl Drop for OCamlRuntime { @@ -108,11 +113,64 @@ impl Drop for OCamlBlockingSection { } } +pub struct OCamlDomainLock { + _private: (), +} + +extern "C" { + pub fn caml_c_thread_register() -> isize; + pub fn caml_c_thread_unregister() -> isize; +} + +impl OCamlDomainLock { + #[inline(always)] + fn new() -> Self { + unsafe { + caml_c_thread_register(); + ocaml_sys::caml_leave_blocking_section(); + }; + Self { _private: () } + } + + #[inline(always)] + fn recover_handle<'a>(&self) -> &'a OCamlRuntime { + unsafe { OCamlRuntime::recover_handle() } + } + + #[inline(always)] + fn recover_handle_mut<'a>(&self) -> &'a mut OCamlRuntime { + unsafe { OCamlRuntime::recover_handle_mut() } + } +} + +impl Drop for OCamlDomainLock { + fn drop(&mut self) { + unsafe { + ocaml_sys::caml_enter_blocking_section(); + // FIXME: breaks with OCaml 5 + // caml_c_thread_unregister(); + }; + } +} + +impl Deref for OCamlDomainLock { + type Target = OCamlRuntime; + + fn deref(&self) -> &OCamlRuntime { + self.recover_handle() + } +} + +impl DerefMut for OCamlDomainLock { + fn deref_mut(&mut self) -> &mut OCamlRuntime { + self.recover_handle_mut() + } +} + // For initializing from an OCaml-driven program #[no_mangle] extern "C" fn ocaml_interop_setup(_unit: crate::RawOCaml) -> crate::RawOCaml { - unsafe { boxroot_setup() }; ocaml_sys::UNIT } diff --git a/src/value.rs b/src/value.rs index 4ff7d61..d091a5a 100644 --- a/src/value.rs +++ b/src/value.rs @@ -374,7 +374,7 @@ impl<'a, A, Err> OCaml<'a, Result> { impl<'a, A> OCaml<'a, OCamlList> { /// Returns an OCaml nil (empty list) value. - pub fn nil() -> Self { + pub fn nil(_: &'a mut OCamlRuntime) -> Self { OCaml { _marker: PhantomData, raw: EMPTY_LIST, diff --git a/testing/rust-caller/Cargo.toml b/testing/rust-caller/Cargo.toml index 50344a6..905e882 100644 --- a/testing/rust-caller/Cargo.toml +++ b/testing/rust-caller/Cargo.toml @@ -4,8 +4,9 @@ version = "0.1.0" authors = ["Bruno Deferrari "] edition = "2018" -[dependencies.ocaml-interop] -path = "../.." +[dependencies] +ocaml-interop = { path = "../.." } +ocaml-sys = "*" [dev-dependencies] -serial_test = "*" \ No newline at end of file +once_cell = "*" diff --git a/testing/rust-caller/build.rs b/testing/rust-caller/build.rs index 2825de9..93a54ae 100644 --- a/testing/rust-caller/build.rs +++ b/testing/rust-caller/build.rs @@ -8,29 +8,29 @@ fn main() { let ocaml_callable_dir = "./ocaml"; let dune_dir = "../../_build/default/testing/rust-caller/ocaml"; Command::new("opam") - .args(&["exec", "--", "dune", "build", &format!("{}/callable.exe.o", ocaml_callable_dir)]) + .args(["exec", "--", "dune", "build", &format!("{}/callable.exe.o", ocaml_callable_dir)]) .status() .expect("Dune failed"); Command::new("rm") - .args(&["-f", &format!("{}/libcallable.a", out_dir)]) + .args(["-f", &format!("{}/libcallable.a", out_dir)]) .status() .expect("rm failed"); Command::new("rm") - .args(&["-f", &format!("{}/libcallable.o", out_dir)]) + .args(["-f", &format!("{}/libcallable.o", out_dir)]) .status() .expect("rm failed"); Command::new("cp") - .args(&[ + .args([ &format!("{}/callable.exe.o", dune_dir), - &format!("{}/libcallable.o", out_dir), + &format!("{}/callable.o", out_dir), ]) .status() .expect("File copy failed."); Command::new("ar") - .args(&[ + .args([ "qs", &format!("{}/libcallable.a", out_dir), - &format!("{}/libcallable.o", out_dir), + &format!("{}/callable.o", out_dir), ]) .status() .expect("ar failed"); diff --git a/testing/rust-caller/ocaml/dune b/testing/rust-caller/ocaml/dune index d047731..737f7af 100644 --- a/testing/rust-caller/ocaml/dune +++ b/testing/rust-caller/ocaml/dune @@ -1,3 +1,5 @@ (executables (names callable) + (libraries threads) + (flags (:standard -noautolink -cclib -lunix -cclib -lthreadsnat)) (modes object)) \ No newline at end of file diff --git a/testing/rust-caller/src/lib.rs b/testing/rust-caller/src/lib.rs index ec244b0..768fd77 100644 --- a/testing/rust-caller/src/lib.rs +++ b/testing/rust-caller/src/lib.rs @@ -4,7 +4,7 @@ extern crate ocaml_interop; #[cfg(test)] -use ocaml_interop::cons; +use ocaml_interop::{cons, OCamlDomainLock}; use ocaml_interop::{OCaml, OCamlBytes, OCamlRuntime, ToOCaml}; #[cfg(test)] use std::borrow::Borrow; @@ -140,7 +140,10 @@ pub fn verify_variant_test(cr: &mut OCamlRuntime, variant: ocaml::Movement) -> S result.to_rust(cr) } -pub fn verify_polymorphic_variant_test(cr: &mut OCamlRuntime, variant: ocaml::PolymorphicEnum) -> String { +pub fn verify_polymorphic_variant_test( + cr: &mut OCamlRuntime, + variant: ocaml::PolymorphicEnum, +) -> String { let ocaml_variant = variant.to_boxroot(cr); let result = ocaml::stringify_polymorphic_variant(cr, &ocaml_variant); result.to_rust(cr) @@ -158,23 +161,27 @@ pub fn allocate_alot(cr: &mut OCamlRuntime) -> bool { // Tests -// NOTE: required because at the moment, no synchronization is done on OCaml calls #[cfg(test)] -use serial_test::serial; +fn acquire_domain_lock() -> OCamlDomainLock { + static INIT: std::sync::Once = std::sync::Once::new(); + + INIT.call_once(|| { + OCamlRuntime::init_persistent(); + unsafe { ocaml_sys::caml_enter_blocking_section() }; + }); + + OCamlRuntime::acquire_lock() +} #[test] -#[serial] fn test_twice() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; + let mut cr = acquire_domain_lock(); assert_eq!(twice(&mut cr, 10), 20); } #[test] -#[serial] fn test_increment_bytes() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; + let mut cr = acquire_domain_lock(); assert_eq!( increment_bytes(&mut cr, "0000000000000000", 10), "1111111111000000" @@ -182,20 +189,16 @@ fn test_increment_bytes() { } #[test] -#[serial] fn test_increment_ints_list() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; + let mut cr = acquire_domain_lock(); let ints = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; let expected = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; assert_eq!(increment_ints_list(&mut cr, &ints), expected); } #[test] -#[serial] fn test_make_tuple() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; + let mut cr = acquire_domain_lock(); assert_eq!( make_tuple(&mut cr, "fst".to_owned(), 9), ("fst".to_owned(), 9) @@ -203,10 +206,8 @@ fn test_make_tuple() { } #[test] -#[serial] fn test_make_some() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; + let mut cr = acquire_domain_lock(); assert_eq!( make_some(&mut cr, "some".to_owned()), Some("some".to_owned()) @@ -214,10 +215,8 @@ fn test_make_some() { } #[test] -#[serial] fn test_make_result() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; + let mut cr = acquire_domain_lock(); assert_eq!(make_ok(&mut cr, 10), Ok(10)); assert_eq!( make_error(&mut cr, "error".to_owned()), @@ -226,18 +225,14 @@ fn test_make_result() { } #[test] -#[serial] fn test_frame_management() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; - assert_eq!(allocate_alot(&mut cr), true); + let mut cr = acquire_domain_lock(); + assert!(allocate_alot(&mut cr)); } #[test] -#[serial] fn test_record_conversion() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; + let mut cr = acquire_domain_lock(); let record = ocaml::TestRecord { i: 10, f: 5.0, @@ -251,10 +246,8 @@ fn test_record_conversion() { } #[test] -#[serial] fn test_variant_conversion() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; + let mut cr = acquire_domain_lock(); assert_eq!( verify_variant_test(&mut cr, ocaml::Movement::RotateLeft), "RotateLeft".to_owned() @@ -270,10 +263,8 @@ fn test_variant_conversion() { } #[test] -#[serial] fn test_polymorphic_variant_conversion() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; + let mut cr = acquire_domain_lock(); assert_eq!( verify_polymorphic_variant_test(&mut cr, ocaml::PolymorphicEnum::Unit), "Unit".to_owned() @@ -283,88 +274,78 @@ fn test_polymorphic_variant_conversion() { "Single(10.00)".to_owned() ); assert_eq!( - verify_polymorphic_variant_test(&mut cr, ocaml::PolymorphicEnum::Multiple(10, "text".to_string())), + verify_polymorphic_variant_test( + &mut cr, + ocaml::PolymorphicEnum::Multiple(10, "text".to_string()) + ), "Multiple(10, text)".to_owned() ); } #[test] -#[serial] fn test_bigarray() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; - + let mut cr = acquire_domain_lock(); let arr: Vec = (0..16).collect(); - let crr = &mut cr; - let arr_ocaml: BoxRoot> = arr.as_slice().to_boxroot(crr); - ocaml::double_u16_array(crr, &arr_ocaml); + let arr_ocaml: BoxRoot> = arr.as_slice().to_boxroot(&mut cr); + ocaml::double_u16_array(&mut cr, &arr_ocaml); assert_eq!( - crr.get(&arr_ocaml).as_slice(), + cr.get(&arr_ocaml).as_slice(), (0..16u16).map(|i| i * 2).collect::>().as_slice() ); } #[test] -#[serial] fn test_exception_handling_with_message() { - OCamlRuntime::init_persistent(); + let mut cr = acquire_domain_lock(); let result = std::panic::catch_unwind(move || { - let mut cr = unsafe { OCamlRuntime::recover_handle() }; - let mcr = &mut cr; - let message = "my-error-message".to_boxroot(mcr); - ocaml::raises_message_exception(mcr, &message); + let message = "my-error-message".to_boxroot(&mut cr); + ocaml::raises_message_exception(&mut cr, &message); }); assert_eq!( result .err() - .and_then(|err| Some(err.downcast_ref::().unwrap().clone())) + .map(|err| err.downcast_ref::().unwrap().clone()) .unwrap(), "OCaml exception, message: Some(\"my-error-message\")" ); } #[test] -#[serial] fn test_exception_handling_without_message() { - OCamlRuntime::init_persistent(); - let result = std::panic::catch_unwind(|| { - let cr = unsafe { OCamlRuntime::recover_handle() }; - ocaml::raises_nonmessage_exception(cr, &OCaml::unit()); + let mut cr = acquire_domain_lock(); + let result = std::panic::catch_unwind(move || { + ocaml::raises_nonmessage_exception(&mut cr, &OCaml::unit()); }); assert_eq!( result .err() - .and_then(|err| Some(err.downcast_ref::().unwrap().clone())) + .map(|err| err.downcast_ref::().unwrap().clone()) .unwrap(), "OCaml exception, message: None" ); } #[test] -#[serial] fn test_exception_handling_nonblock_exception() { - OCamlRuntime::init_persistent(); - let result = std::panic::catch_unwind(|| { - let cr = unsafe { OCamlRuntime::recover_handle() }; - ocaml::raises_nonblock_exception(cr, &OCaml::unit()); + let mut cr = acquire_domain_lock(); + let result = std::panic::catch_unwind(move || { + ocaml::raises_nonblock_exception(&mut cr, &OCaml::unit()); }); assert_eq!( result .err() - .and_then(|err| Some(err.downcast_ref::().unwrap().clone())) + .map(|err| err.downcast_ref::().unwrap().clone()) .unwrap(), "OCaml exception, message: None" ); } #[test] -#[serial] fn test_dynbox() { - OCamlRuntime::init_persistent(); - let mut cr = unsafe { OCamlRuntime::recover_handle() }; + let mut cr = acquire_domain_lock(); - let mut list = OCaml::nil().root(); + let mut list = OCaml::nil(&mut cr).root(); let mut l2; // Note: building a list with cons will build it in reverse order for e in (0u16..4).rev() { @@ -381,3 +362,26 @@ fn test_dynbox() { ocaml::gc_compact(&mut cr, OCaml::unit().as_ref()); assert_eq!(vec2, vec![3, 2, 1, 0]); } + +#[test] +fn test_threads() { + // Create a vector to store the handles of the spawned threads + let mut handles = Vec::new(); + + // Spawn 100 threads + for n in 0..100 { + let handle = std::thread::spawn(move || { + let mut cr = acquire_domain_lock(); + println!("thread: {n}"); + allocate_alot(&mut cr) + }); + + handles.push((n, handle)); + } + std::thread::sleep(std::time::Duration::from_secs(1)); + // Wait for all of the threads to finish + for (n, handle) in handles { + println!("Joining thread {n}"); + assert!(handle.is_finished()); + } +}