mmtk 0.33.0

MMTk is a framework for the design and implementation of high-performance and portable memory managers.
Documentation
use std::{
    sync::{Condvar, Mutex, MutexGuard},
    thread::JoinHandle,
    time::Duration,
};

use super::mock_test_prelude::*;
use crate::{
    util::{options::GCTriggerSelector, Address, OpaquePointer, VMThread, VMWorkerThread},
    MMTKBuilder, MMTK,
};

#[derive(Default)]
struct ShutdownTestShared {
    sync: Mutex<ShutdownTestSync>,
    all_threads_spawned: Condvar,
    all_threads_exited: Condvar,
}

#[derive(Default)]
struct ShutdownTestSync {
    join_handles: Vec<JoinHandle<()>>,
    spawned_threads: usize,
    exited_threads: usize,
}

lazy_static! {
    static ref SHARED: ShutdownTestShared = ShutdownTestShared::default();
}

const NUM_WORKER_THREADS: usize = 4;
const TIMEOUT: Duration = Duration::from_secs(5);

fn wait_timeout_while<'a, T, F>(
    guard: MutexGuard<'a, T>,
    condvar: &Condvar,
    condition: F,
) -> MutexGuard<'a, T>
where
    F: FnMut(&mut T) -> bool,
{
    let (guard, timeout_result) = condvar
        .wait_timeout_while(guard, TIMEOUT, condition)
        .unwrap();
    assert!(!timeout_result.timed_out());
    guard
}

fn simple_spawn_gc_thread(
    _vm_thread: VMThread,
    context: GCThreadContext<MockVM>,
    mmtk: &'static MMTK<MockVM>,
) {
    let GCThreadContext::Worker(worker) = context;
    let join_handle = std::thread::spawn(move || {
        let ordinal = worker.ordinal;
        println!("GC thread starting. Ordinal: {ordinal}");

        let gc_thread_tls = VMWorkerThread(VMThread(OpaquePointer::from_address(Address::ZERO)));
        memory_manager::start_worker(mmtk, gc_thread_tls, worker);

        let mut sync = SHARED.sync.lock().unwrap();
        sync.exited_threads += 1;
        if sync.exited_threads == NUM_WORKER_THREADS {
            SHARED.all_threads_exited.notify_all();
        }

        println!("GC thread stopped. Ordinal: {ordinal}");
    });

    let mut sync = SHARED.sync.lock().unwrap();
    sync.join_handles.push(join_handle);
    sync.spawned_threads += 1;
    if sync.spawned_threads == NUM_WORKER_THREADS {
        SHARED.all_threads_spawned.notify_all();
    }
}

#[test]
pub fn test_shutdown_stops_gc_threads() {
    let mut builder = MMTKBuilder::new();
    let trigger = GCTriggerSelector::FixedHeapSize(1024 * 1024);
    builder.options.gc_trigger.set(trigger);
    builder.options.threads.set(NUM_WORKER_THREADS);
    let mmtk: &'static mut MMTK<MockVM> = Box::leak(Box::new(builder.build::<MockVM>()));

    let mock_vm = MockVM {
        spawn_gc_thread: MockMethod::new_fixed(Box::new(|(vm_thread, context)| {
            simple_spawn_gc_thread(vm_thread, context, mmtk)
        })),
        ..Default::default()
    };
    write_mockvm(move |mock_vm_ref| *mock_vm_ref = mock_vm);

    let test_thread_tls = VMThread(OpaquePointer::from_address(Address::ZERO));
    mmtk.initialize_collection(test_thread_tls);

    let join_handles = {
        let sync = SHARED.sync.lock().unwrap();
        let mut sync = wait_timeout_while(sync, &SHARED.all_threads_spawned, |sync| {
            sync.spawned_threads < NUM_WORKER_THREADS
        });
        std::mem::take(&mut sync.join_handles)
    };

    assert_eq!(join_handles.len(), NUM_WORKER_THREADS);

    memory_manager::mmtk_shutdown(mmtk);

    println!("Waiting for GC worker threads to stop");

    {
        let sync = SHARED.sync.lock().unwrap();
        let sync = wait_timeout_while(sync, &SHARED.all_threads_exited, |sync| {
            sync.exited_threads < NUM_WORKER_THREADS
        });
        assert_eq!(sync.exited_threads, NUM_WORKER_THREADS);
    }

    assert!(!mmtk.state.is_initialized());

    for join_handle in join_handles {
        join_handle.join().unwrap();
    }
}