1mod balance;
4mod cancellation;
5mod cpu_lifecycle;
6mod deadline;
7mod deferred_work;
8mod delivery;
9mod dispatch;
10mod exited_work;
11mod lifecycle;
12mod membarrier;
13mod migration;
14mod model;
15mod outcome;
16mod park_exit;
17mod pi;
18#[cfg(axtest)]
19pub use {
20 pi::PiScheduleTestProbeSnapshot, pi::begin_pi_schedule_test_probe,
21 pi::end_pi_schedule_test_probe, pi::pi_schedule_test_probe_snapshot,
22};
23mod placement;
24mod priority_index;
25mod registry;
26mod root_domain;
27mod scheduling;
28mod switch;
29mod thread_api;
30mod thread_callbacks;
31mod thread_creation;
32
33use alloc::{sync::Arc, vec::Vec};
34use core::{pin::Pin, ptr};
35
36use exited_work::ExitedThreadWork;
37pub(crate) use membarrier::{MembarrierCpuTargets, MembarrierTarget};
38use model::{
39 BalanceReason, BalanceTransferOutcome, DeferredTaskWorkClass, DetachedOwnerMessageBatch,
40 FAIR_BALANCE_BALANCED_BACKOFF_FACTOR, FAIR_BALANCE_CONSTRAINED_BACKOFF_FACTOR,
41 FairBalanceResult, FairPolicyPlacement,
42};
43pub use model::{DeferredTaskWorkBatch, OwnedThreadReapError, TaskSystem};
44pub(crate) use outcome::SwitchEndpoint;
45pub use outcome::{
46 ChargeOutcome, DeadlineActivitySnapshot, DeadlineRuntimeSnapshot, OwnerControlDrain,
47 ScheduleDecision, SchedulerOutcome, SwitchInCompletion, YieldOutcome,
48};
49pub(crate) use park_exit::CurrentExitPermit;
50use priority_index::RootDomainPriorityIndex;
51use registry::{
52 CpuRegistration, DeadlineCallbackClaim, DetachedThreadRecord, TaskSystemState, ThreadRecord,
53 ThreadSlot,
54};
55use root_domain::{DeadlineBandwidthRebuild, RootDomain, RootDomainPushClass, RootDomainState};
56use thread_callbacks::ThreadCallbackState;
57
58use super::thread_sched::{
59 PiScheduleUpdate, ThreadDeadlineInit, ThreadPlacementInit, ThreadPolicyInit, ThreadRuntimeInit,
60 ThreadSchedCell, ThreadSchedInit, ThreadSchedState,
61};
62use crate::{
63 executor::CoroutineHeader,
64 runtime::{
65 RuntimeStatus,
66 config::TaskSystemConfig,
67 cpu::{CpuLocal, CpuRemote, CpuRemoteHandle, CpuSnapshot, RuntimeCpuId},
68 delivery::{
69 inbox::{InboxKind, InboxMessage, InboxOperation, PublishResult, SchedulerInbox},
70 work::{TaskWorkConsumerGuard, TaskWorkDoorbell},
71 },
72 lock::{IrqScope, IrqTicketLock, PreemptTicketLock},
73 resource::{
74 AddressSpaceDestroyOutcome, AddressSpaceMembarrierId, AddressSpaceMembarrierState,
75 AddressSpaceReclaimArmOutcome, MembarrierRegistration, MembarrierRegistrationPhase,
76 ThreadResources,
77 },
78 switch::{ContextThreadBinding, CurrentThreadPublication, CurrentThreadRef},
79 sync::{PiMutexRaw, PiWaitToken},
80 task_runtime,
81 },
82 sched::{
83 CpuId, CpuSet, DeadlineBandwidthSnapshot, FairMode, SchedulePolicy, SchedulerTimestamp,
84 SchedulingClass, ThreadAffinityChange,
85 algorithm::{
86 ActiveSchedulingState, DeadlineAdmission, EnqueueReason, PickedThread, QueuedThread,
87 QueuedThreadSnapshot, RqTaskMetadata, SchedulingEntity,
88 },
89 system::{
90 CpuRemotePublication, SchedulerDeadlineDerivationSource, SchedulerPolicyRef,
91 cpu::{
92 CpuRunQueueState, CurrentDispatch, DeadlineBaseGuardSource, EqualRtWakeAction,
93 HardTimerServiceClaim, HardTimerServiceStep, IdlePullReservation,
94 KtimerServiceClaim, OwnerRqEntry, OwnerRqTxn, PreparedMigrationDelivery,
95 RescheduleKind, RunQueueClockSnapshot, RunQueueDomainPublication,
96 RunQueueGuardSource, SchedulerDeadlineRqObservation, SchedulerRequestScope,
97 SchedulerThreadRef, SoftTimerExpireBatch, WakePreemptionDecision,
98 },
99 },
100 },
101 thread::{
102 DEADLINE_CLASS_RANK, DeadlineEntity, DeadlineServer, OwnedThreadSchedulerExit, ParkCommit,
103 ParkPrepare, ParkTicket, PiDonation, PiWaitKey, PiWaitRegistration, REALTIME_CLASS_RANK,
104 SchedulingUrgency, SwitchReason, TaskError, ThreadCore, ThreadCoreInit, ThreadExtension,
105 ThreadExtensionBorrow, ThreadExtensionLease, ThreadExtensionView, ThreadHandle, ThreadId,
106 ThreadRuntimeSnapshot, ThreadSpec, ThreadState, ThreadWakeHandle, WaitWakeClaim,
107 WaitWakeDelivery, WakeIntent, WakeResult,
108 },
109 time::{
110 MonotonicDeadline, MonotonicInstant,
111 queue::{
112 ExpiredTaskDeadline, KernelTimerExecution, TaskDeadlineArmPlan, TaskDeadlineError,
113 TaskDeadlineKind, TaskDeadlineNode, TaskDeadlineQueue, TaskDeadlineRegistration,
114 },
115 },
116};
117
118struct UnpublishedThreadGuard<'system> {
119 system: &'system TaskSystem,
120 spec: Option<ThreadSpec>,
121}
122
123fn apply_pi_schedule_update(
124 sched: &mut ThreadSchedState,
125 mut active: ActiveSchedulingState,
126 update: PiScheduleUpdate,
127 owner_now_ns: u64,
128 fair_placement: Option<FairPolicyPlacement>,
129) -> Result<ActiveSchedulingState, TaskError> {
130 if update.generation != sched.policy.dispatch_generation {
131 return Err(TaskError::InvalidPiState);
132 }
133
134 let PiScheduleUpdate {
135 policy,
136 donor,
137 deadline_donor,
138 deadline_donor_core,
139 deadline_donor_server,
140 generation: _,
141 } = update;
142 let old_donor = sched.pi.donor;
143 let old_deadline_donor = sched.pi.deadline_donor;
144 let base = sched.policy.base;
145 let old_uses_inherited = active.uses_inherited_entity();
146 let next_uses_inherited = donor.is_some() && !pi_reuses_base_entity(base, policy);
147 if old_uses_inherited && !next_uses_inherited {
148 active.use_base_entity(base);
149 }
150
151 let source_changed = old_donor != donor || old_deadline_donor != deadline_donor;
152 let donor_server = deadline_donor_server;
153 match (donor, policy) {
154 (None, base_policy) if base_policy == base => {
155 active.use_base_entity(base_policy);
156 }
157 (Some(_), SchedulePolicy::Deadline(_)) => {
158 if !next_uses_inherited {
159 return Err(TaskError::InvalidPiState);
160 }
161 if !old_uses_inherited || source_changed {
162 active.use_inherited_entity(
163 policy,
164 SchedulingEntity::Deadline(crate::thread::DeadlineEntity::from_donor_server(
165 sched.deadline.server.clone(),
166 donor_server.ok_or(TaskError::InvalidPiState)?,
167 )),
168 );
169 } else {
170 active.update_inherited_effective_policy(policy);
171 }
172 let SchedulingEntity::Deadline(deadline) = active.entity() else {
173 return Err(TaskError::InvalidPiState);
174 };
175 deadline.replenish_for_pi(owner_now_ns);
176 }
177 (Some(_), SchedulePolicy::Fifo { .. }) => {
178 if next_uses_inherited {
179 active.use_inherited_entity(policy, SchedulingEntity::Fifo);
180 } else if !matches!(active.base_entity(), SchedulingEntity::Fifo) {
181 return Err(TaskError::InvalidPiState);
182 } else {
183 active.use_base_entity_with_effective_policy(policy);
184 }
185 }
186 (Some(_), SchedulePolicy::RoundRobin { quantum_ns, .. }) => {
187 if next_uses_inherited {
188 return Err(TaskError::InvalidPiState);
189 }
190 if !matches!(active.base_entity(), SchedulingEntity::RoundRobin { .. }) {
191 return Err(TaskError::InvalidPiState);
192 }
193 if old_donor.is_none()
194 && let SchedulingEntity::RoundRobin {
195 remaining_quantum_ns,
196 } = active.base_entity_mut()
197 && *remaining_quantum_ns > quantum_ns
198 {
199 *remaining_quantum_ns = quantum_ns;
200 }
201 active.use_base_entity_with_effective_policy(policy);
202 }
203 (Some(_), SchedulePolicy::Fair { nice, mode }) => {
204 if next_uses_inherited {
205 return Err(TaskError::InvalidPiState);
206 }
207 let SchedulingEntity::Fair(fair) = *active.base_entity() else {
208 return Err(TaskError::InvalidPiState);
209 };
210 let placement = fair_placement.ok_or(TaskError::InvalidPiState)?;
211 active.replace_base_entity(SchedulingEntity::Fair(fair.reconfigure(
212 nice,
213 mode,
214 placement.source_virtual_time,
215 placement.destination_virtual_time,
216 )));
217 active.use_base_entity_with_effective_policy(policy);
218 }
219 _ => return Err(TaskError::InvalidPiState),
220 }
221
222 sched.pi.donor = donor;
223 sched.pi.deadline_donor = deadline_donor;
224 sched.pi.deadline_donor_core = deadline_donor_core;
225 Ok(active)
226}
227
228fn pi_reuses_base_entity(base: SchedulePolicy, effective: SchedulePolicy) -> bool {
229 matches!(
230 (base, effective),
231 (
232 SchedulePolicy::Fifo { .. } | SchedulePolicy::RoundRobin { .. },
233 SchedulePolicy::Fifo { .. } | SchedulePolicy::RoundRobin { .. }
234 ) | (SchedulePolicy::Fair { .. }, SchedulePolicy::Fair { .. })
235 )
236}
237
238struct OwnerNext {
239 core: SchedulerThreadRef,
240 policy: SchedulerPolicyRef,
241 urgency: SchedulingUrgency,
242}
243
244enum OwnerSchedulerDeadline {
251 Unchanged,
252 Reevaluate(SchedulerDeadlineRqObservation),
253}
254
255impl<'system> UnpublishedThreadGuard<'system> {
256 fn new(system: &'system TaskSystem, spec: ThreadSpec) -> Self {
257 Self {
258 system,
259 spec: Some(spec),
260 }
261 }
262
263 fn into_owned_parts(mut self) -> (Option<ThreadExtension>, ThreadResources) {
264 self.spec
265 .take()
266 .expect("unpublished thread transaction must still own its specification")
267 .into_owned_parts()
268 }
269
270 fn into_spec(mut self) -> ThreadSpec {
271 self.spec
272 .take()
273 .expect("unpublished thread transaction must still own its specification")
274 }
275
276 fn spec(&self) -> &ThreadSpec {
277 self.spec
278 .as_ref()
279 .expect("unpublished thread transaction must still own its specification")
280 }
281}
282
283impl Drop for UnpublishedThreadGuard<'_> {
284 fn drop(&mut self) {
285 if let Some(spec) = self.spec.take() {
286 let (extension, resources) = spec.into_owned_parts();
287 self.system
288 .release_unpublished_thread(DetachedThreadRecord::new(resources, extension));
289 }
290 }
291}
292
293impl TaskSystem {
294 pub fn new(config: TaskSystemConfig) -> Result<Self, TaskError> {
302 validate_config(config)?;
303 let task_work = Arc::new(TaskWorkDoorbell::new());
304 let cpu_remotes = (0..config.cpu_count())
305 .map(|index| CpuRemote::create(CpuId::new(index as u32), config))
306 .collect::<Result<Vec<_>, _>>()?;
307 let cpu_registrations = cpu_remotes
308 .iter()
309 .cloned()
310 .map(|remote| CpuRegistration { remote })
311 .collect();
312 let root_domain = RootDomain::new(config, cpu_remotes.clone());
313 Ok(Self {
314 config,
315 cpu_remotes,
316 state: PreemptTicketLock::new(TaskSystemState {
317 cpus: cpu_registrations,
318 slots: crate::thread::allocation::try_vec(config.thread_capacity())?,
319 free_slots: crate::thread::allocation::try_vec(config.thread_capacity())?,
320 pending_address_space_reclaims: crate::thread::allocation::try_vec(
321 config.thread_capacity().max(config.cpu_count()),
322 )?,
323 task_work_class_cursor: DeferredTaskWorkClass::Deadline,
324 address_space_reclaim_first: true,
325 exited_work: ExitedThreadWork::new(config.thread_capacity())?,
326 }),
327 root_domain,
328 deferred_coroutine_reclaims: SchedulerInbox::new(InboxKind::Reclaim),
329 deferred_thread_cancellations: SchedulerInbox::new(InboxKind::Reclaim),
330 deferred_deadline_callbacks: SchedulerInbox::new(InboxKind::TaskWork),
331 deferred_scheduler_ticks: SchedulerInbox::new(InboxKind::TaskWork),
332 task_work,
333 })
334 }
335}
336
337fn validate_config(config: TaskSystemConfig) -> Result<(), TaskError> {
338 if config.cpu_count() == 0 || config.cpu_count() > u32::MAX as usize {
339 return Err(TaskError::InvalidCpuCount(config.cpu_count()));
340 }
341 if config.deadline_cap_percent() == 0
342 || config.deadline_cap_percent() > 100
343 || config.rt_period_ns() == 0
344 || config.rt_runtime_ns() > config.rt_period_ns()
345 || config.balance_interval_ns() == 0
346 || config.thread_capacity() == 0
347 || config.thread_capacity() > u32::MAX as usize
348 || config.batch_limit() == 0
349 || config.batch_limit() > crate::runtime::config::DEFAULT_BATCH_LIMIT
350 || config.pi_chain_limit() == 0
351 {
352 return Err(TaskError::InvalidConfiguration);
353 }
354 Ok(())
355}
356
357fn deadline_zero_lag(deadline: &DeadlineEntity) -> SchedulerTimestamp {
358 let policy = deadline.policy();
359 let lag_ns = deadline.remaining_runtime_ns() as u128 * policy.period_ns() as u128
360 / policy.runtime_ns() as u128;
361 let lag_ns = u64::try_from(lag_ns)
362 .expect("Deadline zero-lag interval cannot exceed one scheduler period");
363 SchedulerTimestamp::from_nanos(
364 deadline
365 .absolute_deadline_ns()
366 .expect("an active Deadline entity must own a zero-lag anchor"),
367 )
368 .retreat(lag_ns)
369}
370
371fn ensure_runtime_success(status: RuntimeStatus) -> Result<(), TaskError> {
372 if status == RuntimeStatus::Success {
373 Ok(())
374 } else {
375 Err(TaskError::RuntimeFailure(status as u32))
376 }
377}
378
379fn validate_affinity(affinity: &CpuSet, cpu_count: usize) -> Result<(), TaskError> {
380 if affinity.topology_len() == cpu_count {
381 Ok(())
382 } else {
383 Err(TaskError::InvalidConfiguration)
384 }
385}
386
387const MAX_THREAD_GENERATION: u32 = i32::MAX as u32;
391
392const fn next_generation(generation: u32) -> u32 {
393 if generation < MAX_THREAD_GENERATION {
394 generation + 1
395 } else {
396 generation
397 }
398}
399
400fn advance_thread_slot_generation(slot: &mut ThreadSlot) -> bool {
401 assert_eq!(
402 slot.pending_deadline_reservation, 0,
403 "a reusable thread slot must not retain Deadline admission"
404 );
405 let next = next_generation(slot.generation);
406 if next == slot.generation {
407 false
411 } else {
412 slot.generation = next;
413 true
414 }
415}