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