Skip to main content

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