Skip to main content

ax_task/thread/current/
park.rs

1use alloc::sync::Arc;
2use core::num::NonZeroU64;
3
4use crate::{
5    runtime::{
6        TaskSystem,
7        context::{
8            RuntimeIrqGuard, RuntimeSchedulerFrameGuard, runtime_current_cpu_mut,
9            runtime_task_system,
10        },
11        cpu::CpuLocal,
12        lock::PreemptScope,
13        service::SchedulerTickMode,
14        switch::{RuntimeScheduleOrigin, RuntimeSchedulerEntry, dispatch::execute_switch_plan},
15        task_runtime,
16    },
17    sched::{
18        CpuId, SchedulePolicy,
19        system::{DeadlineBaseGuardSource, SchedulerDeadlineDerivationSource},
20    },
21    thread::{
22        ParkCommit, ParkPrepare, TaskError, ThreadCore, ThreadId, ThreadWakeHandle,
23        current::{BlockingPermit, acquire_blocking_permit, current_thread_core_arc},
24    },
25    time::{MonotonicDeadline, MonotonicInstant, queue::TaskDeadlineKind},
26};
27
28/// Result of beginning an externally queued current-thread park transaction.
29///
30/// OS wait subsystems use this after validating their condition under their own
31/// queue lock. The scheduler still owns the generation, deadline, and context
32/// switch transaction; callers own only publication and removal of their
33/// domain-specific waiter record.
34#[derive(Debug)]
35pub enum CurrentParkStart {
36    /// A preceding wake was consumed, so no waiter may be published for this attempt.
37    Notified,
38    /// The current thread entered `Parking` and must be committed or cancelled.
39    Prepared(PreparedCurrentPark),
40}
41
42/// Move-only ownership of one prepared current-thread park transaction.
43#[must_use = "a prepared current-thread park must be committed or cancelled"]
44#[derive(Debug)]
45pub struct PreparedCurrentPark {
46    thread: Arc<ThreadCore>,
47    ticket: Option<crate::thread::ParkTicket>,
48    // Reuse the validated runtime owner through schedule-out. Looking it up
49    // again at commit repeats registry and handle checks on the futex hot path.
50    system: &'static TaskSystem,
51}
52
53/// Terminal scheduler information returned after a prepared park resumes.
54#[derive(Clone, Copy, Debug, Eq, PartialEq)]
55pub struct CurrentParkResume {
56    generation: u64,
57    deadline_expired: bool,
58    disposition: CurrentParkDisposition,
59}
60
61/// Scheduler disposition of one completed current-thread park transaction.
62#[derive(Clone, Copy, Debug, Eq, PartialEq)]
63pub enum CurrentParkDisposition {
64    /// A scheduler notification cancelled the park before schedule-out.
65    NotifiedBeforeBlock,
66    /// The current thread committed `Blocked`, switched out, and later resumed.
67    BlockedAndResumed,
68}
69
70impl CurrentParkResume {
71    /// Returns the scheduler park generation completed by this transaction.
72    pub const fn generation(self) -> u64 {
73        self.generation
74    }
75
76    /// Reports whether an armed task deadline physically expired before cleanup.
77    pub const fn deadline_expired(self) -> bool {
78        self.deadline_expired
79    }
80
81    /// Returns whether this park switched out or was cancelled before blocking.
82    pub const fn disposition(self) -> CurrentParkDisposition {
83        self.disposition
84    }
85
86    /// Reports whether a scheduler notification cancelled schedule-out.
87    pub const fn was_notified_before_block(self) -> bool {
88        matches!(
89            self.disposition,
90            CurrentParkDisposition::NotifiedBeforeBlock
91        )
92    }
93}
94
95impl PreparedCurrentPark {
96    /// Returns the generation-bearing scheduler identity being parked.
97    pub fn thread_id(&self) -> ThreadId {
98        self.thread.id()
99    }
100
101    /// Returns a generation-bearing wake capability for this parked thread.
102    ///
103    /// External waiter queues should publish only this restricted capability,
104    /// not a full scheduler thread handle.
105    pub fn wake_handle(&self) -> ThreadWakeHandle {
106        ThreadWakeHandle::from_core(Arc::clone(&self.thread))
107    }
108
109    /// Returns this park attempt's monotonically increasing generation.
110    pub fn generation(&self) -> u64 {
111        self.ticket()
112            .expect("prepared park ticket remains owned")
113            .generation()
114    }
115
116    /// Arms an absolute deadline in the runtime's finite monotonic domain.
117    pub fn arm_deadline(&mut self, deadline: MonotonicDeadline) -> Result<(), TaskError> {
118        let ticket = self
119            .ticket
120            .as_mut()
121            .expect("prepared park ticket remains owned");
122        arm_current_park_deadline(&self.thread, ticket, deadline)
123    }
124
125    /// Commits the scheduler park and returns after this thread is runnable again.
126    pub fn commit(mut self) -> Result<CurrentParkResume, TaskError> {
127        let mut ticket = self
128            .ticket
129            .take()
130            .expect("prepared park ticket remains owned");
131        let generation = ticket.generation();
132        let deadline_armed = ticket.has_deadline();
133        let disposition =
134            match commit_current_park_with_system(self.system, &self.thread, &mut ticket) {
135                Ok(disposition) => disposition,
136                Err(error) => {
137                    let deadline_result = cancel_current_park_deadline(&self.thread, &mut ticket);
138                    if cancel_current_park(&self.thread, &mut ticket).is_err() {
139                        task_runtime::fatal_invariant(
140                            0x5041_0002,
141                            self.thread.id().as_u64() as usize,
142                        );
143                    }
144                    let _cancelled = deadline_result?;
145                    return Err(error);
146                }
147            };
148        let deadline_cancelled = cancel_current_park_deadline(&self.thread, &mut ticket)?;
149        Ok(CurrentParkResume {
150            generation,
151            deadline_expired: deadline_armed && !deadline_cancelled,
152            disposition,
153        })
154    }
155
156    /// Cancels this transaction without blocking the current thread.
157    pub fn cancel(mut self) -> Result<(), TaskError> {
158        let mut ticket = self
159            .ticket
160            .take()
161            .expect("prepared park ticket remains owned");
162        let deadline_result = cancel_current_park_deadline(&self.thread, &mut ticket);
163        let park_result = cancel_current_park(&self.thread, &mut ticket);
164        let _cancelled = deadline_result?;
165        park_result
166    }
167
168    fn ticket(&self) -> Option<&crate::thread::ParkTicket> {
169        self.ticket.as_ref()
170    }
171}
172
173impl Drop for PreparedCurrentPark {
174    fn drop(&mut self) {
175        if self
176            .ticket
177            .as_ref()
178            .is_some_and(|ticket| !ticket.is_resolved())
179        {
180            task_runtime::fatal_invariant(0x5041_0003, self.thread.id().as_u64() as usize);
181        }
182    }
183}
184
185/// Begins a scheduler-owned park transaction for an OS-owned waiter queue.
186///
187/// The caller must serialize its condition check and waiter publication so a
188/// selecting producer either observes the waiter or leaves the scheduler's
189/// sticky wake-before-park notification. This function is bounded and does not
190/// sleep, allocate, or invoke OS callbacks.
191pub fn begin_current_park() -> Result<CurrentParkStart, TaskError> {
192    let permit = acquire_blocking_permit()?;
193    begin_current_park_with_permit(&permit)
194}
195
196pub(crate) fn begin_current_park_with_permit(
197    _permit: &BlockingPermit,
198) -> Result<CurrentParkStart, TaskError> {
199    let system = runtime_task_system()?;
200    // `current` is migration-stable only while task preemption is disabled.
201    // Keep this lighter than the CPU/rq owner protocol: the pin exists solely
202    // to make the independent task_cpu/on_rq and on_cpu publications one
203    // current-task observation before PARKING becomes visible.
204    let _current_pin = PreemptScope::enter();
205    let thread = current_thread_core_arc()?;
206    let prepare = system.prepare_current_park(&thread);
207    match prepare? {
208        ParkPrepare::Notified => Ok(CurrentParkStart::Notified),
209        ParkPrepare::Prepared(ticket) => Ok(CurrentParkStart::Prepared(PreparedCurrentPark {
210            thread,
211            ticket: Some(ticket),
212            system,
213        })),
214    }
215}
216
217/// Performs one bounded task-clockevent pass without allocation or callbacks.
218pub fn on_clock_event(
219    now: MonotonicInstant,
220    budget: usize,
221    scheduler_event: ClaimedSchedulerDeadlines,
222) -> Result<TaskClockEventOutcome, TaskError> {
223    let system = runtime_task_system()?;
224    let mut irq = RuntimeIrqGuard::enter();
225    let mut cpu = runtime_current_cpu_mut(&mut irq)?;
226    let periodic_tick = scheduler_event.runs_periodic_task_tick();
227    if periodic_tick && cpu.promote_lazy_reschedule() {
228        // Linux PREEMPT_RT promotes TIF_NEED_RESCHED_LAZY before invoking the
229        // current class's periodic task_tick hook through resched_curr(). The
230        // logical request and architecture preemption word are separate here,
231        // so publish both before IRQ return. A new lazy request created by the
232        // class hook remains lazy until the following promotion point.
233        let _self_serviced = task_runtime::publish_local_scheduler_work();
234    }
235    // A plain hard-timer callback may enqueue Fair work. Linux settles the
236    // current Fair entity before enqueue and wakeup-preemption comparisons;
237    // take the owner-rq accounting sample before running callbacks so the
238    // same ordering also holds for our shared hard-timer path.
239    let (charge, clock, current, task_tick_rq_observation) = match scheduler_event.accounting_kind()
240    {
241        ClockAccountingKind::RuntimeOnly => {
242            system.charge_current_until_with_clock(cpu.as_mut(), 0)?
243        }
244        ClockAccountingKind::SchedulerDeadline => {
245            system.clock_event_current_until_with_clock(cpu.as_mut(), 0)?
246        }
247        ClockAccountingKind::PeriodicTick => system.task_tick_current_until_with_clock(
248            cpu.as_mut(),
249            0,
250            scheduler_event.periodic_tick_ns(),
251        )?,
252        ClockAccountingKind::PeriodicTickWithSchedulerDeadline => system
253            .task_tick_and_clock_event_current_until_with_clock(
254                cpu.as_mut(),
255                0,
256                scheduler_event.periodic_tick_ns(),
257            )?,
258    };
259    let rt_period_rescheduled = system.service_rt_period(&cpu, now);
260    let hard = system.service_due_hard_timers(cpu.as_mut(), now, budget)?;
261    let batch = hard.soft();
262    let rq_observation =
263        match clock_event_rq_observation_plan(rt_period_rescheduled, hard.processed()) {
264            ClockEventRqObservationPlan::ReuseAccounted => cpu
265                .as_mut()
266                .scheduler_work_due_from_rq_observation(now, task_tick_rq_observation),
267            ClockEventRqObservationPlan::RefreshAndPublish => cpu.as_mut().scheduler_work_due(now),
268        };
269    let runtime_deadline = cpu.scheduler_runtime_deadline_for_rq_observation(rq_observation);
270    let update = cpu
271        .as_mut()
272        .next_scheduler_deadline_update_from_rq_observation(
273            rq_observation,
274            SchedulerDeadlineDerivationSource::ClockEvent,
275        )?;
276    Ok(TaskClockEventOutcome {
277        slice_expired: charge.slice_expired(),
278        deadline_overrun: charge.deadline_overrun(),
279        expired: hard.processed().saturating_add(batch.expired()),
280        update,
281        runtime_deadline: Some(runtime_deadline),
282        scheduler_tick: SchedulerTickStamp {
283            cpu: cpu.owner(),
284            thread: current,
285            observed_ns: clock.task().as_nanos(),
286        },
287    })
288}
289
290const fn clock_event_rq_observation_reusable(
291    rt_period_rescheduled: bool,
292    hard_timers_processed: usize,
293) -> bool {
294    !rt_period_rescheduled && hard_timers_processed == 0
295}
296
297#[derive(Clone, Copy, Debug, Eq, PartialEq)]
298enum ClockEventRqObservationPlan {
299    ReuseAccounted,
300    RefreshAndPublish,
301}
302
303const fn clock_event_rq_observation_plan(
304    rt_period_rescheduled: bool,
305    hard_timers_processed: usize,
306) -> ClockEventRqObservationPlan {
307    if clock_event_rq_observation_reusable(rt_period_rescheduled, hard_timers_processed) {
308        ClockEventRqObservationPlan::ReuseAccounted
309    } else {
310        // A hard-timer callback may make the current rq deadline due even
311        // when this physical edge did not claim the scheduler runtime timer.
312        // Linux's hrtick callback publishes reschedule work before it leaves
313        // the hard-timer queue; a fresh observation here must do the same.
314        ClockEventRqObservationPlan::RefreshAndPublish
315    }
316}
317
318/// Scheduler-owned deadlines claimed by one physical clockevent firing.
319#[derive(Clone, Copy, Debug, Eq, PartialEq)]
320pub struct ClaimedSchedulerDeadlines {
321    periodic_tick_ns: Option<NonZeroU64>,
322    scheduler_deadline_elapsed: bool,
323}
324
325#[derive(Clone, Copy, Debug, Eq, PartialEq)]
326enum ClockAccountingKind {
327    RuntimeOnly,
328    SchedulerDeadline,
329    PeriodicTick,
330    PeriodicTickWithSchedulerDeadline,
331}
332
333impl ClaimedSchedulerDeadlines {
334    /// Captures the periodic-tick duration and independent scheduler deadline
335    /// observed by the physical clockevent owner.
336    pub const fn new(
337        periodic_tick_ns: Option<NonZeroU64>,
338        scheduler_deadline_elapsed: bool,
339    ) -> Self {
340        Self {
341            periodic_tick_ns,
342            scheduler_deadline_elapsed,
343        }
344    }
345
346    const fn runs_periodic_task_tick(self) -> bool {
347        self.periodic_tick_ns.is_some()
348    }
349
350    fn periodic_tick_ns(self) -> u64 {
351        match self.periodic_tick_ns {
352            Some(tick_ns) => tick_ns.get(),
353            None => task_runtime::fatal_invariant(0x5251_1013, 0),
354        }
355    }
356
357    const fn accounting_kind(self) -> ClockAccountingKind {
358        match (
359            self.periodic_tick_ns.is_some(),
360            self.scheduler_deadline_elapsed,
361        ) {
362            (false, false) => ClockAccountingKind::RuntimeOnly,
363            (false, true) => ClockAccountingKind::SchedulerDeadline,
364            (true, false) => ClockAccountingKind::PeriodicTick,
365            (true, true) => ClockAccountingKind::PeriodicTickWithSchedulerDeadline,
366        }
367    }
368}
369
370/// Samples CPU time and publishes extension work for a periodic scheduler tick
371/// already accounted by [`on_clock_event`].
372///
373/// The opaque stamp binds this publication to the exact `rq->curr` and task
374/// clock sampled by the preceding owner-rq transaction. Physical clockevent
375/// sources therefore do not pass compatibility booleans into task deadline
376/// processing, and a delayed publication cannot silently target a new task.
377pub fn publish_scheduler_tick(
378    stamp: SchedulerTickStamp,
379    mode: SchedulerTickMode,
380    tick_ns: u64,
381) -> Result<(), TaskError> {
382    if tick_ns == 0 {
383        return Err(TaskError::InvalidConfiguration);
384    }
385    let system = runtime_task_system()?;
386    let mut irq = RuntimeIrqGuard::enter();
387    let cpu = runtime_current_cpu_mut(&mut irq)?;
388    if cpu.owner() != stamp.cpu {
389        return Err(TaskError::CpuOwnerMismatch {
390            expected: stamp.cpu.as_u32(),
391            actual: cpu.owner().as_u32(),
392        });
393    }
394    system.publish_current_scheduler_tick_work(&cpu, stamp.thread, stamp.observed_ns, mode, tick_ns)
395}
396
397pub(crate) fn commit_current_park(
398    current: &Arc<ThreadCore>,
399    ticket: &mut crate::thread::ParkTicket,
400) -> Result<CurrentParkDisposition, TaskError> {
401    let system = runtime_task_system()?;
402    commit_current_park_with_system(system, current, ticket)
403}
404
405fn commit_current_park_with_system(
406    system: &'static TaskSystem,
407    current: &Arc<ThreadCore>,
408    ticket: &mut crate::thread::ParkTicket,
409) -> Result<CurrentParkDisposition, TaskError> {
410    // The task scheduler frame is the authoritative Linux schedule-entry
411    // boundary. Its `Task` claim validates IRQ state, hard-IRQ context, and
412    // preemption depth while taking the baton; repeating the public blocking
413    // probe here would toggle IRQs twice for every park without adding a
414    // stronger guarantee.
415    let mut scheduler_frame = RuntimeSchedulerFrameGuard::enter(
416        RuntimeScheduleOrigin::Block,
417        RuntimeSchedulerEntry::Task,
418    )?;
419    let commit = {
420        let mut cpu = runtime_current_cpu_mut(&mut scheduler_frame)?;
421        // SAFETY: `scheduler_frame` owns the IRQ-off scheduler baton.
422        unsafe { system.commit_park_in_scheduler_frame(cpu.as_mut(), current, ticket)? }
423    };
424    match commit {
425        ParkCommit::Notified => Ok(CurrentParkDisposition::NotifiedBeforeBlock),
426        ParkCommit::Blocked(mut decision) => {
427            execute_switch_plan(&mut scheduler_frame, &mut decision);
428            Ok(CurrentParkDisposition::BlockedAndResumed)
429        }
430    }
431}
432
433pub(crate) fn cancel_current_park(
434    current: &ThreadCore,
435    ticket: &mut crate::thread::ParkTicket,
436) -> Result<(), TaskError> {
437    let mut irq = RuntimeIrqGuard::enter();
438    let mut cpu = runtime_current_cpu_mut(&mut irq)?;
439    runtime_task_system()?.cancel_current_park(cpu.as_mut(), current, ticket)
440}
441
442pub(crate) fn arm_current_park_deadline(
443    thread: &Arc<ThreadCore>,
444    ticket: &mut crate::thread::ParkTicket,
445    deadline: MonotonicDeadline,
446) -> Result<(), TaskError> {
447    let mut irq = RuntimeIrqGuard::enter();
448    let cpu = runtime_current_cpu_mut(&mut irq)?;
449    if ticket.thread() != thread.id()
450        || ticket.is_resolved()
451        || ticket.has_deadline()
452        || cpu.current() != Some(thread.id())
453    {
454        return Err(TaskError::StaleThreadId);
455    }
456    let owner = cpu.owner();
457    let (registration, update) = {
458        let mut deadline_base = cpu
459            .remote()
460            .lock_deadline_activity(DeadlineBaseGuardSource::Registration);
461        let non_timer = deadline_base.non_timer;
462        let kind = TaskDeadlineKind::park_timeout(ticket.generation());
463        let registration = if matches!(
464            thread.base_policy_snapshot(),
465            SchedulePolicy::Fifo { .. }
466                | SchedulePolicy::RoundRobin { .. }
467                | SchedulePolicy::Deadline(_)
468        ) {
469            deadline_base.queue.arm_hard_park(
470                thread.sleep_timer(),
471                deadline,
472                kind,
473                Arc::clone(thread),
474            )
475        } else {
476            deadline_base
477                .queue
478                .arm(thread.sleep_timer(), deadline, kind)
479        }
480        .map_err(|error| match error {
481            crate::time::queue::TaskDeadlineError::Capacity => TaskError::TimerCapacity,
482            crate::time::queue::TaskDeadlineError::GenerationExhausted
483            | crate::time::queue::TaskDeadlineError::KindMismatch => {
484                TaskError::InvalidConfiguration
485            }
486        })?;
487        let token = registration.token();
488        thread.register_sleep_timer(owner, token.generation());
489        let update = match CpuLocal::update_scheduler_deadline_registration_publication(
490            &mut deadline_base,
491            non_timer,
492        ) {
493            Ok(update) => update,
494            Err(error) => {
495                let removed = deadline_base.queue.cancel(&registration);
496                let completed = thread.complete_sleep_timer(token.generation());
497                if !removed || !completed {
498                    task_runtime::fatal_invariant(0x5444_0005, thread.id().as_u64() as usize);
499                }
500                return Err(error);
501            }
502        };
503        (registration, update)
504    };
505    task_runtime::publish_scheduler_deadline(update);
506    if ticket.attach_deadline(registration).is_err() {
507        task_runtime::fatal_invariant(0x5444_0002, thread.id().as_u64() as usize);
508    }
509    Ok(())
510}
511
512pub(crate) fn cancel_current_park_deadline(
513    thread: &ThreadCore,
514    ticket: &mut crate::thread::ParkTicket,
515) -> Result<bool, TaskError> {
516    if ticket.thread() != thread.id() {
517        return Err(TaskError::StaleThreadId);
518    }
519    let Some(token) = ticket.deadline().map(|registration| registration.token()) else {
520        return Ok(false);
521    };
522    let system = runtime_task_system()?;
523    let mut irq = RuntimeIrqGuard::enter();
524    let cpu = runtime_current_cpu_mut(&mut irq)?;
525    let actual = cpu.owner();
526    let Some(expected) = thread.sleep_timer_cpu_for(token.generation()) else {
527        // Expiration physically removes the queue entry and clears the core's
528        // matching generation before the owner thread resumes. Only that
529        // terminal state permits consuming the ticket without queue access.
530        if !ticket.clear_deadline(token) {
531            task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
532        }
533        return Ok(false);
534    };
535    if actual != expected {
536        let remote = system
537            .cpu_remote(expected)
538            .ok_or(TaskError::CpuOffline(expected.as_u32()))?;
539        let registration = ticket
540            .deadline()
541            .expect("the deadline registration remains owned until cancellation");
542        let (cancellation, expired) = {
543            let mut deadline_base =
544                remote.lock_deadline_activity(DeadlineBaseGuardSource::Registration);
545            let cancellation = deadline_base.queue.begin_cancel(registration);
546            let expired = if cancellation.is_none() {
547                deadline_base.cancel_expired_task_deadline(registration)
548            } else {
549                false
550            };
551            (cancellation, expired)
552        };
553        let cancelled = match (cancellation, expired) {
554            (Some(cancellation), _) => {
555                // Linux does not reprogram another CPU's clockevent when a
556                // remote hrtimer is removed. The stale edge is conservative;
557                // its owner recomputes the authoritative queue when it fires.
558                cancellation.commit();
559                true
560            }
561            (None, true) => false,
562            (None, false) if thread.sleep_timer_cpu_for(token.generation()).is_none() => {
563                if !ticket.clear_deadline(token) {
564                    task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
565                }
566                return Ok(false);
567            }
568            (None, false) => {
569                task_runtime::fatal_invariant(0x5444_0006, thread.id().as_u64() as usize)
570            }
571        };
572        if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token) {
573            task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
574        }
575        return Ok(cancelled);
576    }
577    let (cancellation, update) = {
578        let registration = ticket
579            .deadline()
580            .expect("the deadline registration remains owned until cancellation");
581        let mut deadline_base = cpu
582            .remote()
583            .lock_deadline_activity(DeadlineBaseGuardSource::Registration);
584        let non_timer = deadline_base.non_timer;
585        let cancellation = deadline_base.queue.begin_cancel(registration);
586        let expired = if cancellation.is_none() {
587            deadline_base.cancel_expired_task_deadline(registration)
588        } else {
589            false
590        };
591        let cancellation = match (cancellation, expired) {
592            (Some(cancellation), _) => cancellation,
593            (None, true) => {
594                if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token)
595                {
596                    task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
597                }
598                return Ok(false);
599            }
600            (None, false) if thread.sleep_timer_cpu_for(token.generation()).is_none() => {
601                if !ticket.clear_deadline(token) {
602                    task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
603                }
604                return Ok(false);
605            }
606            (None, false) => {
607                task_runtime::fatal_invariant(0x5444_0006, thread.id().as_u64() as usize);
608            }
609        };
610        let update = match CpuLocal::update_scheduler_deadline_registration_publication(
611            &mut deadline_base,
612            non_timer,
613        ) {
614            Ok(update) => update,
615            Err(error) => {
616                cancellation.rollback(&mut deadline_base.queue);
617                return Err(error);
618            }
619        };
620        (cancellation, update)
621    };
622    task_runtime::publish_scheduler_deadline(update);
623    cancellation.commit();
624    if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token) {
625        task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
626    }
627    Ok(true)
628}
629
630/// Bounded task-clockevent result consumed by the runtime clockevent owner.
631#[derive(Clone, Copy, Debug, Eq, PartialEq)]
632pub struct TaskClockEventOutcome {
633    slice_expired: bool,
634    deadline_overrun: bool,
635    expired: usize,
636    update: crate::runtime::cpu::SchedulerDeadlineUpdate,
637    runtime_deadline: Option<crate::runtime::cpu::SchedulerRuntimeDeadline>,
638    scheduler_tick: SchedulerTickStamp,
639}
640
641/// Opaque owner-rq sample required to publish one periodic scheduler tick.
642#[derive(Clone, Copy, Debug, Eq, PartialEq)]
643pub struct SchedulerTickStamp {
644    cpu: CpuId,
645    thread: ThreadId,
646    observed_ns: u64,
647}
648
649impl TaskClockEventOutcome {
650    /// Returns whether the current scheduling slice or budget expired.
651    pub const fn slice_expired(self) -> bool {
652        self.slice_expired
653    }
654    /// Returns whether the current Deadline reservation exhausted its CBS budget.
655    pub const fn deadline_overrun(self) -> bool {
656        self.deadline_overrun
657    }
658    /// Returns the number of timer events claimed by this bounded IRQ pass.
659    pub const fn expired(self) -> usize {
660        self.expired
661    }
662    /// Returns the complete generation-ordered task-deadline publication.
663    pub const fn update(self) -> crate::runtime::cpu::SchedulerDeadlineUpdate {
664        self.update
665    }
666    /// Returns an owner-local class-runtime update when rq state was sampled.
667    pub const fn runtime_deadline(self) -> Option<crate::runtime::cpu::SchedulerRuntimeDeadline> {
668        self.runtime_deadline
669    }
670    /// Returns the rq-bound stamp consumed when this physical edge was also a
671    /// periodic scheduler tick.
672    pub const fn scheduler_tick_stamp(self) -> SchedulerTickStamp {
673        self.scheduler_tick
674    }
675    /// Returns the next finite task-owned deadline.
676    pub const fn next_deadline(self) -> Option<MonotonicDeadline> {
677        self.update.deadline()
678    }
679}
680
681#[cfg(test)]
682mod tests {
683    use core::num::NonZeroU64;
684
685    use super::{
686        ClaimedSchedulerDeadlines, ClockAccountingKind, ClockEventRqObservationPlan,
687        clock_event_rq_observation_plan, clock_event_rq_observation_reusable,
688    };
689
690    const _: () = assert!(matches!(
691        clock_event_rq_observation_plan(false, 1),
692        ClockEventRqObservationPlan::RefreshAndPublish
693    ));
694
695    #[test]
696    fn only_periodic_clock_events_run_the_scheduler_tick() {
697        let tick_ns = NonZeroU64::new(10).unwrap();
698
699        assert!(!ClaimedSchedulerDeadlines::new(None, false).runs_periodic_task_tick());
700        assert!(!ClaimedSchedulerDeadlines::new(None, true).runs_periodic_task_tick());
701        assert!(ClaimedSchedulerDeadlines::new(Some(tick_ns), false).runs_periodic_task_tick());
702        assert!(ClaimedSchedulerDeadlines::new(Some(tick_ns), true).runs_periodic_task_tick());
703    }
704
705    #[test]
706    fn unrelated_physical_clockevent_only_accounts_runtime() {
707        let tick_ns = NonZeroU64::new(10).unwrap();
708
709        assert_eq!(
710            ClaimedSchedulerDeadlines::new(None, false).accounting_kind(),
711            ClockAccountingKind::RuntimeOnly
712        );
713        assert_eq!(
714            ClaimedSchedulerDeadlines::new(None, true).accounting_kind(),
715            ClockAccountingKind::SchedulerDeadline
716        );
717        assert_eq!(
718            ClaimedSchedulerDeadlines::new(Some(tick_ns), false).accounting_kind(),
719            ClockAccountingKind::PeriodicTick
720        );
721        assert_eq!(
722            ClaimedSchedulerDeadlines::new(Some(tick_ns), true).accounting_kind(),
723            ClockAccountingKind::PeriodicTickWithSchedulerDeadline
724        );
725    }
726
727    #[test]
728    fn linux_common_tick_reuses_the_task_tick_rq_observation() {
729        assert!(clock_event_rq_observation_reusable(false, 0));
730        assert!(!clock_event_rq_observation_reusable(true, 0));
731        assert!(!clock_event_rq_observation_reusable(false, 1));
732    }
733
734    #[test]
735    fn plain_hard_timer_rechecks_and_publishes_due_scheduler_work() {
736        assert_eq!(
737            clock_event_rq_observation_plan(false, 1),
738            ClockEventRqObservationPlan::RefreshAndPublish
739        );
740    }
741}