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