mmtk/scheduler/
work_bucket.rs

1use super::worker_monitor::WorkerMonitor;
2use super::*;
3use crate::vm::VMBinding;
4use crossbeam::deque::{Injector, Steal, Worker};
5use enum_map::Enum;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::{Arc, Mutex};
8
9pub(super) struct BucketQueue<VM: VMBinding> {
10    flag: AtomicBool,
11    queue0: Injector<Box<dyn GCWork<VM>>>,
12    queue1: Injector<Box<dyn GCWork<VM>>>,
13}
14
15impl<VM: VMBinding> BucketQueue<VM> {
16    fn new() -> Self {
17        Self {
18            flag: AtomicBool::new(false),
19            queue0: Injector::new(),
20            queue1: Injector::new(),
21        }
22    }
23
24    fn active_queue(&self) -> &Injector<Box<dyn GCWork<VM>>> {
25        if self.flag.load(Ordering::Relaxed) {
26            &self.queue1
27        } else {
28            &self.queue0
29        }
30    }
31
32    fn inactive_queue(&self) -> &Injector<Box<dyn GCWork<VM>>> {
33        if self.flag.load(Ordering::Relaxed) {
34            &self.queue0
35        } else {
36            &self.queue1
37        }
38    }
39
40    fn is_empty(&self) -> bool {
41        self.active_queue().is_empty()
42    }
43
44    pub(super) fn steal(&self) -> Steal<Box<dyn GCWork<VM>>> {
45        self.active_queue().steal()
46    }
47
48    fn steal_batch_and_pop(
49        &self,
50        dest: &Worker<Box<dyn GCWork<VM>>>,
51    ) -> Steal<Box<dyn GCWork<VM>>> {
52        self.active_queue().steal_batch_and_pop(dest)
53    }
54
55    fn push(&self, w: Box<dyn GCWork<VM>>) {
56        self.active_queue().push(w);
57    }
58
59    fn push_all(&self, ws: Vec<Box<dyn GCWork<VM>>>) {
60        for w in ws {
61            self.active_queue().push(w);
62        }
63    }
64
65    fn push_inactive(&self, w: Box<dyn GCWork<VM>>) {
66        self.inactive_queue().push(w);
67    }
68
69    fn push_all_inactive(&self, ws: Vec<Box<dyn GCWork<VM>>>) {
70        for w in ws {
71            self.inactive_queue().push(w);
72        }
73    }
74
75    /// Remove and return every packet currently in this queue, leaving it empty.
76    fn drain(&self) -> Vec<Box<dyn GCWork<VM>>> {
77        let mut items = Vec::new();
78        loop {
79            match self.queue0.steal() {
80                Steal::Success(w) => items.push(w),
81                Steal::Retry => continue,
82                Steal::Empty => break,
83            }
84        }
85        loop {
86            match self.queue1.steal() {
87                Steal::Success(w) => items.push(w),
88                Steal::Retry => continue,
89                Steal::Empty => break,
90            }
91        }
92        items
93    }
94
95    /// Dump all the packets in this queue for debugging purpose.
96    /// This function may dump items from the queue temporarily, thus should only be called when it is safe to do so
97    /// (e.g. when the execution has failed already and the system is going to panic).
98    pub fn debug_dump_packets(&self) -> Vec<String> {
99        let mut items = Vec::new();
100        let queue = self.active_queue();
101
102        {
103            // Drain queue by stealing until empty
104            loop {
105                match queue.steal() {
106                    crossbeam::deque::Steal::Success(work) => {
107                        items.push(work);
108                    }
109                    crossbeam::deque::Steal::Retry => continue,
110                    crossbeam::deque::Steal::Empty => break,
111                }
112            }
113        }
114
115        // Format collected items (just type names or Debug, depending on GCWork)
116        let debug_items: Vec<String> = items
117            .iter()
118            .map(|i| i.get_type_name().to_string()) // placeholder since GCWork isn’t Debug
119            .collect();
120
121        // Push items back into the queue
122        {
123            for work in items {
124                queue.push(work);
125            }
126        }
127
128        debug_items
129    }
130}
131
132pub type BucketOpenCondition<VM> = Box<dyn (Fn(&GCWorkScheduler<VM>) -> bool) + Send>;
133
134pub struct WorkBucket<VM: VMBinding> {
135    /// Whether this bucket has been opened. Work from an open bucket can be fetched by workers.
136    open: AtomicBool,
137    /// Whether this bucket is enabled.
138    /// A disabled work bucket will behave as if it does not exist in terms of scheduling,
139    /// except that users can add work to a disabled bucket, and enable it later to allow those
140    /// work to be scheduled.
141    enabled: AtomicBool,
142    /// The stage name of this bucket.
143    stage: WorkBucketStage,
144    queue: BucketQueue<VM>,
145    monitor: Arc<WorkerMonitor>,
146    /// The open condition for a bucket. If this is `Some`, the bucket will be open
147    /// when the condition is met. If this is `None`, the bucket needs to be open manually.
148    can_open: Option<BucketOpenCondition<VM>>,
149    /// After this bucket is open and all pending work packets (including the packets in this
150    /// bucket) are drained, this work packet, if exists, will be added to this bucket.  When this
151    /// happens, it will prevent opening subsequent work packets.
152    ///
153    /// The sentinel work packet may set another work packet as the new sentinel which will be
154    /// added to this bucket again after all pending work packets are drained.  This may happend
155    /// again and again, causing the GC to stay at the same stage and drain work packets in a loop.
156    ///
157    /// This is useful for handling weak references that may expand the transitive closure
158    /// recursively, such as ephemerons and Java-style SoftReference and finalizers.  Sentinels
159    /// can be used repeatedly to discover and process more such objects.
160    sentinel: Mutex<Option<Box<dyn GCWork<VM>>>>,
161}
162
163impl<VM: VMBinding> WorkBucket<VM> {
164    pub(crate) fn new(stage: WorkBucketStage, monitor: Arc<WorkerMonitor>) -> Self {
165        Self {
166            open: AtomicBool::new(stage.is_open_by_default()),
167            enabled: AtomicBool::new(stage.is_enabled_by_default()),
168            stage,
169            queue: BucketQueue::new(),
170            monitor,
171            can_open: None,
172            sentinel: Mutex::new(None),
173        }
174    }
175
176    pub fn set_enabled(&self, enabled: bool) {
177        self.enabled.store(enabled, Ordering::SeqCst)
178    }
179
180    pub fn is_enabled(&self) -> bool {
181        self.enabled.load(Ordering::Relaxed)
182    }
183
184    pub fn flip(&self) {
185        self.queue.flag.fetch_xor(true, Ordering::SeqCst);
186    }
187
188    fn notify_one_worker(&self) {
189        // If the bucket is not open, don't notify anyone.
190        if !self.is_open() || !self.is_enabled() {
191            return;
192        }
193        // Notify one if there're any parked workers.
194        self.monitor.notify_work_available(false);
195    }
196
197    pub fn notify_all_workers(&self) {
198        // If the bucket is not open, don't notify anyone.
199        if !self.is_open() || !self.is_enabled() {
200            return;
201        }
202        // Notify all if there're any parked workers.
203        self.monitor.notify_work_available(true);
204    }
205
206    pub fn is_open(&self) -> bool {
207        self.open.load(Ordering::SeqCst)
208    }
209
210    /// Open the bucket
211    pub fn open(&self) {
212        self.open.store(true, Ordering::SeqCst);
213    }
214
215    /// Test if the bucket is drained
216    pub fn is_empty(&self) -> bool {
217        self.queue.is_empty()
218    }
219
220    pub fn is_drained(&self) -> bool {
221        !self.is_enabled() || (self.is_open() && self.is_empty())
222    }
223
224    /// Remove and return every packet currently queued in this bucket (including the
225    /// prioritized queue, if any), leaving it empty.
226    ///
227    /// This does not synchronize with producers or consumers of this bucket in any way: the
228    /// caller must independently ensure that nothing can be concurrently adding to or polling
229    /// this bucket (e.g. by only calling this once all GC workers are known to be parked, and
230    /// after disabling the bucket so no new packets can be routed to it, as
231    /// `ConcurrentImmix::schedule_concurrent_marking_final_pause` does for `Concurrent`).
232    pub(crate) fn drain_all_packets(&self) -> Vec<Box<dyn GCWork<VM>>> {
233        self.queue.drain()
234    }
235
236    /// Close the bucket
237    pub fn close(&self) {
238        debug_assert!(
239            self.queue.is_empty(),
240            "Bucket {:?} not drained before close",
241            self.stage
242        );
243        self.open.store(false, Ordering::Relaxed);
244    }
245
246    fn warn_notify_add_if_disabled(&self) {
247        #[cfg(debug_assertions)]
248        if !self.is_enabled() {
249            // This is usually benign if it happens occasionally. But if we keep adding work to a disabled bucket,
250            // we keep waking up workers for new work that they can't work on.
251            warn!(
252                "Add a work to a disabled bucket with notifying one worker {:?}",
253                self.stage
254            );
255        }
256    }
257
258    /// Add a work packet to this bucket
259    pub fn add<W: GCWork<VM>>(&self, work: W) {
260        self.warn_notify_add_if_disabled();
261        self.queue.push(Box::new(work));
262        self.notify_one_worker();
263    }
264
265    /// Add a work packet to this bucket
266    pub fn add_boxed(&self, work: Box<dyn GCWork<VM>>) {
267        self.warn_notify_add_if_disabled();
268        self.queue.push(work);
269        self.notify_one_worker();
270    }
271
272    pub fn add_deferred(&self, work: Box<dyn GCWork<VM>>) {
273        self.queue.push_inactive(work);
274    }
275
276    pub fn bulk_add_deferred(&self, work_vec: Vec<Box<dyn GCWork<VM>>>) {
277        self.queue.push_all_inactive(work_vec);
278    }
279
280    /// Add a work packet to this bucket, but do not notify any workers.
281    /// This is useful when the current thread is holding the mutex of `WorkerMonitor` which is
282    /// used for notifying workers.  This usually happens if the current thread is the last worker
283    /// parked.
284    pub(crate) fn add_no_notify<W: GCWork<VM>>(&self, work: W) {
285        self.queue.push(Box::new(work));
286    }
287
288    /// Like [`WorkBucket::add_no_notify`], but the work is boxed.
289    pub(crate) fn add_boxed_no_notify(&self, work: Box<dyn GCWork<VM>>) {
290        self.queue.push(work);
291    }
292
293    /// Add multiple packets
294    pub fn bulk_add(&self, work_vec: Vec<Box<dyn GCWork<VM>>>) {
295        debug_assert!(self.is_enabled());
296        if work_vec.is_empty() {
297            return;
298        }
299        let len = work_vec.len();
300        self.queue.push_all(work_vec);
301        if self.is_open() {
302            if len == 1 {
303                self.notify_one_worker();
304            } else {
305                self.notify_all_workers();
306            }
307        }
308    }
309
310    /// Get a work packet from this bucket
311    pub fn poll(&self, worker: &Worker<Box<dyn GCWork<VM>>>) -> Steal<Box<dyn GCWork<VM>>> {
312        if !self.is_enabled() || !self.is_open() || self.is_empty() {
313            return Steal::Empty;
314        }
315        self.queue.steal_batch_and_pop(worker)
316    }
317
318    pub fn set_open_condition(
319        &mut self,
320        pred: impl Fn(&GCWorkScheduler<VM>) -> bool + Send + 'static,
321    ) {
322        self.can_open = Some(Box::new(pred));
323    }
324
325    pub fn set_sentinel(&self, new_sentinel: Box<dyn GCWork<VM>>) {
326        let mut sentinel = self.sentinel.lock().unwrap();
327        *sentinel = Some(new_sentinel);
328    }
329
330    pub fn has_sentinel(&self) -> bool {
331        let sentinel = self.sentinel.lock().unwrap();
332        sentinel.is_some()
333    }
334
335    pub fn update(&self, scheduler: &GCWorkScheduler<VM>) -> bool {
336        if let Some(can_open) = self.can_open.as_ref() {
337            if !self.is_open() && can_open(scheduler) {
338                debug!("Opening work bucket: {:?}", self.stage);
339                self.open();
340                return true;
341            }
342        }
343        false
344    }
345
346    pub fn maybe_schedule_sentinel(&self) -> bool {
347        debug_assert!(
348            self.is_open(),
349            "Attempted to schedule sentinel work while bucket is not open"
350        );
351        let maybe_sentinel = {
352            let mut sentinel = self.sentinel.lock().unwrap();
353            sentinel.take()
354        };
355        if let Some(work) = maybe_sentinel {
356            // We don't need to notify other workers because this function is called by the last
357            // parked worker.  After this function returns, the caller will notify workers because
358            // more work packets become available.
359            self.add_boxed_no_notify(work);
360            true
361        } else {
362            false
363        }
364    }
365
366    pub(super) fn get_queue(&self) -> &BucketQueue<VM> {
367        &self.queue
368    }
369
370    pub(super) fn get_stage(&self) -> WorkBucketStage {
371        self.stage
372    }
373}
374
375/// This enum defines all the work bucket types. The scheduler
376/// will instantiate a work bucket for each stage defined here.
377#[derive(Debug, Enum, Copy, Clone, Eq, PartialEq, Hash)]
378pub enum WorkBucketStage {
379    /// This bucket is always open.
380    Unconstrained,
381    /// This bucket is intended for concurrent work. Though some concurrent work may be put and executed in the unconstrained bucket,
382    /// work in the unconstrained bucket will always be consumed during STW. Users can disable this bucket
383    /// and cache some concurrent work during STW, and only enable this bucket and allow concurrent execution once a STW is done.
384    Concurrent,
385    /// Concurrent work that may be resumed across a stop-the-world pause (LXR-specific), such as
386    /// concurrent marking packets discovered while processing reference-count increments during
387    /// `InitialMark`.  Unlike other stages, this bucket is allowed to remain non-empty when a STW
388    /// pause ends.
389    ConcurrentResumable,
390    /// The first stop-the-world stage (see [`WorkBucketStage::FIRST_STW_STAGE`]).  Used to join
391    /// outstanding concurrent work, e.g. flushing SATB mod-buffer packets recorded by the LXR
392    /// barrier, before the rest of the STW stages proceed.
393    FinishConcurrentWork,
394    /// Process the reference-count increments that must not move their objects.  LXR also scans
395    /// roots here (see `root_scanning_stage`); the roots a binding reports as objects rather than
396    /// as slots are counted in this stage because there is no slot to write a forwarding pointer
397    /// back into.  Counting them before `RCProcessIncs` opens is what keeps them in place.
398    RCProcessIncsNonMoving,
399    /// Process reference-count increments from the LXR barrier and from root slots.  May
400    /// evacuate nursery objects, so it must run after `RCProcessIncsNonMoving`.
401    RCProcessIncs,
402    /// Preparation work.  Plans, spaces, GC workers, mutators, etc. should be prepared for GC at
403    /// this stage.
404    Prepare,
405    /// Clear the VO bit metadata.  Mainly used by ImmixSpace.
406    #[cfg(feature = "vo_bit")]
407    ClearVOBits,
408    /// Compute the transtive closure starting from transitively pinning (TP) roots following only strong references.
409    /// No objects in this closure are allow to move.
410    TPinningClosure,
411    /// Trace (non-transitively) pinning roots. Objects pointed by those roots must not move, but their children may. To ensure correctness, these must be processed after TPinningClosure
412    PinningRootsTrace,
413    /// Compute the transtive closure following only strong references.
414    Closure,
415    /// Handle Java-style soft references, and potentially expand the transitive closure.
416    SoftRefClosure,
417    /// Handle Java-style weak references.
418    WeakRefClosure,
419    /// Resurrect Java-style finalizable objects, and potentially expand the transitive closure.
420    FinalRefClosure,
421    /// Handle Java-style phantom references.
422    PhantomRefClosure,
423    /// Let the VM handle VM-specific weak data structures, including weak references, weak
424    /// collections, table of finalizable objects, ephemerons, etc.  Potentially expand the
425    /// transitive closure.
426    ///
427    /// NOTE: This stage is intended to replace the Java-specific weak reference handling stages
428    /// above.
429    VMRefClosure,
430    /// Compute the forwarding addresses of objects (mark-compact-only).
431    CalculateForwarding,
432    /// Scan roots again to initiate another transitive closure to update roots and reference
433    /// after computing the forwarding addresses (mark-compact-only).
434    SecondRoots,
435    /// Update Java-style weak references after computing forwarding addresses (mark-compact-only).
436    ///
437    /// NOTE: This stage should be updated to adapt to the VM-side reference handling.  It shall
438    /// be kept after removing `{Soft,Weak,Final,Phantom}RefClosure`.
439    RefForwarding,
440    /// Update the list of Java-style finalization cadidates and finalizable objects after
441    /// computing forwarding addresses (mark-compact-only).
442    FinalizableForwarding,
443    /// Let the VM handle the forwarding of reference fields in any VM-specific weak data
444    /// structures, including weak references, weak collections, table of finalizable objects,
445    /// ephemerons, etc., after computing forwarding addresses (mark-compact-only).
446    ///
447    /// NOTE: This stage is intended to replace Java-specific forwarding phases above.
448    VMRefForwarding,
449    /// Compact objects (mark-compact-only).
450    Compact,
451    /// Work packets that should be done just before GC shall go here.  This includes releasing
452    /// resources and setting states in plans, spaces, GC workers, mutators, etc.
453    Release,
454    /// Process reference-count decrements recorded by the LXR barrier, and sweep objects whose
455    /// reference count has dropped to zero, during a stop-the-world pause.  This is used when
456    /// lazy (concurrent) decrements are disabled for the current GC.
457    STWRCDecsAndSweep,
458    /// Resume mutators and end GC.
459    Final,
460}
461
462// Alias
463#[allow(non_upper_case_globals)]
464impl WorkBucketStage {
465    /// The first stop-the-world stage. This stage has no open condition, and will be opened manually
466    /// once all the mutators threads are stopped.
467    pub const FIRST_STW_STAGE: Self = WorkBucketStage::FinishConcurrentWork;
468
469    /// Is this the first stop-the-world stage? See [`Self::FIRST_STW_STAGE`].
470    pub const fn is_first_stw_stage(&self) -> bool {
471        matches!(self, &WorkBucketStage::FIRST_STW_STAGE)
472    }
473
474    /// Is this stage always open?
475    pub const fn is_always_open(&self) -> bool {
476        matches!(self, WorkBucketStage::Unconstrained)
477    }
478
479    /// Is this stage open by default?
480    pub const fn is_open_by_default(&self) -> bool {
481        matches!(
482            self,
483            WorkBucketStage::Unconstrained
484                | WorkBucketStage::Concurrent
485                | WorkBucketStage::ConcurrentResumable
486        )
487    }
488
489    /// Is this stage enabled by default?
490    pub const fn is_enabled_by_default(&self) -> bool {
491        !matches!(self, WorkBucketStage::Concurrent)
492            && !matches!(self, WorkBucketStage::ConcurrentResumable)
493    }
494
495    /// Is this stage sequentially opened? All the stop-the-world stages, except the first one, are sequentially opened.
496    pub const fn is_sequentially_opened(&self) -> bool {
497        self.is_stw() && !self.is_first_stw_stage()
498    }
499
500    /// Is this stage a stop-the-world stage?
501    pub const fn is_stw(&self) -> bool {
502        !self.is_concurrent()
503    }
504
505    /// Is this stage concurrent (which may be executed during mutator time)?
506    pub const fn is_concurrent(&self) -> bool {
507        matches!(
508            self,
509            WorkBucketStage::Unconstrained
510                | WorkBucketStage::Concurrent
511                | WorkBucketStage::ConcurrentResumable
512        )
513    }
514
515    /// Alias for [`WorkBucketStage::Closure`], used by LXR when scheduling mature-space
516    /// evacuation remset packets so that they are processed as part of the transitive closure
517    /// stage.
518    pub const RCEvacuateMature: Self = Self::Closure;
519}