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