moirai_executor/schedule/runtime/
worker.rs1mod indexed;
4mod wait;
5
6use std::{
7 sync::{Mutex, MutexGuard},
8 thread::{self, JoinHandle},
9};
10
11use super::super::job::ScheduledJob;
12use super::super::queue::WorkerQueueOwner;
13
14use super::idle_hooks::run_idle_hooks;
15use super::scheduler::placement::WorkerPin;
16use super::types::{ContendedWakePolicy, SchedulerInner, WorkerState, set_current_worker_id};
17pub(super) use indexed::{
18 indexed_chunk_bounds, indexed_chunk_count, inline_map_reduce, map_reduce_range,
19};
20use wait::{should_stop, spin_for_work, wait_for_work};
21
22#[cfg(feature = "scheduler-diagnostics")]
23use super::types::BoundedContendedWake;
24
25pub(super) const WORKER_IDLE_SPIN_ATTEMPTS: usize = 256;
26pub(super) const JOIN_FAST_SPIN_ATTEMPTS: usize = WORKER_IDLE_SPIN_ATTEMPTS;
27
28pub(super) fn join_other_threads(handles: &mut Vec<JoinHandle<()>>) {
35 let current = thread::current().id();
36 while let Some(handle) = handles.pop() {
37 if handle.thread().id() != current {
38 let _ = handle.join();
39 }
40 }
41}
42
43pub(super) fn worker_loop<const BLOCKING_QUEUE_CAPACITY: usize, const SPIN_LIMIT: usize>(
44 inner: std::sync::Arc<SchedulerInner<BLOCKING_QUEUE_CAPACITY>>,
45 worker_id: usize,
46 mut owner: WorkerQueueOwner,
47 pin: Option<WorkerPin>,
48) {
49 set_current_worker_id(Some(worker_id));
50 let _ = inner.workers[worker_id].thread.set(thread::current());
51
52 if let Some(pin) = pin {
55 let outcome = pin.bind();
56 let refused = outcome.is_err();
57 let _ = inner.workers[worker_id].placement.set(outcome);
58 if refused {
59 return;
60 }
61 }
62
63 loop {
64 if let Some(job) = next_job(&inner, worker_id, &mut owner) {
65 execute_job(&inner, worker_id, job);
66 continue;
67 }
68
69 if should_stop(&inner) {
70 break;
71 }
72
73 if spin_for_work::<BLOCKING_QUEUE_CAPACITY, SPIN_LIMIT>(&inner, worker_id) {
74 continue;
75 }
76
77 run_idle_memory_maintenance();
80
81 owner.shrink_drained_planes();
82
83 run_idle_hooks();
87
88 wait_for_work(&inner, worker_id);
89 }
90}
91
92#[cfg(feature = "mnemosyne")]
93melinoe::thread_cached! {
94 mod last_maintenance_time: std::time::Instant;
95}
96
97#[inline]
98fn run_idle_memory_maintenance() {
99 #[cfg(feature = "mnemosyne")]
100 {
101 use mnemosyne::{LocalAllocatorSelector, MemoryBackendWrapper};
102 if <MemoryBackendWrapper as LocalAllocatorSelector<MemoryBackendWrapper>>::get_allocator_ptr_raw().is_null() {
103 return;
104 }
105
106 let now = std::time::Instant::now();
107 let should_run = if let Some(last) = last_maintenance_time::get() {
108 if now.duration_since(last) >= std::time::Duration::from_millis(500) {
109 last_maintenance_time::set(now);
110 true
111 } else {
112 false
113 }
114 } else {
115 last_maintenance_time::set(now);
116 true
117 };
118
119 if should_run {
120 let _ =
121 <MemoryBackendWrapper as LocalAllocatorSelector<MemoryBackendWrapper>>::with_allocator(
122 |alloc| unsafe {
127 alloc.periodic_defragmentation_sweep();
128 },
129 );
130 }
131 }
132}
133
134pub(super) fn next_job<const BLOCKING_QUEUE_CAPACITY: usize>(
135 inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
136 worker_id: usize,
137 owner: &mut WorkerQueueOwner,
138) -> Option<ScheduledJob> {
139 let local = &inner.workers[worker_id];
140 local
141 .lifo_slot
142 .pop()
143 .or_else(|| owner.pop_local())
144 .or_else(|| steal_job(inner, worker_id, owner))
145}
146
147pub(super) fn next_shared_job<const BLOCKING_QUEUE_CAPACITY: usize>(
149 inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
150 worker_id: usize,
151) -> Option<ScheduledJob> {
152 let local = &inner.workers[worker_id];
153 local
154 .lifo_slot
155 .pop()
156 .or_else(|| local.queues.steal_one())
157 .or_else(|| steal_shared_job(inner, worker_id))
158}
159
160fn steal_job<const BLOCKING_QUEUE_CAPACITY: usize>(
161 inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
162 worker_id: usize,
163 owner: &mut WorkerQueueOwner,
164) -> Option<ScheduledJob> {
165 let worker_count = inner.workers.len();
166 let my_node = inner.worker_numa_nodes.get(worker_id).copied().flatten();
167
168 if let Some(node) = my_node {
182 let start = next_steal_start();
183 for offset in 0..worker_count {
184 let victim_index = (start.wrapping_add(offset)) % worker_count;
185 if victim_index == worker_id {
186 continue;
187 }
188 if inner.worker_numa_nodes.get(victim_index).copied().flatten() != Some(node) {
190 continue;
191 }
192 let victim = &inner.workers[victim_index];
193 if let Some(job) = owner.steal_batch(&victim.queues) {
194 return Some(job);
195 }
196 if let Some(job) = victim.lifo_slot.steal() {
197 return Some(job);
198 }
199 }
200 }
201
202 let start = next_steal_start();
204 for offset in 0..worker_count {
205 let victim_index = (start.wrapping_add(offset)) % worker_count;
206 if victim_index == worker_id {
207 continue;
208 }
209 let victim = &inner.workers[victim_index];
210 if let Some(job) = owner.steal_batch(&victim.queues) {
211 return Some(job);
212 }
213 if let Some(job) = victim.lifo_slot.steal() {
214 return Some(job);
215 }
216 }
217
218 None
219}
220
221fn steal_shared_job<const BLOCKING_QUEUE_CAPACITY: usize>(
222 inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
223 worker_id: usize,
224) -> Option<ScheduledJob> {
225 let worker_count = inner.workers.len();
226 let start = next_steal_start();
227 for offset in 0..worker_count {
228 let victim_index = start.wrapping_add(offset) % worker_count;
229 if victim_index == worker_id {
230 continue;
231 }
232 let victim = &inner.workers[victim_index];
233 if let Some(job) = victim.queues.steal_one() {
234 return Some(job);
235 }
236 if let Some(job) = victim.lifo_slot.steal() {
237 return Some(job);
238 }
239 }
240 None
241}
242
243fn next_steal_start() -> usize {
249 use std::cell::Cell;
250 std::thread_local! {
251 #[allow(clippy::missing_const_for_thread_local)]
253 static RNG: Cell<u64> = const { Cell::new(0) };
254 }
255 RNG.with(|cell| {
256 let mut x = cell.get();
257 if x == 0 {
258 x = (cell as *const Cell<u64> as u64) | 1;
259 }
260 x ^= x << 13;
261 x ^= x >> 7;
262 x ^= x << 17;
263 cell.set(x);
264 x as usize
265 })
266}
267
268pub(super) fn execute_job<const BLOCKING_QUEUE_CAPACITY: usize>(
269 inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
270 worker_id: usize,
271 job: ScheduledJob,
272) {
273 execute_job_with_counters(
274 inner,
275 worker_id,
276 job,
277 &inner.pending_tasks,
278 &inner.active_workers,
279 );
280}
281
282pub(super) fn execute_blocking_job<const BLOCKING_QUEUE_CAPACITY: usize>(
283 inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
284 worker_id: usize,
285 job: ScheduledJob,
286) {
287 execute_job_with_counters(
288 inner,
289 worker_id,
290 job,
291 &inner.blocking_pending_tasks,
292 &inner.blocking_active_workers,
293 );
294}
295
296fn execute_job_with_counters<const BLOCKING_QUEUE_CAPACITY: usize>(
297 inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
298 worker_id: usize,
299 job: ScheduledJob,
300 pending_tasks: &std::sync::atomic::AtomicUsize,
301 active_workers: &std::sync::atomic::AtomicUsize,
302) {
303 use std::sync::atomic::Ordering;
304 active_workers.fetch_add(1, Ordering::Release);
305 pending_tasks.fetch_sub(1, Ordering::Release);
306
307 if job.execute(worker_id) {
308 inner.completed_tasks.fetch_add(1, Ordering::Relaxed);
309 } else {
310 inner.failed_tasks.fetch_add(1, Ordering::Relaxed);
311 }
312
313 if active_workers.fetch_sub(1, Ordering::SeqCst) == 1 {
324 notify_quiescent(inner);
325 }
326}
327
328pub(super) fn wake_worker(worker: &WorkerState) {
329 if let Some(thread) = worker.thread.get() {
330 thread.unpark();
331 }
332}
333
334pub(super) fn wake_all_workers<const BLOCKING_QUEUE_CAPACITY: usize>(
335 inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
336) {
337 for worker in inner.workers.iter() {
338 wake_worker(worker);
339 }
340}
341
342pub(super) trait ContendedWakable {
343 #[cfg(feature = "scheduler-diagnostics")]
345 fn worker_count(&self) -> usize;
346 #[cfg(feature = "scheduler-diagnostics")]
348 fn wake_worker(&self, worker_index: usize);
349 fn wake_contended<P>(&self, worker_index: usize, previous_pending: usize) -> usize
350 where
351 P: ContendedWakePolicy;
352}
353
354impl<const BLOCKING_QUEUE_CAPACITY: usize> ContendedWakable
355 for SchedulerInner<BLOCKING_QUEUE_CAPACITY>
356{
357 #[cfg(feature = "scheduler-diagnostics")]
358 fn worker_count(&self) -> usize {
359 self.workers.len()
360 }
361
362 #[cfg(feature = "scheduler-diagnostics")]
363 fn wake_worker(&self, worker_index: usize) {
364 wake_worker(&self.workers[worker_index]);
365 }
366
367 fn wake_contended<P>(&self, worker_index: usize, previous_pending: usize) -> usize
368 where
369 P: ContendedWakePolicy,
370 {
371 let worker_count = self.workers.len();
372 wake_worker(&self.workers[worker_index]);
373
374 if P::WAKE_LIMIT < 2 || worker_count < 2 {
375 return 1;
376 }
377
378 let peer_index = worker_index.wrapping_add(previous_pending) % worker_count;
379 wake_worker(&self.workers[peer_index]);
380 2
381 }
382}
383
384#[cold]
385#[inline(never)]
386pub(super) fn wake_contended_workers<P>(
387 inner: &impl ContendedWakable,
388 worker_index: usize,
389 previous_pending: usize,
390) -> usize
391where
392 P: ContendedWakePolicy,
393{
394 inner.wake_contended::<P>(worker_index, previous_pending)
395}
396
397#[cfg(feature = "scheduler-diagnostics")]
398#[inline]
399pub(super) fn diagnostic_publish_work_available(
400 inner: &impl ContendedWakable,
401 worker_index: usize,
402 previous_pending: usize,
403) -> usize {
404 let worker_count = inner.worker_count();
405 if previous_pending == 0 {
406 inner.wake_worker(worker_index);
407 1
408 } else if previous_pending < worker_count {
409 wake_contended_workers::<BoundedContendedWake>(inner, worker_index, previous_pending)
410 } else {
411 0
412 }
413}
414
415pub(super) fn notify_quiescent<const BLOCKING_QUEUE_CAPACITY: usize>(
416 inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
417) {
418 use std::sync::atomic::Ordering;
419 if inner.join_waiters.load(Ordering::SeqCst) != 0 && is_quiescent(inner) {
424 let _guard = lock_mutex(&inner.wait_lock);
425 if is_quiescent(inner) {
426 inner.wait_signal.notify_all();
427 }
428 }
429}
430
431pub(super) fn is_quiescent<const BLOCKING_QUEUE_CAPACITY: usize>(
432 inner: &SchedulerInner<BLOCKING_QUEUE_CAPACITY>,
433) -> bool {
434 use std::sync::atomic::Ordering;
435 inner.pending_tasks.load(Ordering::SeqCst) == 0
441 && inner.active_workers.load(Ordering::SeqCst) == 0
442 && inner.blocking_pending_tasks.load(Ordering::SeqCst) == 0
443 && inner.blocking_active_workers.load(Ordering::SeqCst) == 0
444}
445
446pub(super) fn lock_mutex<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
447 mutex
448 .lock()
449 .unwrap_or_else(|poisoned| poisoned.into_inner())
450}