1use self::worker::PollResult;
2
3use super::gc_work::ScheduleCollection;
4use super::stat::SchedulerStat;
5use super::work_bucket::*;
6use super::worker::{GCWorker, ThreadId, WorkerGroup};
7use super::worker_goals::{WorkerGoal, WorkerGoals};
8use super::worker_monitor::{LastParkedResult, WorkerMonitor};
9use super::*;
10use crate::mmtk::MMTK;
11use crate::plan::tracing::gc_work::weakref::{
12 VMForwardWeakRefs, VMPostForwarding, VMProcessWeakRefs,
13};
14use crate::util::opaque_pointer::*;
15use crate::util::options::AffinityKind;
16use crate::vm::Collection;
17use crate::vm::VMBinding;
18use crate::Plan;
19use crossbeam::deque::Steal;
20use enum_map::{Enum, EnumMap};
21use std::collections::HashMap;
22use std::sync::Arc;
23use std::time::Instant;
24
25pub struct GCWorkScheduler<VM: VMBinding> {
26 pub work_buckets: EnumMap<WorkBucketStage, WorkBucket<VM>>,
28 pub(crate) worker_group: Arc<WorkerGroup<VM>>,
30 pub(crate) worker_monitor: Arc<WorkerMonitor>,
32 affinity: AffinityKind,
34}
35
36unsafe impl<VM: VMBinding> Sync for GCWorkScheduler<VM> {}
40
41impl<VM: VMBinding> GCWorkScheduler<VM> {
42 pub fn new(num_workers: usize, affinity: AffinityKind) -> Arc<Self> {
43 let worker_monitor: Arc<WorkerMonitor> = Arc::new(WorkerMonitor::new(num_workers));
44 let worker_group = WorkerGroup::new(num_workers);
45
46 let mut work_buckets = EnumMap::from_fn(|stage: WorkBucketStage| {
48 WorkBucket::new(stage, worker_monitor.clone())
49 });
50
51 {
53 let mut open_stages: Vec<WorkBucketStage> = vec![WorkBucketStage::FIRST_STW_STAGE];
54 let stages = (0..WorkBucketStage::LENGTH).map(WorkBucketStage::from_usize);
55 for stage in stages {
56 if stage.is_sequentially_opened() {
57 let cur_stages = open_stages.clone();
58 work_buckets[stage].set_open_condition(
61 move |scheduler: &GCWorkScheduler<VM>| {
62 debug!(
63 "Check if {:?} can be opened? These needs to be drained: {:?}",
64 stage, cur_stages
65 );
66 scheduler.are_buckets_drained(&cur_stages)
67 },
68 );
69 open_stages.push(stage);
70 }
71 }
72 }
73
74 Arc::new(Self {
75 work_buckets,
76 worker_group,
77 worker_monitor,
78 affinity,
79 })
80 }
81
82 pub fn num_workers(&self) -> usize {
83 self.worker_group.as_ref().worker_count()
84 }
85
86 pub fn spawn_gc_threads(self: &Arc<Self>, mmtk: &'static MMTK<VM>, tls: VMThread) {
91 self.worker_group.initial_spawn(tls, mmtk);
92 }
93
94 pub fn stop_gc_threads_for_forking(self: &Arc<Self>) {
96 self.worker_group.prepare_surrender_buffer();
97
98 debug!("A mutator is requesting GC threads to stop for forking...");
99 self.worker_monitor.make_request(WorkerGoal::StopForFork);
100 }
101
102 pub fn shutdown_gc_threads(self: &Arc<Self>) {
104 self.worker_group.prepare_surrender_buffer();
105
106 info!("A mutator is requesting GC threads to shut down...");
107 self.worker_monitor.make_request(WorkerGoal::Shutdown);
108 }
109
110 pub fn surrender_gc_worker(&self, worker: Box<GCWorker<VM>>) {
112 let all_surrendered = self.worker_group.surrender_gc_worker(worker);
113
114 if all_surrendered {
115 debug!(
116 "All {} workers surrendered.",
117 self.worker_group.worker_count()
118 );
119 self.worker_monitor.on_all_workers_exited();
120 }
121 }
122
123 pub fn respawn_gc_threads_after_forking(self: &Arc<Self>, tls: VMThread) {
127 self.worker_group.respawn(tls)
128 }
129
130 pub fn resolve_affinity(&self, thread: ThreadId) {
132 self.affinity.resolve_affinity(thread);
133 }
134
135 pub(crate) fn request_schedule_collection(&self) {
137 debug!("A mutator is sending GC-scheduling request to workers...");
138 self.worker_monitor.make_request(WorkerGoal::Gc);
139 }
140
141 fn add_schedule_collection_packet(&self) {
143 probe!(mmtk, add_schedule_collection_packet);
145 self.work_buckets[WorkBucketStage::Unconstrained].add_no_notify(ScheduleCollection);
146 }
147
148 pub fn schedule_common_work<C: GCWorkContext<VM = VM>>(&self, plan: &'static C::PlanType) {
150 use crate::scheduler::gc_work::*;
151 self.work_buckets[WorkBucketStage::Unconstrained].add(StopMutators::<C>::new());
153
154 self.work_buckets[WorkBucketStage::Prepare].add(Prepare::<C>::new(plan));
156
157 self.work_buckets[WorkBucketStage::Release].add(Release::<C>::new(plan));
159
160 #[cfg(feature = "analysis")]
162 {
163 use crate::util::analysis::GcHookWork;
164 self.work_buckets[WorkBucketStage::Unconstrained].add(GcHookWork);
165 }
166
167 #[cfg(feature = "sanity")]
169 {
170 use crate::util::sanity::sanity_checker::ScheduleSanityGC;
171 self.work_buckets[WorkBucketStage::Final]
172 .add(ScheduleSanityGC::<C::PlanType>::new(plan));
173 }
174
175 if !*plan.base().options.no_reference_types {
177 use crate::util::reference_processor::{
178 PhantomRefProcessing, SoftRefProcessing, WeakRefProcessing,
179 };
180 self.work_buckets[WorkBucketStage::SoftRefClosure]
181 .add(SoftRefProcessing::<C::DefaultTrace>::new());
182 self.work_buckets[WorkBucketStage::WeakRefClosure].add(WeakRefProcessing::<VM>::new());
183 self.work_buckets[WorkBucketStage::PhantomRefClosure]
184 .add(PhantomRefProcessing::<VM>::new());
185
186 use crate::util::reference_processor::RefForwarding;
187 if plan.constraints().needs_forward_after_liveness {
188 self.work_buckets[WorkBucketStage::RefForwarding]
189 .add(RefForwarding::<C::DefaultTrace>::new());
190 }
191
192 use crate::util::reference_processor::RefEnqueue;
193 self.work_buckets[WorkBucketStage::Release].add(RefEnqueue::<VM>::new());
194 }
195
196 if !*plan.base().options.no_finalizer {
198 use crate::util::finalizable_processor::{Finalization, ForwardFinalization};
199 self.work_buckets[WorkBucketStage::FinalRefClosure]
201 .add(Finalization::<C::DefaultTrace>::new());
202 if plan.constraints().needs_forward_after_liveness {
204 self.work_buckets[WorkBucketStage::FinalizableForwarding]
205 .add(ForwardFinalization::<C::DefaultTrace>::new());
206 }
207 }
208
209 self.work_buckets[WorkBucketStage::VMRefClosure]
231 .set_sentinel(Box::new(VMProcessWeakRefs::<C::DefaultTrace>::new()));
232
233 if plan.constraints().needs_forward_after_liveness {
234 self.work_buckets[WorkBucketStage::VMRefForwarding]
236 .add(VMForwardWeakRefs::<C::DefaultTrace>::new());
237 }
238
239 self.work_buckets[WorkBucketStage::Release].add(VMPostForwarding::<VM>::default());
240 }
241
242 fn are_buckets_drained(&self, buckets: &[WorkBucketStage]) -> bool {
243 buckets
244 .iter()
245 .all(|&b| !self.work_buckets[b].is_enabled() || self.work_buckets[b].is_drained())
246 }
247
248 pub fn debug_assert_all_stw_buckets_empty(&self) {
249 debug_assert!(self
250 .work_buckets
251 .values()
252 .filter(|bucket| bucket.get_stage().is_stw())
253 .all(|bucket| {
254 if !bucket.is_empty() {
255 warn!(
256 "Work bucket {:?} is not empty but it is expected to be empty!",
257 bucket.get_stage()
258 );
259 warn!("Queue: {:?}", bucket.get_queue().debug_dump_packets());
260 false
261 } else {
262 true
263 }
264 }))
265 }
266
267 pub(crate) fn schedule_sentinels(&self) -> bool {
269 let mut new_packets = false;
270 for (id, work_bucket) in self.work_buckets.iter() {
271 if work_bucket.is_open() && work_bucket.maybe_schedule_sentinel() {
272 trace!("Scheduled sentinel packet into {:?}", id);
273 new_packets = true;
274 }
275 }
276 new_packets
277 }
278
279 pub(crate) fn update_buckets(&self) -> bool {
286 debug!("update_buckets");
287 let mut buckets_updated = false;
288 let mut new_packets = false;
289 for i in 0..WorkBucketStage::LENGTH {
290 let id = WorkBucketStage::from_usize(i);
291 if id.is_always_open() {
292 continue;
293 }
294 let bucket = &self.work_buckets[id];
295 if !bucket.is_enabled() {
296 debug!("Work bucket {:?} is disabled. Skip.", id);
297 continue;
298 }
299 debug!("Checking if {:?} can be opened...", id);
300 let bucket_opened = bucket.update(self);
301 buckets_updated = buckets_updated || bucket_opened;
302 if bucket_opened {
303 probe!(mmtk, bucket_opened, id);
304 new_packets = new_packets || !bucket.is_drained();
305 if new_packets {
306 trace!("Found new packets at stage {:?}. Break.", id);
308 break;
309 }
310 new_packets = new_packets || bucket.maybe_schedule_sentinel();
311 if new_packets {
312 trace!("Sentinel is scheduled at stage {:?}. Break.", id);
314 break;
315 }
316 }
317 }
318 buckets_updated && new_packets
319 }
320
321 pub fn close_all_stw_buckets(&self) {
322 self.work_buckets.iter().for_each(|(id, bkt)| {
323 if id.is_stw() {
324 bkt.close();
325 }
326 });
327 }
328
329 pub fn reset_state(&self) {
330 self.work_buckets.iter().for_each(|(id, bkt)| {
331 if id.is_stw() && !id.is_first_stw_stage() {
332 bkt.close();
333 }
334 });
335 }
336
337 pub fn debug_assert_all_stw_buckets_closed(&self) {
338 if cfg!(debug_assertions) {
339 self.work_buckets.iter().for_each(|(id, bkt)| {
340 if id.is_stw() {
341 assert!(!bkt.is_open());
342 }
343 });
344 }
345 }
346
347 pub(crate) fn assert_all_open_buckets_are_empty(&self) {
349 let mut error_example = None;
350 for (id, bucket) in self.work_buckets.iter() {
351 if bucket.is_enabled() && bucket.is_open() && !bucket.is_empty() {
352 error!("Work bucket {:?} is not drained!", id);
353 error!("Queue: {:?}", bucket.get_queue().debug_dump_packets());
354 error_example = Some(id);
359 }
360 }
361 if let Some(id) = error_example {
362 panic!("Some open buckets (such as {:?}) are not empty.", id);
363 }
364 }
365
366 fn poll_schedulable_work_once(&self, worker: &GCWorker<VM>) -> Steal<Box<dyn GCWork<VM>>> {
368 let mut should_retry = false;
369 if let Some(w) = worker.shared.designated_work.pop() {
371 return Steal::Success(w);
372 }
373 for work_bucket in self.work_buckets.values() {
375 match work_bucket.poll(&worker.local_work_buffer) {
376 Steal::Success(w) => return Steal::Success(w),
377 Steal::Retry => should_retry = true,
378 _ => {}
379 }
380 }
381 for (id, worker_shared) in self.worker_group.workers_shared.iter().enumerate() {
383 if id == worker.ordinal {
384 continue;
385 }
386 match worker_shared.stealer.as_ref().unwrap().steal() {
387 Steal::Success(w) => return Steal::Success(w),
388 Steal::Retry => should_retry = true,
389 _ => {}
390 }
391 }
392 if should_retry {
393 Steal::Retry
394 } else {
395 Steal::Empty
396 }
397 }
398
399 fn poll_schedulable_work(&self, worker: &GCWorker<VM>) -> Option<Box<dyn GCWork<VM>>> {
401 loop {
403 match self.poll_schedulable_work_once(worker) {
404 Steal::Success(w) => {
405 return Some(w);
406 }
407 Steal::Retry => {
408 std::thread::yield_now();
409 continue;
410 }
411 Steal::Empty => {
412 return None;
413 }
414 }
415 }
416 }
417
418 pub(crate) fn poll(&self, worker: &GCWorker<VM>) -> PollResult<VM> {
421 if let Some(work) = self.poll_schedulable_work(worker) {
422 return Ok(work);
423 }
424 self.poll_slow(worker)
425 }
426
427 fn poll_slow(&self, worker: &GCWorker<VM>) -> PollResult<VM> {
428 loop {
429 if let Some(work) = self.poll_schedulable_work(worker) {
431 return Ok(work);
432 }
433
434 let ordinal = worker.ordinal;
435 self.worker_monitor
436 .park_and_wait(ordinal, |goals| self.on_last_parked(worker, goals))?;
437 }
438 }
439
440 fn on_last_parked(&self, worker: &GCWorker<VM>, goals: &mut WorkerGoals) -> LastParkedResult {
443 let Some(ref current_goal) = goals.current() else {
444 return self.respond_to_requests(worker, goals);
446 };
447
448 match current_goal {
449 WorkerGoal::Gc => {
450 assert!(
455 !goals.debug_is_requested(WorkerGoal::Gc),
456 "GC request sent to WorkerMonitor while GC is still in progress."
457 );
458
459 trace!("The last worker parked during GC. Try to find more work to do...");
461
462 self.assert_all_open_buckets_are_empty();
464
465 let found_more_work = self.find_more_work_for_workers();
467
468 if found_more_work {
469 LastParkedResult::WakeAll
470 } else {
471 let concurrent_work_scheduled = self.on_gc_finished(worker);
473
474 goals.on_current_goal_completed();
476
477 if concurrent_work_scheduled {
478 LastParkedResult::WakeAll
481 } else {
482 self.respond_to_requests(worker, goals)
485 }
486 }
487 }
488 WorkerGoal::StopForFork | WorkerGoal::Shutdown => {
489 panic!(
490 "Worker {} parked again when it is asked to exit.",
491 worker.ordinal
492 )
493 }
494 }
495 }
496
497 fn respond_to_requests(
499 &self,
500 worker: &GCWorker<VM>,
501 goals: &mut WorkerGoals,
502 ) -> LastParkedResult {
503 assert!(goals.current().is_none());
504
505 let Some(goal) = goals.poll_next_goal() else {
506 return LastParkedResult::ParkSelf;
508 };
509
510 match goal {
511 WorkerGoal::Gc => {
512 trace!("A mutator requested a GC to be scheduled.");
513
514 probe!(mmtk, gc_start);
517
518 {
519 let mut gc_start_time = worker.mmtk.state.gc_start_time.borrow_mut();
520 assert!(gc_start_time.is_none(), "GC already started?");
521 *gc_start_time = Some(Instant::now());
522 }
523
524 self.add_schedule_collection_packet();
525 LastParkedResult::WakeSelf
526 }
527 WorkerGoal::StopForFork | WorkerGoal::Shutdown => {
528 trace!("A mutator requested {:?}", goal);
529 LastParkedResult::WakeAll
530 }
531 }
532 }
533
534 fn find_more_work_for_workers(&self) -> bool {
536 if self.worker_group.has_designated_work() {
537 trace!("Some workers have designated work.");
538 return true;
539 }
540
541 if self.schedule_sentinels() {
543 trace!("Some sentinels are scheduled.");
544 return true;
545 }
546
547 if self.update_buckets() {
549 trace!("Some buckets are opened.");
550 return true;
551 }
552
553 false
555 }
556
557 fn on_gc_finished(&self, worker: &GCWorker<VM>) -> bool {
561 debug_assert!(!self.worker_group.has_designated_work());
563 self.debug_assert_all_stw_buckets_empty();
564
565 self.close_all_stw_buckets();
567 self.debug_assert_all_stw_buckets_closed();
568
569 let mmtk = worker.mmtk;
570
571 mmtk.gc_trigger.policy.on_gc_end(mmtk);
573
574 probe!(mmtk, plan_end_of_gc_begin);
576 let plan_mut: &mut dyn Plan<VM = VM> = unsafe { mmtk.get_plan_mut() };
577 plan_mut.end_of_gc(worker.tls);
578 probe!(mmtk, plan_end_of_gc_end);
579
580 let start_time = {
582 let mut gc_start_time = worker.mmtk.state.gc_start_time.borrow_mut();
583 gc_start_time.take().expect("GC not started yet?")
584 };
585 let elapsed = start_time.elapsed();
586
587 info!(
588 "End of GC ({}/{} pages, took {} ms)",
589 mmtk.get_plan().get_reserved_pages(),
590 mmtk.get_plan().get_total_pages(),
591 elapsed.as_millis()
592 );
593
594 probe!(mmtk, gc_end);
596
597 if *mmtk.get_options().count_live_bytes_in_gc {
598 let live_bytes = mmtk
600 .scheduler
601 .worker_group
602 .get_and_clear_worker_live_bytes();
603 let mut live_bytes_in_last_gc = mmtk.state.live_bytes_in_last_gc.borrow_mut();
604 *live_bytes_in_last_gc = mmtk.aggregate_live_bytes_in_last_gc(live_bytes);
605 for (space_name, &stats) in live_bytes_in_last_gc.iter() {
607 info!(
608 "{} = {} pages ({:.1}% live)",
609 space_name,
610 stats.used_pages,
611 stats.live_bytes as f64 * 100.0 / stats.used_bytes as f64,
612 );
613 }
614 }
615
616 mmtk.state
617 .set_used_pages_after_last_gc(mmtk.get_plan().get_used_pages());
618
619 #[cfg(feature = "extreme_assertions")]
620 if crate::util::slot_logger::should_check_duplicate_slots(mmtk.get_plan()) {
621 mmtk.slot_logger.reset();
623 }
624
625 mmtk.state.reset_collection_trigger();
627
628 let concurrent_work_scheduled = self.schedule_concurrent_packets();
629 self.debug_assert_all_stw_buckets_closed();
630
631 if concurrent_work_scheduled {
633 mmtk.state.gc_status.set_in_concurrent_gc();
634 } else {
635 mmtk.state.gc_status.set_not_in_gc();
636 }
637 if mmtk.stats.get_gathering_stats() {
638 mmtk.stats.end_gc();
639 }
640 <VM as VMBinding>::VMCollection::resume_mutators(worker.tls);
641
642 concurrent_work_scheduled
643 }
644
645 pub fn enable_stat(&self) {
646 for worker in &self.worker_group.workers_shared {
647 let worker_stat = worker.borrow_stat();
648 worker_stat.enable();
649 }
650 }
651
652 pub fn statistics(&self) -> HashMap<String, String> {
653 let mut summary = SchedulerStat::default();
654 for worker in &self.worker_group.workers_shared {
655 let worker_stat = worker.borrow_stat();
656 summary.merge(&worker_stat);
657 }
658 summary.harness_stat()
659 }
660
661 pub fn notify_mutators_paused(&self, mmtk: &'static MMTK<VM>) {
662 mmtk.state.gc_status.set_in_pause();
663 let first_stw_bucket = &self.work_buckets[WorkBucketStage::FIRST_STW_STAGE];
664 debug_assert!(!first_stw_bucket.is_open());
665 first_stw_bucket.open();
673 self.worker_monitor.notify_work_available(true);
674 }
675
676 pub(super) fn schedule_concurrent_packets(&self) -> bool {
677 let concurrent_bucket = &self.work_buckets[WorkBucketStage::Concurrent];
678 if !concurrent_bucket.is_empty() {
679 concurrent_bucket.set_enabled(true);
680 concurrent_bucket.open();
681 true
682 } else {
683 concurrent_bucket.set_enabled(false);
684 concurrent_bucket.close();
685 false
686 }
687 }
688}