starnix_core/task/scheduler/
manager.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::task::{RoleOverrides, Task};
6use fidl::HandleBased;
7use fidl_fuchsia_scheduler::{
8    RoleManagerMarker, RoleManagerSetRoleRequest, RoleManagerSynchronousProxy, RoleName, RoleTarget,
9};
10use fuchsia_component::client::connect_to_protocol_sync;
11use starnix_logging::{impossible_error, log_debug, log_warn, track_stub};
12use starnix_uapi::errors::Errno;
13use starnix_uapi::{
14    SCHED_BATCH, SCHED_DEADLINE, SCHED_FIFO, SCHED_IDLE, SCHED_NORMAL, SCHED_RESET_ON_FORK,
15    SCHED_RR, errno, error, sched_param,
16};
17
18pub struct SchedulerManager {
19    role_manager: Option<RoleManagerSynchronousProxy>,
20    role_overrides: RoleOverrides,
21}
22
23impl SchedulerManager {
24    /// Create a new SchedulerManager which will apply any provided `role_overrides` before
25    /// computing a role name based on a Task's scheduler state.
26    pub fn new(role_overrides: RoleOverrides) -> SchedulerManager {
27        let role_manager = fuchsia_runtime::with_thread_self(|thread| {
28            let role_manager = connect_to_protocol_sync::<RoleManagerMarker>().unwrap();
29            if let Err(e) = Self::set_thread_role_inner(
30                &role_manager,
31                thread,
32                SchedulerState::default().role_name(),
33            ) {
34                log_debug!("Setting thread role failed ({e:?}), will not set thread priority.");
35                None
36            } else {
37                log_debug!("Thread role set successfully, scheduler manager initialized.");
38                Some(role_manager)
39            }
40        });
41
42        SchedulerManager { role_manager, role_overrides }
43    }
44
45    /// Create a new empty SchedulerManager for testing.
46    pub fn empty_for_tests() -> Self {
47        Self { role_manager: None, role_overrides: RoleOverrides::new().build().unwrap() }
48    }
49
50    /// Return the currently active role name for this task. Requires read access to `task`'s state,
51    /// should only be called by code which is not already modifying the provided `task`.
52    pub fn role_name(&self, task: &Task) -> Result<&str, Errno> {
53        let scheduler_state = task.read().scheduler_state;
54        self.role_name_inner(task, scheduler_state)
55    }
56
57    fn role_name_inner(&self, task: &Task, scheduler_state: SchedulerState) -> Result<&str, Errno> {
58        let process_name = task
59            .thread_group()
60            .read()
61            .get_task(task.thread_group().leader)
62            .ok_or_else(|| errno!(EINVAL))?
63            .command();
64        let thread_name = task.command();
65        if let Some(name) = self.role_overrides.get_role_name(&process_name, &thread_name) {
66            return Ok(name);
67        }
68        Ok(scheduler_state.role_name())
69    }
70
71    /// Give the provided `task`'s Zircon thread a role.
72    ///
73    /// Requires passing the current `SchedulerState` so that this can be
74    /// performed without touching `task`'s state lock.
75    pub fn set_thread_role(
76        &self,
77        task: &Task,
78        scheduler_state: SchedulerState,
79    ) -> Result<(), Errno> {
80        let Some(role_manager) = self.role_manager.as_ref() else {
81            log_debug!("no role manager for setting role");
82            return Ok(());
83        };
84
85        let role_name = self.role_name_inner(task, scheduler_state)?;
86        let thread = task.thread.read();
87        let Some(thread) = thread.as_ref() else {
88            log_debug!("thread role update requested for task without thread, skipping");
89            return Ok(());
90        };
91        Self::set_thread_role_inner(role_manager, thread, role_name)
92    }
93
94    fn set_thread_role_inner(
95        role_manager: &RoleManagerSynchronousProxy,
96        thread: &zx::Thread,
97        role_name: &str,
98    ) -> Result<(), Errno> {
99        log_debug!(role_name; "setting thread role");
100
101        let thread = thread.duplicate_handle(zx::Rights::SAME_RIGHTS).map_err(impossible_error)?;
102        let request = RoleManagerSetRoleRequest {
103            target: Some(RoleTarget::Thread(thread)),
104            role: Some(RoleName { role: role_name.to_string() }),
105            ..Default::default()
106        };
107        let _ = role_manager.set_role(request, zx::MonotonicInstant::INFINITE).map_err(|err| {
108            log_warn!(err:%; "Unable to set thread role.");
109            errno!(EINVAL)
110        })?;
111        Ok(())
112    }
113}
114
115/// The task normal priority, used for favoring or disfavoring a task running
116/// with some non-real-time scheduling policies. Ranges from -20 to +19 in
117/// "user-space" representation and +1 to +40 in "kernel-internal"
118/// representation. See "The nice value" at sched(7) for full specification.
119#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
120pub struct NormalPriority {
121    /// 1 (weakest) to 40 (strongest) (in "kernel-internal" representation),
122    /// from setpriority()
123    value: u8,
124}
125
126impl NormalPriority {
127    const MIN_VALUE: u8 = 1;
128    const DEFAULT_VALUE: u8 = 20;
129    const MAX_VALUE: u8 = 40;
130
131    /// Creates a normal priority from a value to be interpreted according
132    /// to the "user-space nice" (-20..=19) scale, clamping values outside
133    /// that scale.
134    ///
135    /// It would be strange for this to be called from anywhere outside of
136    /// the setpriority system call.
137    pub(crate) fn from_setpriority_syscall(user_nice: i32) -> Self {
138        Self {
139            value: (Self::DEFAULT_VALUE as i32)
140                .saturating_sub(user_nice)
141                .clamp(Self::MIN_VALUE as i32, Self::MAX_VALUE as i32) as u8,
142        }
143    }
144
145    /// Creates a normal priority from a value to be interpreted according
146    /// to the "user-space nice" (-20..=19) scale, rejecting values outside
147    /// that scale.
148    ///
149    /// It would be strange for this to be called from anywhere outside of
150    /// our Binder implementation.
151    pub fn from_binder(user_nice: i8) -> Result<Self, Errno> {
152        let value = (Self::DEFAULT_VALUE as i8).saturating_sub(user_nice);
153        if value < (Self::MIN_VALUE as i8) || value > (Self::MAX_VALUE as i8) {
154            return error!(EINVAL);
155        }
156        Ok(Self { value: u8::try_from(value).expect("normal priority should fit in a u8") })
157    }
158
159    /// Returns this normal priority's integer representation according
160    /// to the "user-space nice" (-20..=19) scale.
161    pub fn as_nice(&self) -> i8 {
162        (Self::DEFAULT_VALUE as i8) - (self.value as i8)
163    }
164
165    /// Returns this normal priority's integer representation according
166    /// to the "kernel space nice" (1..=40) scale.
167    pub(crate) fn raw_priority(&self) -> u8 {
168        self.value
169    }
170
171    /// Returns whether this normal priority exceeds the given limit.
172    pub(crate) fn exceeds(&self, limit: u64) -> bool {
173        limit < (self.value as u64)
174    }
175}
176
177impl std::default::Default for NormalPriority {
178    fn default() -> Self {
179        Self { value: Self::DEFAULT_VALUE }
180    }
181}
182
183/// The task real-time priority, used for favoring or disfavoring a task
184/// running with real-time scheduling policies. See "Scheduling policies"
185/// at sched(7) for full specification.
186#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
187pub(crate) struct RealtimePriority {
188    /// 1 (weakest) to 99 (strongest), from sched_setscheduler() and
189    /// sched_setparam(). Only meaningfully used for Fifo and
190    /// Round-Robin; set to 0 for other policies.
191    value: u8,
192}
193
194impl RealtimePriority {
195    const NON_REAL_TIME_VALUE: u8 = 0;
196    const MIN_VALUE: u8 = 1;
197    const MAX_VALUE: u8 = 99;
198
199    const NON_REAL_TIME: RealtimePriority = RealtimePriority { value: Self::NON_REAL_TIME_VALUE };
200
201    pub(crate) fn exceeds(&self, limit: u64) -> bool {
202        limit < (self.value as u64)
203    }
204}
205
206/// The scheduling policies described in "Scheduling policies" at sched(7).
207#[derive(Clone, Copy, Debug, Eq, PartialEq)]
208pub(crate) enum SchedulingPolicy {
209    Normal,
210    Batch,
211    Idle,
212    Fifo,
213    RoundRobin,
214}
215
216impl SchedulingPolicy {
217    fn realtime_priority_min(&self) -> u8 {
218        match self {
219            Self::Normal | Self::Batch | Self::Idle => RealtimePriority::NON_REAL_TIME_VALUE,
220            Self::Fifo | Self::RoundRobin => RealtimePriority::MIN_VALUE,
221        }
222    }
223
224    fn realtime_priority_max(&self) -> u8 {
225        match self {
226            Self::Normal | Self::Batch | Self::Idle => RealtimePriority::NON_REAL_TIME_VALUE,
227            Self::Fifo | Self::RoundRobin => RealtimePriority::MAX_VALUE,
228        }
229    }
230
231    pub(crate) fn realtime_priority_from(&self, priority: i32) -> Result<RealtimePriority, Errno> {
232        let priority = u8::try_from(priority).map_err(|_| errno!(EINVAL))?;
233        if priority < self.realtime_priority_min() || priority > self.realtime_priority_max() {
234            return error!(EINVAL);
235        }
236        Ok(RealtimePriority { value: priority })
237    }
238}
239
240impl TryFrom<u32> for SchedulingPolicy {
241    type Error = Errno;
242
243    fn try_from(value: u32) -> Result<Self, Errno> {
244        Ok(match value {
245            SCHED_NORMAL => Self::Normal,
246            SCHED_BATCH => Self::Batch,
247            SCHED_IDLE => Self::Idle,
248            SCHED_FIFO => Self::Fifo,
249            SCHED_RR => Self::RoundRobin,
250            _ => {
251                return error!(EINVAL);
252            }
253        })
254    }
255}
256
257#[derive(Clone, Copy, Debug, Eq, PartialEq)]
258pub struct SchedulerState {
259    pub(crate) policy: SchedulingPolicy,
260    /// Although nice is only used for Normal and Batch, normal priority
261    /// ("nice") is still maintained, observable, and alterable when a
262    /// task is using Idle, Fifo, and RoundRobin.
263    pub(crate) normal_priority: NormalPriority,
264    /// 1 (weakest) to 99 (strongest), from sched_setscheduler() and
265    /// sched_setparam(). Only used for Fifo and Round-Robin.
266    pub(crate) realtime_priority: RealtimePriority,
267    pub(crate) reset_on_fork: bool,
268}
269
270impl SchedulerState {
271    pub fn is_default(&self) -> bool {
272        self == &Self::default()
273    }
274
275    /// Create a policy according to the "sched_policy" and "priority" bits of
276    /// a flat_binder_object_flags bitmask (see uapi/linux/android/binder.h).
277    ///
278    /// It would be very strange for this to need to be called anywhere outside
279    /// of our Binder implementation.
280    pub fn from_binder(policy: u8, priority_or_nice: u8) -> Result<Self, Errno> {
281        let (policy, normal_priority, realtime_priority) = match policy as u32 {
282            SCHED_NORMAL => (
283                SchedulingPolicy::Normal,
284                NormalPriority::from_binder(priority_or_nice as i8)?,
285                RealtimePriority::NON_REAL_TIME,
286            ),
287            SCHED_BATCH => (
288                SchedulingPolicy::Batch,
289                NormalPriority::from_binder(priority_or_nice as i8)?,
290                RealtimePriority::NON_REAL_TIME,
291            ),
292            SCHED_FIFO => (
293                SchedulingPolicy::Fifo,
294                NormalPriority::default(),
295                SchedulingPolicy::Fifo.realtime_priority_from(priority_or_nice as i32)?,
296            ),
297            SCHED_RR => (
298                SchedulingPolicy::RoundRobin,
299                NormalPriority::default(),
300                SchedulingPolicy::RoundRobin.realtime_priority_from(priority_or_nice as i32)?,
301            ),
302            _ => return error!(EINVAL),
303        };
304        Ok(Self { policy, normal_priority, realtime_priority, reset_on_fork: false })
305    }
306
307    pub fn fork(self) -> Self {
308        if self.reset_on_fork {
309            let (policy, normal_priority, realtime_priority) = if self.is_realtime() {
310                // If the calling task has a real-time scheduling policy, the
311                // policy given to child processes is SCHED_OTHER and the nice is
312                // NormalPriority::default() (in all such cases and without caring
313                // about whether the caller's nice had been stronger or weaker than
314                // NormalPriority::default()).
315                (
316                    SchedulingPolicy::Normal,
317                    NormalPriority::default(),
318                    RealtimePriority::NON_REAL_TIME,
319                )
320            } else {
321                // If the calling task has a non-real-time scheduling policy, the
322                // state given to child processes is the same as that of the
323                // caller except with the caller's nice clamped to
324                // NormalPriority::default() at the strongest.
325                (
326                    self.policy,
327                    std::cmp::min(self.normal_priority, NormalPriority::default()),
328                    RealtimePriority::NON_REAL_TIME,
329                )
330            };
331            Self {
332                policy,
333                normal_priority,
334                realtime_priority,
335                // This flag is disabled in child processes created by fork(2).
336                reset_on_fork: false,
337            }
338        } else {
339            self
340        }
341    }
342
343    /// Return the policy as an integer (SCHED_NORMAL, SCHED_BATCH, &c) bitwise-ored
344    /// with the current reset-on-fork status (SCHED_RESET_ON_FORK or 0, depending).
345    ///
346    /// It would be strange for this to need to be called anywhere outside the
347    /// implementation of the sched_getscheduler system call.
348    pub fn policy_for_sched_getscheduler(&self) -> u32 {
349        let mut base = match self.policy {
350            SchedulingPolicy::Normal => SCHED_NORMAL,
351            SchedulingPolicy::Batch => SCHED_BATCH,
352            SchedulingPolicy::Idle => SCHED_IDLE,
353            SchedulingPolicy::Fifo => SCHED_FIFO,
354            SchedulingPolicy::RoundRobin => SCHED_RR,
355        };
356        if self.reset_on_fork {
357            base |= SCHED_RESET_ON_FORK;
358        }
359        base
360    }
361
362    /// Return the priority as a field in a sched_param struct.
363    ///
364    /// It would be strange for this to need to be called anywhere outside the
365    /// implementation of the sched_getparam system call.
366    pub fn get_sched_param(&self) -> sched_param {
367        sched_param {
368            sched_priority: (if self.is_realtime() {
369                self.realtime_priority.value
370            } else {
371                RealtimePriority::NON_REAL_TIME_VALUE
372            }) as i32,
373        }
374    }
375
376    pub fn normal_priority(&self) -> NormalPriority {
377        self.normal_priority
378    }
379
380    pub fn is_realtime(&self) -> bool {
381        match self.policy {
382            SchedulingPolicy::Normal | SchedulingPolicy::Batch | SchedulingPolicy::Idle => false,
383            SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => true,
384        }
385    }
386
387    /// Returns a number 0-31 (inclusive) mapping Linux scheduler priority to a Zircon priority
388    /// level for the fair scheduler.
389    ///
390    /// The range of 32 Zircon priorities is divided into a region for each flavor of Linux
391    /// scheduling:
392    ///
393    /// 1. 0 is used for SCHED_IDLE, the lowest priority Linux tasks.
394    /// 2. 6-15 (inclusive) is used for lower-than-default-priority SCHED_OTHER/SCHED_BATCH tasks.
395    /// 3. 16 is used for the default priority SCHED_OTHER/SCHED_BATCH, the same as Zircon's
396    ///    default for Fuchsia processes.
397    /// 4. 17-26 (inclusive) is used for higher-than-default-priority SCHED_OTHER/SCHED_BATCH tasks.
398    /// 5. Realtime tasks receive their own profile name.
399    fn role_name(&self) -> &'static str {
400        match self.policy {
401            // Mapped to 0; see "the [...] nice value has no influence for [the SCHED_IDLE] policy"
402            // at sched(7).
403            SchedulingPolicy::Idle => FAIR_PRIORITY_ROLE_NAMES[0],
404
405            // Configured with nice 0-40 and mapped to 6-26. 20 is the default nice which we want to
406            // map to 16.
407            SchedulingPolicy::Normal => {
408                FAIR_PRIORITY_ROLE_NAMES[(self.normal_priority.value as usize / 2) + 6]
409            }
410            SchedulingPolicy::Batch => {
411                track_stub!(TODO("https://fxbug.dev/308055542"), "SCHED_BATCH hinting");
412                FAIR_PRIORITY_ROLE_NAMES[(self.normal_priority.value as usize / 2) + 6]
413            }
414
415            // Configured with priority 1-99, mapped to a constant bandwidth profile. Priority
416            // between realtime tasks is ignored because we don't currently have a way to tell the
417            // scheduler that a given realtime task is more important than another without
418            // specifying an earlier deadline for the higher priority task. We can't specify
419            // deadlines at runtime, so we'll treat their priorities all the same.
420            SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => REALTIME_ROLE_NAME,
421        }
422    }
423
424    // TODO: https://fxbug.dev/425726327 - better understand what are Binder's requirements when
425    // comparing one scheduling with another.
426    pub fn is_less_than_for_binder(&self, other: Self) -> bool {
427        match self.policy {
428            SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => match other.policy {
429                SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => {
430                    self.realtime_priority < other.realtime_priority
431                }
432                SchedulingPolicy::Normal | SchedulingPolicy::Batch | SchedulingPolicy::Idle => {
433                    false
434                }
435            },
436            SchedulingPolicy::Normal => match other.policy {
437                SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => true,
438                SchedulingPolicy::Normal => {
439                    self.normal_priority.value < other.normal_priority.value
440                }
441                SchedulingPolicy::Batch | SchedulingPolicy::Idle => false,
442            },
443            SchedulingPolicy::Batch => match other.policy {
444                SchedulingPolicy::Fifo
445                | SchedulingPolicy::RoundRobin
446                | SchedulingPolicy::Normal => true,
447                SchedulingPolicy::Batch => self.normal_priority.value < other.normal_priority.value,
448                SchedulingPolicy::Idle => false,
449            },
450            // see "the [...] nice value has no influence for [the SCHED_IDLE] policy" at sched(7).
451            SchedulingPolicy::Idle => match other.policy {
452                SchedulingPolicy::Fifo
453                | SchedulingPolicy::RoundRobin
454                | SchedulingPolicy::Normal
455                | SchedulingPolicy::Batch => true,
456                SchedulingPolicy::Idle => false,
457            },
458        }
459    }
460}
461
462impl std::default::Default for SchedulerState {
463    fn default() -> Self {
464        Self {
465            policy: SchedulingPolicy::Normal,
466            normal_priority: NormalPriority::default(),
467            realtime_priority: RealtimePriority::NON_REAL_TIME,
468            reset_on_fork: false,
469        }
470    }
471}
472
473pub fn min_priority_for_sched_policy(policy: u32) -> Result<u8, Errno> {
474    Ok(match policy {
475        SCHED_DEADLINE => RealtimePriority::NON_REAL_TIME_VALUE,
476        _ => SchedulingPolicy::try_from(policy)?.realtime_priority_min(),
477    })
478}
479
480pub fn max_priority_for_sched_policy(policy: u32) -> Result<u8, Errno> {
481    Ok(match policy {
482        SCHED_DEADLINE => RealtimePriority::NON_REAL_TIME_VALUE,
483        _ => SchedulingPolicy::try_from(policy)?.realtime_priority_max(),
484    })
485}
486
487/// Names of RoleManager roles for each static Zircon priority in the fair scheduler.
488/// The index in the array is equal to the static priority.
489// LINT.IfChange
490const FAIR_PRIORITY_ROLE_NAMES: [&str; 32] = [
491    "fuchsia.starnix.fair.0",
492    "fuchsia.starnix.fair.1",
493    "fuchsia.starnix.fair.2",
494    "fuchsia.starnix.fair.3",
495    "fuchsia.starnix.fair.4",
496    "fuchsia.starnix.fair.5",
497    "fuchsia.starnix.fair.6",
498    "fuchsia.starnix.fair.7",
499    "fuchsia.starnix.fair.8",
500    "fuchsia.starnix.fair.9",
501    "fuchsia.starnix.fair.10",
502    "fuchsia.starnix.fair.11",
503    "fuchsia.starnix.fair.12",
504    "fuchsia.starnix.fair.13",
505    "fuchsia.starnix.fair.14",
506    "fuchsia.starnix.fair.15",
507    "fuchsia.starnix.fair.16",
508    "fuchsia.starnix.fair.17",
509    "fuchsia.starnix.fair.18",
510    "fuchsia.starnix.fair.19",
511    "fuchsia.starnix.fair.20",
512    "fuchsia.starnix.fair.21",
513    "fuchsia.starnix.fair.22",
514    "fuchsia.starnix.fair.23",
515    "fuchsia.starnix.fair.24",
516    "fuchsia.starnix.fair.25",
517    "fuchsia.starnix.fair.26",
518    "fuchsia.starnix.fair.27",
519    "fuchsia.starnix.fair.28",
520    "fuchsia.starnix.fair.29",
521    "fuchsia.starnix.fair.30",
522    "fuchsia.starnix.fair.31",
523];
524const REALTIME_ROLE_NAME: &str = "fuchsia.starnix.realtime";
525// LINT.ThenChange(src/starnix/config/starnix.profiles)
526
527#[cfg(test)]
528mod tests {
529    use super::*;
530    use assert_matches::assert_matches;
531
532    #[fuchsia::test]
533    fn default_role_name() {
534        assert_eq!(SchedulerState::default().role_name(), "fuchsia.starnix.fair.16");
535    }
536
537    #[fuchsia::test]
538    fn normal_with_non_default_nice_role_name() {
539        assert_eq!(
540            SchedulerState {
541                policy: SchedulingPolicy::Normal,
542                normal_priority: NormalPriority { value: 10 },
543                realtime_priority: RealtimePriority::NON_REAL_TIME,
544                reset_on_fork: false
545            }
546            .role_name(),
547            "fuchsia.starnix.fair.11"
548        );
549        assert_eq!(
550            SchedulerState {
551                policy: SchedulingPolicy::Normal,
552                normal_priority: NormalPriority { value: 27 },
553                realtime_priority: RealtimePriority::NON_REAL_TIME,
554                reset_on_fork: false
555            }
556            .role_name(),
557            "fuchsia.starnix.fair.19"
558        );
559    }
560
561    #[fuchsia::test]
562    fn fifo_role_name() {
563        assert_eq!(
564            SchedulerState {
565                policy: SchedulingPolicy::Fifo,
566                normal_priority: NormalPriority::default(),
567                realtime_priority: RealtimePriority { value: 1 },
568                reset_on_fork: false
569            }
570            .role_name(),
571            "fuchsia.starnix.realtime",
572        );
573        assert_eq!(
574            SchedulerState {
575                policy: SchedulingPolicy::Fifo,
576                normal_priority: NormalPriority::default(),
577                realtime_priority: RealtimePriority { value: 2 },
578                reset_on_fork: false
579            }
580            .role_name(),
581            "fuchsia.starnix.realtime",
582        );
583        assert_eq!(
584            SchedulerState {
585                policy: SchedulingPolicy::Fifo,
586                normal_priority: NormalPriority::default(),
587                realtime_priority: RealtimePriority { value: 99 },
588                reset_on_fork: false
589            }
590            .role_name(),
591            "fuchsia.starnix.realtime",
592        );
593    }
594
595    #[fuchsia::test]
596    fn idle_role_name() {
597        assert_eq!(
598            SchedulerState {
599                policy: SchedulingPolicy::Idle,
600                normal_priority: NormalPriority { value: 1 },
601                realtime_priority: RealtimePriority::NON_REAL_TIME,
602                reset_on_fork: false,
603            }
604            .role_name(),
605            "fuchsia.starnix.fair.0"
606        );
607        assert_eq!(
608            SchedulerState {
609                policy: SchedulingPolicy::Idle,
610                normal_priority: NormalPriority::default(),
611                realtime_priority: RealtimePriority::NON_REAL_TIME,
612                reset_on_fork: false,
613            }
614            .role_name(),
615            "fuchsia.starnix.fair.0"
616        );
617        assert_eq!(
618            SchedulerState {
619                policy: SchedulingPolicy::Idle,
620                normal_priority: NormalPriority { value: 40 },
621                realtime_priority: RealtimePriority::NON_REAL_TIME,
622                reset_on_fork: false,
623            }
624            .role_name(),
625            "fuchsia.starnix.fair.0"
626        );
627    }
628
629    #[fuchsia::test]
630    fn build_policy_from_binder() {
631        assert_matches!(SchedulerState::from_binder(SCHED_NORMAL as u8, 0), Ok(_));
632        assert_matches!(
633            SchedulerState::from_binder(SCHED_NORMAL as u8, ((-21) as i8) as u8),
634            Err(_)
635        );
636        assert_matches!(
637            SchedulerState::from_binder(SCHED_NORMAL as u8, ((-20) as i8) as u8),
638            Ok(SchedulerState {
639                policy: SchedulingPolicy::Normal,
640                normal_priority: NormalPriority { value: 40 },
641                realtime_priority: RealtimePriority::NON_REAL_TIME,
642                reset_on_fork: false,
643            })
644        );
645        assert_matches!(SchedulerState::from_binder(SCHED_NORMAL as u8, 1), Ok(_));
646        assert_matches!(SchedulerState::from_binder(SCHED_NORMAL as u8, 19), Ok(_));
647        assert_matches!(SchedulerState::from_binder(SCHED_NORMAL as u8, 20), Err(_));
648        assert_matches!(SchedulerState::from_binder(SCHED_FIFO as u8, 0), Err(_));
649        assert_matches!(SchedulerState::from_binder(SCHED_FIFO as u8, 1), Ok(_));
650        assert_matches!(SchedulerState::from_binder(SCHED_FIFO as u8, 99), Ok(_));
651        assert_matches!(SchedulerState::from_binder(SCHED_FIFO as u8, 100), Err(_));
652        assert_matches!(SchedulerState::from_binder(SCHED_RR as u8, 0), Err(_));
653        assert_matches!(SchedulerState::from_binder(SCHED_RR as u8, 1), Ok(_));
654        assert_matches!(SchedulerState::from_binder(SCHED_RR as u8, 99), Ok(_));
655        assert_matches!(SchedulerState::from_binder(SCHED_RR as u8, 100), Err(_));
656        assert_matches!(SchedulerState::from_binder(SCHED_BATCH as u8, 11), Ok(_));
657        assert_eq!(SchedulerState::from_binder(SCHED_IDLE as u8, 11), error!(EINVAL));
658        assert_matches!(SchedulerState::from_binder(42, 0), Err(_));
659        assert_matches!(SchedulerState::from_binder(42, 0), Err(_));
660    }
661
662    // NOTE(https://fxbug.dev/425726327): some or all of this test may need to change based
663    // on what is learned in https://fxbug.dev/425726327.
664    #[fuchsia::test]
665    fn is_less_than_for_binder() {
666        let rr_50 = SchedulerState {
667            policy: SchedulingPolicy::RoundRobin,
668            normal_priority: NormalPriority { value: 1 },
669            realtime_priority: RealtimePriority { value: 50 },
670            reset_on_fork: false,
671        };
672        let rr_40 = SchedulerState {
673            policy: SchedulingPolicy::RoundRobin,
674            normal_priority: NormalPriority { value: 1 },
675            realtime_priority: RealtimePriority { value: 40 },
676            reset_on_fork: false,
677        };
678        let fifo_50 = SchedulerState {
679            policy: SchedulingPolicy::Fifo,
680            normal_priority: NormalPriority { value: 1 },
681            realtime_priority: RealtimePriority { value: 50 },
682            reset_on_fork: false,
683        };
684        let fifo_40 = SchedulerState {
685            policy: SchedulingPolicy::Fifo,
686            normal_priority: NormalPriority { value: 1 },
687            realtime_priority: RealtimePriority { value: 40 },
688            reset_on_fork: false,
689        };
690        let normal_40 = SchedulerState {
691            policy: SchedulingPolicy::Normal,
692            normal_priority: NormalPriority { value: 40 },
693            realtime_priority: RealtimePriority::NON_REAL_TIME,
694            reset_on_fork: true,
695        };
696        let normal_10 = SchedulerState {
697            policy: SchedulingPolicy::Normal,
698            normal_priority: NormalPriority { value: 10 },
699            realtime_priority: RealtimePriority::NON_REAL_TIME,
700            reset_on_fork: true,
701        };
702        let batch_40 = SchedulerState {
703            policy: SchedulingPolicy::Batch,
704            normal_priority: NormalPriority { value: 40 },
705            realtime_priority: RealtimePriority::NON_REAL_TIME,
706            reset_on_fork: true,
707        };
708        let batch_30 = SchedulerState {
709            policy: SchedulingPolicy::Batch,
710            normal_priority: NormalPriority { value: 30 },
711            realtime_priority: RealtimePriority::NON_REAL_TIME,
712            reset_on_fork: true,
713        };
714        let idle_40 = SchedulerState {
715            policy: SchedulingPolicy::Idle,
716            normal_priority: NormalPriority { value: 40 },
717            realtime_priority: RealtimePriority::NON_REAL_TIME,
718            reset_on_fork: true,
719        };
720        let idle_30 = SchedulerState {
721            policy: SchedulingPolicy::Idle,
722            normal_priority: NormalPriority { value: 30 },
723            realtime_priority: RealtimePriority::NON_REAL_TIME,
724            reset_on_fork: true,
725        };
726        assert!(!fifo_50.is_less_than_for_binder(fifo_50));
727        assert!(!rr_50.is_less_than_for_binder(rr_50));
728        assert!(!fifo_50.is_less_than_for_binder(rr_50));
729        assert!(!rr_50.is_less_than_for_binder(fifo_50));
730        assert!(!fifo_50.is_less_than_for_binder(rr_40));
731        assert!(rr_40.is_less_than_for_binder(fifo_50));
732        assert!(!rr_50.is_less_than_for_binder(fifo_40));
733        assert!(fifo_40.is_less_than_for_binder(rr_50));
734        assert!(!fifo_40.is_less_than_for_binder(normal_40));
735        assert!(normal_40.is_less_than_for_binder(fifo_40));
736        assert!(!rr_40.is_less_than_for_binder(normal_40));
737        assert!(normal_40.is_less_than_for_binder(rr_40));
738        assert!(!normal_40.is_less_than_for_binder(normal_40));
739        assert!(!normal_40.is_less_than_for_binder(normal_10));
740        assert!(normal_10.is_less_than_for_binder(normal_40));
741        assert!(!normal_10.is_less_than_for_binder(batch_40));
742        assert!(batch_40.is_less_than_for_binder(normal_10));
743        assert!(!batch_40.is_less_than_for_binder(batch_40));
744        assert!(!batch_40.is_less_than_for_binder(batch_30));
745        assert!(batch_30.is_less_than_for_binder(batch_40));
746        assert!(!batch_30.is_less_than_for_binder(idle_40));
747        assert!(idle_40.is_less_than_for_binder(batch_30));
748        assert!(!idle_40.is_less_than_for_binder(idle_40));
749        assert!(!idle_40.is_less_than_for_binder(idle_30));
750        assert!(!idle_30.is_less_than_for_binder(idle_40));
751    }
752
753    #[fuchsia::test]
754    async fn role_overrides_non_realtime() {
755        crate::testing::spawn_kernel_and_run_sync(|_locked, current_task| {
756            let mut builder = RoleOverrides::new();
757            builder.add("my_task", "my_task", "overridden_role");
758            let overrides = builder.build().unwrap();
759            let manager = SchedulerManager { role_manager: None, role_overrides: overrides };
760
761            current_task.set_command_name(starnix_task_command::TaskCommand::new(b"my_task"));
762
763            let mut state = SchedulerState::default();
764            state.policy = SchedulingPolicy::Normal;
765
766            let role = manager.role_name_inner(current_task, state).expect("role_name");
767            assert_eq!(role, "overridden_role");
768        })
769        .await;
770    }
771}