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(SchedulerDeadlineDerivationSource::ClockEvent)?;
273    Ok(TaskClockEventOutcome {
274        slice_expired: charge.slice_expired(),
275        deadline_overrun: charge.deadline_overrun(),
276        expired: hard.processed().saturating_add(batch.expired()),
277        update,
278        runtime_deadline: Some(runtime_deadline),
279        scheduler_tick: SchedulerTickStamp {
280            cpu: cpu.owner(),
281            thread: current,
282            observed_ns: clock.task().as_nanos(),
283        },
284    })
285}
286
287const fn clock_event_rq_observation_reusable(
288    rt_period_rescheduled: bool,
289    hard_timers_processed: usize,
290) -> bool {
291    !rt_period_rescheduled && hard_timers_processed == 0
292}
293
294#[derive(Clone, Copy, Debug, Eq, PartialEq)]
295enum ClockEventRqObservationPlan {
296    ReuseAccounted,
297    RefreshAndPublish,
298}
299
300const fn clock_event_rq_observation_plan(
301    rt_period_rescheduled: bool,
302    hard_timers_processed: usize,
303) -> ClockEventRqObservationPlan {
304    if clock_event_rq_observation_reusable(rt_period_rescheduled, hard_timers_processed) {
305        ClockEventRqObservationPlan::ReuseAccounted
306    } else {
307        // A hard-timer callback may make the current rq deadline due even
308        // when this physical edge did not claim the scheduler runtime timer.
309        // Linux's hrtick callback publishes reschedule work before it leaves
310        // the hard-timer queue; a fresh observation here must do the same.
311        ClockEventRqObservationPlan::RefreshAndPublish
312    }
313}
314
315/// Scheduler-owned deadlines claimed by one physical clockevent firing.
316#[derive(Clone, Copy, Debug, Eq, PartialEq)]
317pub struct ClaimedSchedulerDeadlines {
318    periodic_tick_ns: Option<NonZeroU64>,
319    scheduler_deadline_elapsed: bool,
320}
321
322#[derive(Clone, Copy, Debug, Eq, PartialEq)]
323enum ClockAccountingKind {
324    RuntimeOnly,
325    SchedulerDeadline,
326    PeriodicTick,
327    PeriodicTickWithSchedulerDeadline,
328}
329
330impl ClaimedSchedulerDeadlines {
331    /// Captures the periodic-tick duration and independent scheduler deadline
332    /// observed by the physical clockevent owner.
333    pub const fn new(
334        periodic_tick_ns: Option<NonZeroU64>,
335        scheduler_deadline_elapsed: bool,
336    ) -> Self {
337        Self {
338            periodic_tick_ns,
339            scheduler_deadline_elapsed,
340        }
341    }
342
343    const fn runs_periodic_task_tick(self) -> bool {
344        self.periodic_tick_ns.is_some()
345    }
346
347    fn periodic_tick_ns(self) -> u64 {
348        match self.periodic_tick_ns {
349            Some(tick_ns) => tick_ns.get(),
350            None => task_runtime::fatal_invariant(0x5251_1013, 0),
351        }
352    }
353
354    const fn accounting_kind(self) -> ClockAccountingKind {
355        match (
356            self.periodic_tick_ns.is_some(),
357            self.scheduler_deadline_elapsed,
358        ) {
359            (false, false) => ClockAccountingKind::RuntimeOnly,
360            (false, true) => ClockAccountingKind::SchedulerDeadline,
361            (true, false) => ClockAccountingKind::PeriodicTick,
362            (true, true) => ClockAccountingKind::PeriodicTickWithSchedulerDeadline,
363        }
364    }
365}
366
367/// Samples CPU time and publishes extension work for a periodic scheduler tick
368/// already accounted by [`on_clock_event`].
369///
370/// The opaque stamp binds this publication to the exact `rq->curr` and task
371/// clock sampled by the preceding owner-rq transaction. Physical clockevent
372/// sources therefore do not pass compatibility booleans into task deadline
373/// processing, and a delayed publication cannot silently target a new task.
374pub fn publish_scheduler_tick(
375    stamp: SchedulerTickStamp,
376    mode: SchedulerTickMode,
377    tick_ns: u64,
378) -> Result<(), TaskError> {
379    if tick_ns == 0 {
380        return Err(TaskError::InvalidConfiguration);
381    }
382    let system = runtime_task_system()?;
383    let mut irq = RuntimeIrqGuard::enter();
384    let cpu = runtime_current_cpu_mut(&mut irq)?;
385    if cpu.owner() != stamp.cpu {
386        return Err(TaskError::CpuOwnerMismatch {
387            expected: stamp.cpu.as_u32(),
388            actual: cpu.owner().as_u32(),
389        });
390    }
391    system.publish_current_scheduler_tick_work(&cpu, stamp.thread, stamp.observed_ns, mode, tick_ns)
392}
393
394pub(crate) fn commit_current_park(
395    current: &Arc<ThreadCore>,
396    ticket: &mut crate::thread::ParkTicket,
397) -> Result<CurrentParkDisposition, TaskError> {
398    let system = runtime_task_system()?;
399    commit_current_park_with_system(system, current, ticket)
400}
401
402fn commit_current_park_with_system(
403    system: &'static TaskSystem,
404    current: &Arc<ThreadCore>,
405    ticket: &mut crate::thread::ParkTicket,
406) -> Result<CurrentParkDisposition, TaskError> {
407    // The task scheduler frame is the authoritative Linux schedule-entry
408    // boundary. Its `Task` claim validates IRQ state, hard-IRQ context, and
409    // preemption depth while taking the baton; repeating the public blocking
410    // probe here would toggle IRQs twice for every park without adding a
411    // stronger guarantee.
412    let mut scheduler_frame = RuntimeSchedulerFrameGuard::enter(
413        RuntimeScheduleOrigin::Block,
414        RuntimeSchedulerEntry::Task,
415    )?;
416    let commit = {
417        let mut cpu = runtime_current_cpu_mut(&mut scheduler_frame)?;
418        // SAFETY: `scheduler_frame` owns the IRQ-off scheduler baton.
419        unsafe { system.commit_park_in_scheduler_frame(cpu.as_mut(), current, ticket)? }
420    };
421    match commit {
422        ParkCommit::Notified => Ok(CurrentParkDisposition::NotifiedBeforeBlock),
423        ParkCommit::Blocked(mut decision) => {
424            execute_switch_plan(&mut scheduler_frame, &mut decision);
425            Ok(CurrentParkDisposition::BlockedAndResumed)
426        }
427    }
428}
429
430pub(crate) fn cancel_current_park(
431    current: &ThreadCore,
432    ticket: &mut crate::thread::ParkTicket,
433) -> Result<(), TaskError> {
434    let mut irq = RuntimeIrqGuard::enter();
435    let mut cpu = runtime_current_cpu_mut(&mut irq)?;
436    runtime_task_system()?.cancel_current_park(cpu.as_mut(), current, ticket)
437}
438
439pub(crate) fn arm_current_park_deadline(
440    thread: &Arc<ThreadCore>,
441    ticket: &mut crate::thread::ParkTicket,
442    deadline: MonotonicDeadline,
443) -> Result<(), TaskError> {
444    let mut irq = RuntimeIrqGuard::enter();
445    let cpu = runtime_current_cpu_mut(&mut irq)?;
446    if ticket.thread() != thread.id()
447        || ticket.is_resolved()
448        || ticket.has_deadline()
449        || cpu.current() != Some(thread.id())
450    {
451        return Err(TaskError::StaleThreadId);
452    }
453    let owner = cpu.owner();
454    let (registration, update) = {
455        let mut deadline_base = cpu
456            .remote()
457            .lock_deadline_activity(DeadlineBaseGuardSource::Registration);
458        let non_timer = deadline_base.non_timer;
459        let kind = TaskDeadlineKind::park_timeout(ticket.generation());
460        let registration = if matches!(
461            thread.base_policy_snapshot(),
462            SchedulePolicy::Fifo { .. }
463                | SchedulePolicy::RoundRobin { .. }
464                | SchedulePolicy::Deadline(_)
465        ) {
466            deadline_base.queue.arm_hard_park(
467                thread.sleep_timer(),
468                deadline,
469                kind,
470                Arc::clone(thread),
471            )
472        } else {
473            deadline_base
474                .queue
475                .arm(thread.sleep_timer(), deadline, kind)
476        }
477        .map_err(|error| match error {
478            crate::time::queue::TaskDeadlineError::Capacity => TaskError::TimerCapacity,
479            crate::time::queue::TaskDeadlineError::GenerationExhausted
480            | crate::time::queue::TaskDeadlineError::KindMismatch => {
481                TaskError::InvalidConfiguration
482            }
483        })?;
484        let token = registration.token();
485        thread.register_sleep_timer(owner, token.generation());
486        let update = match CpuLocal::update_scheduler_deadline_registration_publication(
487            &mut deadline_base,
488            non_timer,
489        ) {
490            Ok(update) => update,
491            Err(error) => {
492                let removed = deadline_base.queue.cancel(&registration);
493                let completed = thread.complete_sleep_timer(token.generation());
494                if !removed || !completed {
495                    task_runtime::fatal_invariant(0x5444_0005, thread.id().as_u64() as usize);
496                }
497                return Err(error);
498            }
499        };
500        (registration, update)
501    };
502    task_runtime::publish_scheduler_deadline(update);
503    if ticket.attach_deadline(registration).is_err() {
504        task_runtime::fatal_invariant(0x5444_0002, thread.id().as_u64() as usize);
505    }
506    Ok(())
507}
508
509pub(crate) fn cancel_current_park_deadline(
510    thread: &ThreadCore,
511    ticket: &mut crate::thread::ParkTicket,
512) -> Result<bool, TaskError> {
513    if ticket.thread() != thread.id() {
514        return Err(TaskError::StaleThreadId);
515    }
516    let Some(token) = ticket.deadline().map(|registration| registration.token()) else {
517        return Ok(false);
518    };
519    let system = runtime_task_system()?;
520    let mut irq = RuntimeIrqGuard::enter();
521    let cpu = runtime_current_cpu_mut(&mut irq)?;
522    let actual = cpu.owner();
523    let Some(expected) = thread.sleep_timer_cpu_for(token.generation()) else {
524        // Expiration physically removes the queue entry and clears the core's
525        // matching generation before the owner thread resumes. Only that
526        // terminal state permits consuming the ticket without queue access.
527        if !ticket.clear_deadline(token) {
528            task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
529        }
530        return Ok(false);
531    };
532    if actual != expected {
533        let remote = system
534            .cpu_remote(expected)
535            .ok_or(TaskError::CpuOffline(expected.as_u32()))?;
536        let registration = ticket
537            .deadline()
538            .expect("the deadline registration remains owned until cancellation");
539        let (cancellation, expired) = {
540            let mut deadline_base =
541                remote.lock_deadline_activity(DeadlineBaseGuardSource::Registration);
542            let cancellation = deadline_base.queue.begin_cancel(registration);
543            let expired = if cancellation.is_none() {
544                deadline_base.cancel_expired_task_deadline(registration)
545            } else {
546                false
547            };
548            (cancellation, expired)
549        };
550        let cancelled = match (cancellation, expired) {
551            (Some(cancellation), _) => {
552                // Linux does not reprogram another CPU's clockevent when a
553                // remote hrtimer is removed. The stale edge is conservative;
554                // its owner recomputes the authoritative queue when it fires.
555                cancellation.commit();
556                true
557            }
558            (None, true) => false,
559            (None, false) if thread.sleep_timer_cpu_for(token.generation()).is_none() => {
560                if !ticket.clear_deadline(token) {
561                    task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
562                }
563                return Ok(false);
564            }
565            (None, false) => {
566                task_runtime::fatal_invariant(0x5444_0006, thread.id().as_u64() as usize)
567            }
568        };
569        if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token) {
570            task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
571        }
572        return Ok(cancelled);
573    }
574    let (cancellation, update) = {
575        let registration = ticket
576            .deadline()
577            .expect("the deadline registration remains owned until cancellation");
578        let mut deadline_base = cpu
579            .remote()
580            .lock_deadline_activity(DeadlineBaseGuardSource::Registration);
581        let non_timer = deadline_base.non_timer;
582        let cancellation = deadline_base.queue.begin_cancel(registration);
583        let expired = if cancellation.is_none() {
584            deadline_base.cancel_expired_task_deadline(registration)
585        } else {
586            false
587        };
588        let cancellation = match (cancellation, expired) {
589            (Some(cancellation), _) => cancellation,
590            (None, true) => {
591                if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token)
592                {
593                    task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
594                }
595                return Ok(false);
596            }
597            (None, false) if thread.sleep_timer_cpu_for(token.generation()).is_none() => {
598                if !ticket.clear_deadline(token) {
599                    task_runtime::fatal_invariant(0x5444_0003, thread.id().as_u64() as usize);
600                }
601                return Ok(false);
602            }
603            (None, false) => {
604                task_runtime::fatal_invariant(0x5444_0006, thread.id().as_u64() as usize);
605            }
606        };
607        let update = match CpuLocal::update_scheduler_deadline_registration_publication(
608            &mut deadline_base,
609            non_timer,
610        ) {
611            Ok(update) => update,
612            Err(error) => {
613                cancellation.rollback(&mut deadline_base.queue);
614                return Err(error);
615            }
616        };
617        (cancellation, update)
618    };
619    task_runtime::publish_scheduler_deadline(update);
620    cancellation.commit();
621    if !thread.complete_sleep_timer(token.generation()) || !ticket.clear_deadline(token) {
622        task_runtime::fatal_invariant(0x5444_0004, thread.id().as_u64() as usize);
623    }
624    Ok(true)
625}
626
627/// Bounded task-clockevent result consumed by the runtime clockevent owner.
628#[derive(Clone, Copy, Debug, Eq, PartialEq)]
629pub struct TaskClockEventOutcome {
630    slice_expired: bool,
631    deadline_overrun: bool,
632    expired: usize,
633    update: crate::runtime::cpu::SchedulerDeadlineUpdate,
634    runtime_deadline: Option<crate::runtime::cpu::SchedulerRuntimeDeadline>,
635    scheduler_tick: SchedulerTickStamp,
636}
637
638/// Opaque owner-rq sample required to publish one periodic scheduler tick.
639#[derive(Clone, Copy, Debug, Eq, PartialEq)]
640pub struct SchedulerTickStamp {
641    cpu: CpuId,
642    thread: ThreadId,
643    observed_ns: u64,
644}
645
646impl TaskClockEventOutcome {
647    /// Returns whether the current scheduling slice or budget expired.
648    pub const fn slice_expired(self) -> bool {
649        self.slice_expired
650    }
651    /// Returns whether the current Deadline reservation exhausted its CBS budget.
652    pub const fn deadline_overrun(self) -> bool {
653        self.deadline_overrun
654    }
655    /// Returns the number of timer events claimed by this bounded IRQ pass.
656    pub const fn expired(self) -> usize {
657        self.expired
658    }
659    /// Returns the complete generation-ordered task-deadline publication.
660    pub const fn update(self) -> crate::runtime::cpu::SchedulerDeadlineUpdate {
661        self.update
662    }
663    /// Returns an owner-local class-runtime update when rq state was sampled.
664    pub const fn runtime_deadline(self) -> Option<crate::runtime::cpu::SchedulerRuntimeDeadline> {
665        self.runtime_deadline
666    }
667    /// Returns the rq-bound stamp consumed when this physical edge was also a
668    /// periodic scheduler tick.
669    pub const fn scheduler_tick_stamp(self) -> SchedulerTickStamp {
670        self.scheduler_tick
671    }
672    /// Returns the next finite task-owned deadline.
673    pub const fn next_deadline(self) -> Option<MonotonicDeadline> {
674        self.update.deadline()
675    }
676}
677
678#[cfg(test)]
679mod tests {
680    use core::num::NonZeroU64;
681
682    use super::{
683        ClaimedSchedulerDeadlines, ClockAccountingKind, ClockEventRqObservationPlan,
684        clock_event_rq_observation_plan, clock_event_rq_observation_reusable,
685    };
686
687    const _: () = assert!(matches!(
688        clock_event_rq_observation_plan(false, 1),
689        ClockEventRqObservationPlan::RefreshAndPublish
690    ));
691
692    #[test]
693    fn only_periodic_clock_events_run_the_scheduler_tick() {
694        let tick_ns = NonZeroU64::new(10).unwrap();
695
696        assert!(!ClaimedSchedulerDeadlines::new(None, false).runs_periodic_task_tick());
697        assert!(!ClaimedSchedulerDeadlines::new(None, true).runs_periodic_task_tick());
698        assert!(ClaimedSchedulerDeadlines::new(Some(tick_ns), false).runs_periodic_task_tick());
699        assert!(ClaimedSchedulerDeadlines::new(Some(tick_ns), true).runs_periodic_task_tick());
700    }
701
702    #[test]
703    fn unrelated_physical_clockevent_only_accounts_runtime() {
704        let tick_ns = NonZeroU64::new(10).unwrap();
705
706        assert_eq!(
707            ClaimedSchedulerDeadlines::new(None, false).accounting_kind(),
708            ClockAccountingKind::RuntimeOnly
709        );
710        assert_eq!(
711            ClaimedSchedulerDeadlines::new(None, true).accounting_kind(),
712            ClockAccountingKind::SchedulerDeadline
713        );
714        assert_eq!(
715            ClaimedSchedulerDeadlines::new(Some(tick_ns), false).accounting_kind(),
716            ClockAccountingKind::PeriodicTick
717        );
718        assert_eq!(
719            ClaimedSchedulerDeadlines::new(Some(tick_ns), true).accounting_kind(),
720            ClockAccountingKind::PeriodicTickWithSchedulerDeadline
721        );
722    }
723
724    #[test]
725    fn linux_common_tick_reuses_the_task_tick_rq_observation() {
726        assert!(clock_event_rq_observation_reusable(false, 0));
727        assert!(!clock_event_rq_observation_reusable(true, 0));
728        assert!(!clock_event_rq_observation_reusable(false, 1));
729    }
730
731    #[test]
732    fn plain_hard_timer_rechecks_and_publishes_due_scheduler_work() {
733        assert_eq!(
734            clock_event_rq_observation_plan(false, 1),
735            ClockEventRqObservationPlan::RefreshAndPublish
736        );
737    }
738}