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