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