Skip to content

Commit c6317a3

Browse files
authored
Remove GCRequester, move related code to GCTrigger (#1405)
This PR moves GC triggering related code to `GCTrigger`, including: * The old `GCRequester` which handles the request for GCs. * The check for `is_collection_enabled` in `memory_manager` and `Space::acquire`. * Code for user collection request and internal collection request in `MMTK`.
1 parent 878098e commit c6317a3

File tree

9 files changed

+105
-98
lines changed

9 files changed

+105
-98
lines changed

src/memory_manager.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ pub fn gc_poll<VM: VMBinding>(mmtk: &MMTK<VM>, tls: VMMutatorThread) {
543543
"gc_poll() can only be called by a mutator thread."
544544
);
545545

546-
if VM::VMCollection::is_collection_enabled() && mmtk.gc_trigger.poll(false, None) {
546+
if mmtk.gc_trigger.poll(false, None) {
547547
debug!("Collection required");
548548
if !mmtk.state.is_initialized() {
549549
panic!("GC is not allowed here: collection is not initialized (did you call initialize_collection()?).");

src/mmtk.rs

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! MMTk instance.
22
use crate::global_state::{GcStatus, GlobalState};
3-
use crate::plan::gc_requester::GCRequester;
43
use crate::plan::CreateGeneralPlanArgs;
54
use crate::plan::Plan;
65
use crate::policy::sft_map::{create_sft_map, SFTMap};
@@ -123,7 +122,6 @@ pub struct MMTK<VM: VMBinding> {
123122
#[cfg(feature = "extreme_assertions")]
124123
pub(crate) slot_logger: SlotLogger<VM::VMSlot>,
125124
pub(crate) gc_trigger: Arc<GCTrigger<VM>>,
126-
pub(crate) gc_requester: Arc<GCRequester<VM>>,
127125
pub(crate) stats: Arc<Stats>,
128126
#[cfg(feature = "sanity")]
129127
inside_sanity: AtomicBool,
@@ -156,11 +154,9 @@ impl<VM: VMBinding> MMTK<VM> {
156154

157155
let state = Arc::new(GlobalState::default());
158156

159-
let gc_requester = Arc::new(GCRequester::new(scheduler.clone()));
160-
161157
let gc_trigger = Arc::new(GCTrigger::new(
162158
options.clone(),
163-
gc_requester.clone(),
159+
scheduler.clone(),
164160
state.clone(),
165161
));
166162

@@ -229,7 +225,6 @@ impl<VM: VMBinding> MMTK<VM> {
229225
#[cfg(feature = "analysis")]
230226
analysis_manager: Arc::new(AnalysisManager::new(stats.clone())),
231227
gc_trigger,
232-
gc_requester,
233228
stats,
234229
}
235230
}
@@ -422,44 +417,22 @@ impl<VM: VMBinding> MMTK<VM> {
422417
force: bool,
423418
exhaustive: bool,
424419
) -> bool {
425-
use crate::vm::Collection;
426-
if !self.get_plan().constraints().collects_garbage {
427-
warn!("User attempted a collection request, but the plan can not do GC. The request is ignored.");
428-
return false;
429-
}
430-
431-
if force || !*self.options.ignore_system_gc && VM::VMCollection::is_collection_enabled() {
432-
info!("User triggering collection");
433-
if exhaustive {
434-
if let Some(gen) = self.get_plan().generational() {
435-
gen.force_full_heap_collection();
436-
}
437-
}
438-
439-
self.state
440-
.user_triggered_collection
441-
.store(true, Ordering::Relaxed);
442-
self.gc_requester.request();
420+
if self
421+
.gc_trigger
422+
.handle_user_collection_request(force, exhaustive)
423+
{
424+
use crate::vm::Collection;
443425
VM::VMCollection::block_for_gc(tls);
444-
return true;
426+
true
427+
} else {
428+
false
445429
}
446-
447-
false
448430
}
449431

450432
/// MMTK has requested stop-the-world activity (e.g., stw within a concurrent gc).
451-
// This is not used, as we do not have a concurrent plan.
452433
#[allow(unused)]
453434
pub fn trigger_internal_collection_request(&self) {
454-
self.state
455-
.last_internal_triggered_collection
456-
.store(true, Ordering::Relaxed);
457-
self.state
458-
.internal_triggered_collection
459-
.store(true, Ordering::Relaxed);
460-
// TODO: The current `GCRequester::request()` is probably incorrect for internally triggered GC.
461-
// Consider removing functions related to "internal triggered collection".
462-
self.gc_requester.request();
435+
self.gc_trigger.trigger_internal_collection_request();
463436
}
464437

465438
/// Get a reference to the plan.

src/plan/gc_requester.rs

Lines changed: 0 additions & 43 deletions
This file was deleted.

src/plan/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
mod barriers;
1717
pub use barriers::BarrierSelector;
1818

19-
pub(crate) mod gc_requester;
20-
2119
mod gc_work;
2220

2321
mod global;

src/policy/space.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,7 @@ pub trait Space<VM: VMBinding>: 'static + SFT + Sync + Downcast {
115115

116116
// Should we poll before acquring pages from page resources so that it can trigger a GC?
117117
// - If tls is collector, we cannot attempt a GC.
118-
// - If gc is disabled, we cannot attempt a GC.
119-
let should_poll =
120-
VM::VMActivePlan::is_mutator(tls) && VM::VMCollection::is_collection_enabled();
118+
let should_poll = VM::VMActivePlan::is_mutator(tls);
121119

122120
// If we should poll, do it now. Record if it has triggered a GC.
123121
// If we should not poll, GC is not triggered.

src/scheduler/scheduler.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl<VM: VMBinding> GCWorkScheduler<VM> {
122122
self.affinity.resolve_affinity(thread);
123123
}
124124

125-
/// Request a GC to be scheduled. Called by mutator via `GCRequester`.
125+
/// Request a GC to be scheduled. Called by mutator via `GCTrigger`.
126126
pub(crate) fn request_schedule_collection(&self) {
127127
debug!("A mutator is sending GC-scheduling request to workers...");
128128
self.worker_monitor.make_request(WorkerGoal::Gc);
@@ -642,7 +642,7 @@ impl<VM: VMBinding> GCWorkScheduler<VM> {
642642
}
643643

644644
pub fn notify_mutators_paused(&self, mmtk: &'static MMTK<VM>) {
645-
mmtk.gc_requester.clear_request();
645+
mmtk.gc_trigger.clear_request();
646646
let first_stw_bucket = &self.work_buckets[WorkBucketStage::FIRST_STW_STAGE];
647647
debug_assert!(!first_stw_bucket.is_open());
648648
// Note: This is the only place where a bucket is opened without having all workers parked.

src/util/heap/gc_trigger.rs

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
use atomic::Ordering;
22

33
use crate::global_state::GlobalState;
4-
use crate::plan::gc_requester::GCRequester;
54
use crate::plan::Plan;
65
use crate::policy::space::Space;
6+
use crate::scheduler::GCWorkScheduler;
77
use crate::util::constants::BYTES_IN_PAGE;
88
use crate::util::conversions;
99
use crate::util::options::{GCTriggerSelector, Options, DEFAULT_MAX_NURSERY, DEFAULT_MIN_NURSERY};
10+
use crate::vm::Collection;
1011
use crate::vm::VMBinding;
1112
use crate::MMTK;
1213
use std::mem::MaybeUninit;
13-
use std::sync::atomic::AtomicUsize;
14+
use std::sync::atomic::{AtomicBool, AtomicUsize};
1415
use std::sync::Arc;
1516

1617
/// GCTrigger is responsible for triggering GCs based on the given policy.
@@ -23,15 +24,18 @@ pub struct GCTrigger<VM: VMBinding> {
2324
plan: MaybeUninit<&'static dyn Plan<VM = VM>>,
2425
/// The triggering policy.
2526
pub policy: Box<dyn GCTriggerPolicy<VM>>,
26-
gc_requester: Arc<GCRequester<VM>>,
27+
/// Set by mutators to trigger GC. It is atomic so that mutators can check if GC has already
28+
/// been requested efficiently in `poll` without acquiring any mutex.
29+
request_flag: AtomicBool,
30+
scheduler: Arc<GCWorkScheduler<VM>>,
2731
options: Arc<Options>,
2832
state: Arc<GlobalState>,
2933
}
3034

3135
impl<VM: VMBinding> GCTrigger<VM> {
3236
pub fn new(
3337
options: Arc<Options>,
34-
gc_requester: Arc<GCRequester<VM>>,
38+
scheduler: Arc<GCWorkScheduler<VM>>,
3539
state: Arc<GlobalState>,
3640
) -> Self {
3741
GCTrigger {
@@ -58,7 +62,8 @@ impl<VM: VMBinding> GCTrigger<VM> {
5862
}
5963
},
6064
options,
61-
gc_requester,
65+
request_flag: AtomicBool::new(false),
66+
scheduler,
6267
state,
6368
}
6469
}
@@ -72,6 +77,28 @@ impl<VM: VMBinding> GCTrigger<VM> {
7277
unsafe { self.plan.assume_init() }
7378
}
7479

80+
/// Request a GC. Called by mutators when polling (during allocation) and when handling user
81+
/// GC requests (e.g. `System.gc();` in Java).
82+
fn request(&self) {
83+
if self.request_flag.load(Ordering::Relaxed) {
84+
return;
85+
}
86+
87+
if !self.request_flag.swap(true, Ordering::Relaxed) {
88+
// `GCWorkScheduler::request_schedule_collection` needs to hold a mutex to communicate
89+
// with GC workers, which is expensive for functions like `poll`. We use the atomic
90+
// flag `request_flag` to elide the need to acquire the mutex in subsequent calls.
91+
probe!(mmtk, gc_requested);
92+
self.scheduler.request_schedule_collection();
93+
}
94+
}
95+
96+
/// Clear the "GC requested" flag so that mutators can trigger the next GC.
97+
/// Called by a GC worker when all mutators have come to a stop.
98+
pub fn clear_request(&self) {
99+
self.request_flag.store(false, Ordering::Relaxed);
100+
}
101+
75102
/// This method is called periodically by the allocation subsystem
76103
/// (by default, each time a page is consumed), and provides the
77104
/// collector with an opportunity to collect.
@@ -80,7 +107,11 @@ impl<VM: VMBinding> GCTrigger<VM> {
80107
/// * `space_full`: Space request failed, must recover pages within 'space'.
81108
/// * `space`: The space that triggered the poll. This could `None` if the poll is not triggered by a space.
82109
pub fn poll(&self, space_full: bool, space: Option<&dyn Space<VM>>) -> bool {
83-
let plan = unsafe { self.plan.assume_init() };
110+
if !VM::VMCollection::is_collection_enabled() {
111+
return false;
112+
}
113+
114+
let plan = self.plan();
84115
if self
85116
.policy
86117
.is_gc_required(space_full, space.map(|s| SpaceStats::new(s)), plan)
@@ -96,12 +127,62 @@ impl<VM: VMBinding> GCTrigger<VM> {
96127
plan.get_reserved_pages(),
97128
plan.get_total_pages(),
98129
);
99-
self.gc_requester.request();
130+
self.request();
100131
return true;
101132
}
102133
false
103134
}
104135

136+
/// This method is called when the user manually requests a collection, such as `System.gc()` in Java.
137+
/// Returns true if a collection is actually requested.
138+
///
139+
/// # Arguments
140+
/// * `force`: If true, we force a collection regardless of the settings. If false, we only trigger a collection if the settings allow it.
141+
/// * `exhaustive`: If true, we try to make the collection exhaustive (e.g. full heap collection). If false, the collection kind is determined internally.
142+
pub fn handle_user_collection_request(&self, force: bool, exhaustive: bool) -> bool {
143+
if !self.plan().constraints().collects_garbage {
144+
warn!("User attempted a collection request, but the plan can not do GC. The request is ignored.");
145+
return false;
146+
}
147+
148+
if force || !*self.options.ignore_system_gc && VM::VMCollection::is_collection_enabled() {
149+
info!("User triggering collection");
150+
// TODO: this may not work reliably. If a GC has been triggered, this will not force it to be a full heap GC.
151+
if exhaustive {
152+
if let Some(gen) = self.plan().generational() {
153+
gen.force_full_heap_collection();
154+
}
155+
}
156+
157+
self.state
158+
.user_triggered_collection
159+
.store(true, Ordering::Relaxed);
160+
self.request();
161+
return true;
162+
}
163+
164+
false
165+
}
166+
167+
/// MMTK has requested stop-the-world activity (e.g., stw within a concurrent gc).
168+
// TODO: We should use this for concurrent GC. E.g. in concurrent Immix, when the initial mark is done, we
169+
// can use this function to immediately trigger the final mark pause. The current implementation uses
170+
// normal collection_required check, which may delay the final mark unnecessarily.
171+
#[allow(unused)]
172+
pub fn trigger_internal_collection_request(&self) {
173+
self.state
174+
.last_internal_triggered_collection
175+
.store(true, Ordering::Relaxed);
176+
self.state
177+
.internal_triggered_collection
178+
.store(true, Ordering::Relaxed);
179+
// TODO: The current `request()` is probably incorrect for internally triggered GC.
180+
// Consider removing functions related to "internal triggered collection".
181+
self.request();
182+
// TODO: Make sure this function works correctly for concurrent GC.
183+
unimplemented!()
184+
}
185+
105186
pub fn should_do_stress_gc(&self) -> bool {
106187
Self::should_do_stress_gc_inner(&self.state, &self.options)
107188
}

tools/tracing/timeline/capture.bt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ usdt:$MMTK:mmtk:concurrent_trace_objects {
138138
}
139139
}
140140

141-
usdt:$MMTK:mmtk:gcrequester_request {
142-
printf("gcrequester_request,i,%d,%lu\n", tid, nsecs);
141+
usdt:$MMTK:mmtk:gc_requested {
142+
printf("gc_requested,i,%d,%lu\n", tid, nsecs);
143143
}
144144

145145
usdt:$MMTK:mmtk:add_schedule_collection_packet {

tools/tracing/timeline/visualize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def enrich_event(self, name, ph, tid, ts, result, args):
161161
"stage": int(args[0]),
162162
}
163163

164-
case "gcrequester_request":
164+
case "gc_requested":
165165
result["tid"] = 1
166166

167167
case _:

0 commit comments

Comments
 (0)