Skip to content

Commit 5ab62f9

Browse files
authored
Remove coordinator and support forking (#1067)
This PR removes the coordinator (a.k.a. controller) thread, and allows temporarily terminating and restarting all GC worker threads in order to support forking. Major changes include: - `GCController` is removed. All synchronization mechanisms involving the controller are removed, too. Important synchronization operations, such as opening buckets and declaring GC finished, are done by the last parked worker. The work packet `EndOfGC` is removed, and its job is now done by the last parked worker. - The `WorkerMonitor`, which previously synchronizes between GC workers, now also synchronizes between mutators and GC workers. This allows mutators to trigger GC by directly communicating with GC workers. - Introduced a new mechanism: "goals". Mutators can now request "goals", and GC workers will work on one goal at a time. Currently, a "goal" can be either GC or StopForFork. Triggering GC is now implemented as requesting the GC goal. - Added a pair of new APIs, namely `MMTK::prepare_to_fork()` and `MMTK::after_fork()`. VM bindings call them before and after forking to let the MMTK instance do proper preparation for forking. Fixes: #1053 Fixes: #1054
1 parent 86518d2 commit 5ab62f9

21 files changed

+1277
-579
lines changed

docs/header/mmtk.h

-3
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,6 @@ extern void mmtk_scan_region();
7878
// Request MMTk to trigger a GC. Note that this may not actually trigger a GC
7979
extern void mmtk_handle_user_collection_request(void* tls);
8080

81-
// Run the main loop for the GC controller thread. Does not return
82-
extern void mmtk_start_control_collector(void* tls, void* worker);
83-
8481
// Run the main loop for a GC worker. Does not return
8582
extern void mmtk_start_worker(void* tls, void* worker);
8683

src/global_state.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
22
use std::sync::Mutex;
3+
use std::time::Instant;
4+
5+
use atomic_refcell::AtomicRefCell;
36

47
/// This stores some global states for an MMTK instance.
58
/// Some MMTK components like plans and allocators may keep an reference to the struct, and can access it.
@@ -15,6 +18,8 @@ pub struct GlobalState {
1518
pub(crate) initialized: AtomicBool,
1619
/// The current GC status.
1720
pub(crate) gc_status: Mutex<GcStatus>,
21+
/// When did the last GC start? Only accessed by the last parked worker.
22+
pub(crate) gc_start_time: AtomicRefCell<Option<Instant>>,
1823
/// Is the current GC an emergency collection? Emergency means we may run out of memory soon, and we should
1924
/// attempt to collect as much as we can.
2025
pub(crate) emergency_collection: AtomicBool,
@@ -195,6 +200,7 @@ impl Default for GlobalState {
195200
Self {
196201
initialized: AtomicBool::new(false),
197202
gc_status: Mutex::new(GcStatus::NotInGC),
203+
gc_start_time: AtomicRefCell::new(None),
198204
stacks_prepared: AtomicBool::new(false),
199205
emergency_collection: AtomicBool::new(false),
200206
user_triggered_collection: AtomicBool::new(false),

src/memory_manager.rs

+8-41
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use crate::mmtk::MMTK;
1616
use crate::plan::AllocationSemantics;
1717
use crate::plan::{Mutator, MutatorContext};
1818
use crate::scheduler::WorkBucketStage;
19-
use crate::scheduler::{GCController, GCWork, GCWorker};
19+
use crate::scheduler::{GCWork, GCWorker};
2020
use crate::util::alloc::allocators::AllocatorSelector;
2121
use crate::util::constants::{LOG_BYTES_IN_PAGE, MIN_OBJECT_SIZE};
2222
use crate::util::heap::layout::vm_layout::vm_layout;
@@ -25,7 +25,7 @@ use crate::util::{Address, ObjectReference};
2525
use crate::vm::edge_shape::MemorySlice;
2626
use crate::vm::ReferenceGlue;
2727
use crate::vm::VMBinding;
28-
use std::sync::atomic::Ordering;
28+
2929
/// Initialize an MMTk instance. A VM should call this method after creating an [`crate::MMTK`]
3030
/// instance but before using any of the methods provided in MMTk (except `process()` and `process_bulk()`).
3131
///
@@ -438,6 +438,7 @@ pub fn free_with_size<VM: VMBinding>(mmtk: &MMTK<VM>, addr: Address, old_size: u
438438
/// Get the current active malloc'd bytes. Here MMTk only accounts for bytes that are done through those 'counted malloc' functions.
439439
#[cfg(feature = "malloc_counted_size")]
440440
pub fn get_malloc_bytes<VM: VMBinding>(mmtk: &MMTK<VM>) -> usize {
441+
use std::sync::atomic::Ordering;
441442
mmtk.state.malloc_bytes.load(Ordering::SeqCst)
442443
}
443444

@@ -460,53 +461,18 @@ pub fn gc_poll<VM: VMBinding>(mmtk: &MMTK<VM>, tls: VMMutatorThread) {
460461
}
461462
}
462463

463-
/// Run the main loop for the GC controller thread. This method does not return.
464-
///
465-
/// Arguments:
466-
/// * `tls`: The thread that will be used as the GC controller.
467-
/// * `gc_controller`: The execution context of the GC controller threa.
468-
/// It is the `GCController` passed to `Collection::spawn_gc_thread`.
469-
/// * `mmtk`: A reference to an MMTk instance.
470-
pub fn start_control_collector<VM: VMBinding>(
471-
_mmtk: &'static MMTK<VM>,
472-
tls: VMWorkerThread,
473-
gc_controller: &mut GCController<VM>,
474-
) {
475-
gc_controller.run(tls);
476-
}
477-
478-
/// Run the main loop of a GC worker. This method does not return.
479-
///
480-
/// Arguments:
481-
/// * `tls`: The thread that will be used as the GC worker.
482-
/// * `worker`: The execution context of the GC worker thread.
483-
/// It is the `GCWorker` passed to `Collection::spawn_gc_thread`.
484-
/// * `mmtk`: A reference to an MMTk instance.
464+
/// Wrapper for [`crate::scheduler::GCWorker::run`].
485465
pub fn start_worker<VM: VMBinding>(
486466
mmtk: &'static MMTK<VM>,
487467
tls: VMWorkerThread,
488-
worker: &mut GCWorker<VM>,
468+
worker: Box<GCWorker<VM>>,
489469
) {
490470
worker.run(tls, mmtk);
491471
}
492472

493-
/// Initialize the scheduler and GC workers that are required for doing garbage collections.
494-
/// This is a mandatory call for a VM during its boot process once its thread system
495-
/// is ready. This should only be called once. This call will invoke Collection::spawn_gc_thread()
496-
/// to create GC threads.
497-
///
498-
/// Arguments:
499-
/// * `mmtk`: A reference to an MMTk instance.
500-
/// * `tls`: The thread that wants to enable the collection. This value will be passed back to the VM in
501-
/// Collection::spawn_gc_thread() so that the VM knows the context.
473+
/// Wrapper for [`crate::mmtk::MMTK::initialize_collection`].
502474
pub fn initialize_collection<VM: VMBinding>(mmtk: &'static MMTK<VM>, tls: VMThread) {
503-
assert!(
504-
!mmtk.state.is_initialized(),
505-
"MMTk collection has been initialized (was initialize_collection() already called before?)"
506-
);
507-
mmtk.scheduler.spawn_gc_threads(mmtk, tls);
508-
mmtk.state.initialized.store(true, Ordering::SeqCst);
509-
probe!(mmtk, collection_initialized);
475+
mmtk.initialize_collection(tls);
510476
}
511477

512478
/// Process MMTk run-time options. Returns true if the option is processed successfully.
@@ -554,6 +520,7 @@ pub fn free_bytes<VM: VMBinding>(mmtk: &MMTK<VM>) -> usize {
554520
/// to call this method is at the end of a GC (e.g. when the runtime is about to resume threads).
555521
#[cfg(feature = "count_live_bytes_in_gc")]
556522
pub fn live_bytes_in_last_gc<VM: VMBinding>(mmtk: &MMTK<VM>) -> usize {
523+
use std::sync::atomic::Ordering;
557524
mmtk.state.live_bytes_in_last_gc.load(Ordering::SeqCst)
558525
}
559526

src/mmtk.rs

+90-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ impl<VM: VMBinding> MMTK<VM> {
148148

149149
let state = Arc::new(GlobalState::default());
150150

151-
let gc_requester = Arc::new(GCRequester::new());
151+
let gc_requester = Arc::new(GCRequester::new(scheduler.clone()));
152152

153153
let gc_trigger = Arc::new(GCTrigger::new(
154154
options.clone(),
@@ -220,6 +220,93 @@ impl<VM: VMBinding> MMTK<VM> {
220220
}
221221
}
222222

223+
/// Initialize the GC worker threads that are required for doing garbage collections.
224+
/// This is a mandatory call for a VM during its boot process once its thread system
225+
/// is ready.
226+
///
227+
/// Internally, this function will invoke [`Collection::spawn_gc_thread()`] to spawn GC worker
228+
/// threads.
229+
///
230+
/// # Arguments
231+
///
232+
/// * `tls`: The thread that wants to enable the collection. This value will be passed back
233+
/// to the VM in [`Collection::spawn_gc_thread()`] so that the VM knows the context.
234+
///
235+
/// [`Collection::spawn_gc_thread()`]: crate::vm::Collection::spawn_gc_thread()
236+
pub fn initialize_collection(&'static self, tls: VMThread) {
237+
assert!(
238+
!self.state.is_initialized(),
239+
"MMTk collection has been initialized (was initialize_collection() already called before?)"
240+
);
241+
self.scheduler.spawn_gc_threads(self, tls);
242+
self.state.initialized.store(true, Ordering::SeqCst);
243+
probe!(mmtk, collection_initialized);
244+
}
245+
246+
/// Prepare an MMTk instance for calling the `fork()` system call.
247+
///
248+
/// The `fork()` system call is available on Linux and some UNIX variants, and may be emulated
249+
/// on other platforms by libraries such as Cygwin. The properties of the `fork()` system call
250+
/// requires the users to do some preparation before calling it.
251+
///
252+
/// - **Multi-threading**: If `fork()` is called when the process has multiple threads, it
253+
/// will only duplicate the current thread into the child process, and the child process can
254+
/// only call async-signal-safe functions, notably `exec()`. For VMs that that use
255+
/// multi-process concurrency, it is imperative that when calling `fork()`, only one thread may
256+
/// exist in the process.
257+
///
258+
/// - **File descriptors**: The child process inherits copies of the parent's set of open
259+
/// file descriptors. This may or may not be desired depending on use cases.
260+
///
261+
/// This function helps VMs that use `fork()` for multi-process concurrency. It instructs all
262+
/// GC threads to save their contexts and return from their entry-point functions. Currently,
263+
/// such threads only include GC workers, and the entry point is
264+
/// [`crate::memory_manager::start_worker`]. A subsequent call to `MMTK::after_fork()` will
265+
/// re-spawn the threads using their saved contexts. The VM must not allocate objects in the
266+
/// MMTk heap before calling `MMTK::after_fork()`.
267+
///
268+
/// TODO: Currently, the MMTk core does not keep any files open for a long time. In the
269+
/// future, this function and the `after_fork` function may be used for handling open file
270+
/// descriptors across invocations of `fork()`. One possible use case is logging GC activities
271+
/// and statistics to files, such as performing heap dumps across multiple GCs.
272+
///
273+
/// If a VM intends to execute another program by calling `fork()` and immediately calling
274+
/// `exec`, it may skip this function because the state of the MMTk instance will be irrelevant
275+
/// in that case.
276+
///
277+
/// # Caution!
278+
///
279+
/// This function sends an asynchronous message to GC threads and returns immediately, but it
280+
/// is only safe for the VM to call `fork()` after the underlying **native threads** of the GC
281+
/// threads have exited. After calling this function, the VM should wait for their underlying
282+
/// native threads to exit in VM-specific manner before calling `fork()`.
283+
pub fn prepare_to_fork(&'static self) {
284+
assert!(
285+
self.state.is_initialized(),
286+
"MMTk collection has not been initialized, yet (was initialize_collection() called before?)"
287+
);
288+
probe!(mmtk, prepare_to_fork);
289+
self.scheduler.stop_gc_threads_for_forking();
290+
}
291+
292+
/// Call this function after the VM called the `fork()` system call.
293+
///
294+
/// This function will re-spawn MMTk threads from saved contexts.
295+
///
296+
/// # Arguments
297+
///
298+
/// * `tls`: The thread that wants to respawn MMTk threads after forking. This value will be
299+
/// passed back to the VM in `Collection::spawn_gc_thread()` so that the VM knows the
300+
/// context.
301+
pub fn after_fork(&'static self, tls: VMThread) {
302+
assert!(
303+
self.state.is_initialized(),
304+
"MMTk collection has not been initialized, yet (was initialize_collection() called before?)"
305+
);
306+
probe!(mmtk, after_fork);
307+
self.scheduler.respawn_gc_threads_after_forking(tls);
308+
}
309+
223310
/// Generic hook to allow benchmarks to be harnessed. MMTk will trigger a GC
224311
/// to clear any residual garbage and start collecting statistics for the benchmark.
225312
/// This is usually called by the benchmark harness as its last step before the actual benchmark.
@@ -349,6 +436,8 @@ impl<VM: VMBinding> MMTK<VM> {
349436
self.state
350437
.internal_triggered_collection
351438
.store(true, Ordering::Relaxed);
439+
// TODO: The current `GCRequester::request()` is probably incorrect for internally triggered GC.
440+
// Consider removing functions related to "internal triggered collection".
352441
self.gc_requester.request();
353442
}
354443

src/plan/gc_requester.rs

+17-41
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,42 @@
1+
use crate::scheduler::GCWorkScheduler;
12
use crate::vm::VMBinding;
2-
use std::marker::PhantomData;
33
use std::sync::atomic::{AtomicBool, Ordering};
4-
use std::sync::{Condvar, Mutex};
4+
use std::sync::Arc;
55

6-
struct RequestSync {
7-
request_count: isize,
8-
last_request_count: isize,
9-
}
10-
11-
/// GC requester. This object allows other threads to request (trigger) GC,
12-
/// and the GC coordinator thread waits for GC requests using this object.
6+
/// This data structure lets mutators trigger GC.
137
pub struct GCRequester<VM: VMBinding> {
14-
request_sync: Mutex<RequestSync>,
15-
request_condvar: Condvar,
8+
/// Set by mutators to trigger GC. It is atomic so that mutators can check if GC has already
9+
/// been requested efficiently in `poll` without acquiring any mutex.
1610
request_flag: AtomicBool,
17-
phantom: PhantomData<VM>,
18-
}
19-
20-
// Clippy says we need this...
21-
impl<VM: VMBinding> Default for GCRequester<VM> {
22-
fn default() -> Self {
23-
Self::new()
24-
}
11+
scheduler: Arc<GCWorkScheduler<VM>>,
2512
}
2613

2714
impl<VM: VMBinding> GCRequester<VM> {
28-
pub fn new() -> Self {
15+
pub fn new(scheduler: Arc<GCWorkScheduler<VM>>) -> Self {
2916
GCRequester {
30-
request_sync: Mutex::new(RequestSync {
31-
request_count: 0,
32-
last_request_count: -1,
33-
}),
34-
request_condvar: Condvar::new(),
3517
request_flag: AtomicBool::new(false),
36-
phantom: PhantomData,
18+
scheduler,
3719
}
3820
}
3921

22+
/// Request a GC. Called by mutators when polling (during allocation) and when handling user
23+
/// GC requests (e.g. `System.gc();` in Java).
4024
pub fn request(&self) {
4125
if self.request_flag.load(Ordering::Relaxed) {
4226
return;
4327
}
4428

45-
let mut guard = self.request_sync.lock().unwrap();
46-
if !self.request_flag.load(Ordering::Relaxed) {
47-
self.request_flag.store(true, Ordering::Relaxed);
48-
guard.request_count += 1;
49-
self.request_condvar.notify_all();
29+
if !self.request_flag.swap(true, Ordering::Relaxed) {
30+
// `GCWorkScheduler::request_schedule_collection` needs to hold a mutex to communicate
31+
// with GC workers, which is expensive for functions like `poll`. We use the atomic
32+
// flag `request_flag` to elide the need to acquire the mutex in subsequent calls.
33+
self.scheduler.request_schedule_collection();
5034
}
5135
}
5236

37+
/// Clear the "GC requested" flag so that mutators can trigger the next GC.
38+
/// Called by a GC worker when all mutators have come to a stop.
5339
pub fn clear_request(&self) {
54-
let guard = self.request_sync.lock().unwrap();
5540
self.request_flag.store(false, Ordering::Relaxed);
56-
drop(guard);
57-
}
58-
59-
pub fn wait_for_request(&self) {
60-
let mut guard = self.request_sync.lock().unwrap();
61-
guard.last_request_count += 1;
62-
while guard.last_request_count == guard.request_count {
63-
guard = self.request_condvar.wait(guard).unwrap();
64-
}
6541
}
6642
}

0 commit comments

Comments
 (0)