mmtk/scheduler/
scheduler.rs

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    /// Work buckets
27    pub work_buckets: EnumMap<WorkBucketStage, WorkBucket<VM>>,
28    /// Workers
29    pub(crate) worker_group: Arc<WorkerGroup<VM>>,
30    /// For synchronized communication between workers and with mutators.
31    pub(crate) worker_monitor: Arc<WorkerMonitor>,
32    /// How to assign the affinity of each GC thread. Specified by the user.
33    affinity: AffinityKind,
34}
35
36// FIXME: GCWorkScheduler should be naturally Sync, but we cannot remove this `impl` yet.
37// Some subtle interaction between ObjectRememberingBarrier, Mutator and some GCWork instances
38// makes the compiler think WorkBucket is not Sync.
39unsafe 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        // Create work buckets for workers.
47        let mut work_buckets = EnumMap::from_fn(|stage: WorkBucketStage| {
48            WorkBucket::new(stage, worker_monitor.clone())
49        });
50
51        // Set the open condition of each bucket.
52        {
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                    // Other work packets will be opened after previous stages are done
59                    // (i.e their buckets are drained and all workers parked).
60                    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    /// Create GC threads for the first time.  It will also create the `GCWorker` instances.
87    ///
88    /// Currently GC threads only include worker threads, and we currently have only one worker
89    /// group.  We may add more worker groups in the future.
90    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    /// Ask all GC workers to exit for forking.
95    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    /// Ask all GC workers to exit permanently.
103    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    /// Surrender the `GCWorker` struct of a GC worker when it exits.
111    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    /// Respawn GC threads after forking.  This will reuse the `GCWorker` instances of stopped
124    /// workers.  `tls` is the VM thread that requests GC threads to be re-spawn, and will be
125    /// passed down to [`crate::vm::Collection::spawn_gc_thread`].
126    pub fn respawn_gc_threads_after_forking(self: &Arc<Self>, tls: VMThread) {
127        self.worker_group.respawn(tls)
128    }
129
130    /// Resolve the affinity of a thread.
131    pub fn resolve_affinity(&self, thread: ThreadId) {
132        self.affinity.resolve_affinity(thread);
133    }
134
135    /// Request a GC to be scheduled.  Called by mutator via `GCTrigger`.
136    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    /// Add the `ScheduleCollection` packet.  Called by the last parked worker.
142    fn add_schedule_collection_packet(&self) {
143        // We are still holding the mutex `WorkerMonitor::sync`.  Do not notify now.
144        probe!(mmtk, add_schedule_collection_packet);
145        self.work_buckets[WorkBucketStage::Unconstrained].add_no_notify(ScheduleCollection);
146    }
147
148    /// Schedule all the common work packets
149    pub fn schedule_common_work<C: GCWorkContext<VM = VM>>(&self, plan: &'static C::PlanType) {
150        use crate::scheduler::gc_work::*;
151        // Stop & scan mutators (mutator scanning can happen before STW)
152        self.work_buckets[WorkBucketStage::Unconstrained].add(StopMutators::<C>::new());
153
154        // Prepare global/collectors/mutators
155        self.work_buckets[WorkBucketStage::Prepare].add(Prepare::<C>::new(plan));
156
157        // Release global/collectors/mutators
158        self.work_buckets[WorkBucketStage::Release].add(Release::<C>::new(plan));
159
160        // Analysis GC work
161        #[cfg(feature = "analysis")]
162        {
163            use crate::util::analysis::GcHookWork;
164            self.work_buckets[WorkBucketStage::Unconstrained].add(GcHookWork);
165        }
166
167        // Sanity
168        #[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        // Reference processing
176        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        // Finalization
197        if !*plan.base().options.no_finalizer {
198            use crate::util::finalizable_processor::{Finalization, ForwardFinalization};
199            // finalization
200            self.work_buckets[WorkBucketStage::FinalRefClosure]
201                .add(Finalization::<C::DefaultTrace>::new());
202            // forward refs
203            if plan.constraints().needs_forward_after_liveness {
204                self.work_buckets[WorkBucketStage::FinalizableForwarding]
205                    .add(ForwardFinalization::<C::DefaultTrace>::new());
206            }
207        }
208
209        // We add the VM-specific weak ref processing work regardless of MMTK-side options,
210        // including Options::no_finalizer and Options::no_reference_types.
211        //
212        // VMs need weak reference handling to function properly.  The VM may treat weak references
213        // as strong references, but it is not appropriate to simply disable weak reference
214        // handling from MMTk's side.  The VM, however, may choose to do nothing in
215        // `Collection::process_weak_refs` if appropriate.
216        //
217        // It is also not sound for MMTk core to turn off weak
218        // reference processing or finalization alone, because (1) not all VMs have the notion of
219        // weak references or finalizers, so it may not make sence, and (2) the VM may
220        // processing them together.
221
222        // VM-specific weak ref processing
223        // The `VMProcessWeakRefs` work packet is set as the sentinel so that it is executed when
224        // the `VMRefClosure` bucket is drained.  The VM binding may spawn new work packets into
225        // the `VMRefClosure` bucket, and request another `VMProcessWeakRefs` work packet to be
226        // executed again after this bucket is drained again.  Strictly speaking, the first
227        // `VMProcessWeakRefs` packet can be an ordinary packet (doesn't have to be a sentinel)
228        // because there are no other packets in the bucket.  We set it as sentinel for
229        // consistency.
230        self.work_buckets[WorkBucketStage::VMRefClosure]
231            .set_sentinel(Box::new(VMProcessWeakRefs::<C::DefaultTrace>::new()));
232
233        if plan.constraints().needs_forward_after_liveness {
234            // VM-specific weak ref forwarding
235            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    /// Schedule "sentinel" work packets for all open buckets.
268    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    /// Open buckets if their conditions are met.
280    ///
281    /// This function should only be called after all the workers are parked.
282    /// No workers will be waked up by this function. The caller is responsible for that.
283    ///
284    /// Return true if there're any non-empty buckets updated.
285    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                    // Quit the loop. There are already new packets in the newly opened buckets.
307                    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                    // Quit the loop. A sentinel packet is added to the newly opened buckets.
313                    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    /// Check if all the work buckets are empty
348    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                // This error can be hard to reproduce.
355                // If an error happens in the release build where logs are turned off,
356                // we should show at least one abnormal bucket in the panic message
357                // so that we still have some information for debugging.
358                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    /// Get a schedulable work packet without retry.
367    fn poll_schedulable_work_once(&self, worker: &GCWorker<VM>) -> Steal<Box<dyn GCWork<VM>>> {
368        let mut should_retry = false;
369        // Try find a packet that can be processed only by this worker.
370        if let Some(w) = worker.shared.designated_work.pop() {
371            return Steal::Success(w);
372        }
373        // Try get a packet from a work bucket.
374        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        // Try steal some packets from any worker
382        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    /// Get a schedulable work packet.
400    fn poll_schedulable_work(&self, worker: &GCWorker<VM>) -> Option<Box<dyn GCWork<VM>>> {
401        // Loop until we successfully get a packet.
402        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    /// Called by workers to get a schedulable work packet.
419    /// Park the worker if there're no available packets.
420    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            // Retry polling
430            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    /// Called when the last worker parked.  `goal` allows this function to inspect and change the
441    /// current goal.
442    fn on_last_parked(&self, worker: &GCWorker<VM>, goals: &mut WorkerGoals) -> LastParkedResult {
443        let Some(ref current_goal) = goals.current() else {
444            // There is no goal.  Find a request to respond to.
445            return self.respond_to_requests(worker, goals);
446        };
447
448        match current_goal {
449            WorkerGoal::Gc => {
450                // We are in the progress of GC.
451
452                // In stop-the-world GC, mutators cannot request for GC while GC is in progress.
453                // When we support concurrent GC, we should remove this assertion.
454                assert!(
455                    !goals.debug_is_requested(WorkerGoal::Gc),
456                    "GC request sent to WorkerMonitor while GC is still in progress."
457                );
458
459                // We are in the middle of GC, and the last GC worker parked.
460                trace!("The last worker parked during GC.  Try to find more work to do...");
461
462                // During GC, if all workers parked, all open buckets must have been drained.
463                self.assert_all_open_buckets_are_empty();
464
465                // Find more work for workers to do.
466                let found_more_work = self.find_more_work_for_workers();
467
468                if found_more_work {
469                    LastParkedResult::WakeAll
470                } else {
471                    // GC finished.
472                    let concurrent_work_scheduled = self.on_gc_finished(worker);
473
474                    // Clear the current goal
475                    goals.on_current_goal_completed();
476
477                    if concurrent_work_scheduled {
478                        // It was the initial mark pause and scheduled concurrent work.
479                        // Wake up all GC workers to do concurrent work.
480                        LastParkedResult::WakeAll
481                    } else {
482                        // It was an STW GC or the final mark pause of a concurrent GC.
483                        // Respond to another goal.
484                        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    /// Respond to a worker reqeust.
498    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            // No requests.  Park this worker, too.
507            return LastParkedResult::ParkSelf;
508        };
509
510        match goal {
511            WorkerGoal::Gc => {
512                trace!("A mutator requested a GC to be scheduled.");
513
514                // We set the eBPF trace point here so that bpftrace scripts can start recording
515                // work packet events before the `ScheduleCollection` work packet starts.
516                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    /// Find more work for workers to do.  Return true if more work is available.
535    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        // See if any bucket has a sentinel.
542        if self.schedule_sentinels() {
543            trace!("Some sentinels are scheduled.");
544            return true;
545        }
546
547        // Try to open new buckets.
548        if self.update_buckets() {
549            trace!("Some buckets are opened.");
550            return true;
551        }
552
553        // If all of the above failed, it means GC has finished.
554        false
555    }
556
557    /// Called when GC has finished, i.e. when all work packets have been executed.
558    ///
559    /// Return `true` if any concurrent work packets have been scheduled.
560    fn on_gc_finished(&self, worker: &GCWorker<VM>) -> bool {
561        // All GC workers must have parked by now.
562        debug_assert!(!self.worker_group.has_designated_work());
563        self.debug_assert_all_stw_buckets_empty();
564
565        // Close all work buckets to prepare for the next GC.
566        self.close_all_stw_buckets();
567        self.debug_assert_all_stw_buckets_closed();
568
569        let mmtk = worker.mmtk;
570
571        // Tell GC trigger that GC ended - this happens before we resume mutators.
572        mmtk.gc_trigger.policy.on_gc_end(mmtk);
573
574        // All other workers are parked, so it is safe to access the Plan instance mutably.
575        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        // Compute the elapsed time of the GC.
581        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        // USDT tracepoint for the end of GC.
595        probe!(mmtk, gc_end);
596
597        if *mmtk.get_options().count_live_bytes_in_gc {
598            // Aggregate the live bytes
599            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            // Logging
606            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            // reset the logging info at the end of each GC
622            mmtk.slot_logger.reset();
623        }
624
625        // Reset the triggering information.
626        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        // Set to NotInGC after everything, and right before resuming mutators.
632        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        // Note: This is the only place where a bucket is opened without having all workers parked.
666        // We usually require all workers to park before opening new buckets because otherwise
667        // packets will be executed out of order.  However, since `Prepare` is the first STW
668        // bucket, and all subsequent buckets require all workers to park before opening, workers
669        // cannot execute work packets out of order.  This is not generally true if we are not
670        // opening the first STW bucket.  In the future, we should redesign the opening condition
671        // of work buckets to make the synchronization more robust,
672        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}