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_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_zx_thread_role(
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(crate) 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    #[cfg(test)]
62    pub(crate) fn new_for_tests(
63        role_manager: Option<RoleManagerSynchronousProxy>,
64        role_overrides: RoleOverrides,
65    ) -> Self {
66        Self { role_manager, role_overrides, profile_handle_cache: Default::default() }
67    }
68
69    /// Return the currently active role name for this task. Requires read access to `task`'s state,
70    /// should only be called by code which is not already modifying the provided `task` or holding
71    /// `task.scheduler_state`.
72    pub(crate) fn role_name(
73        &self,
74        task: &Task,
75        scheduler_state: &SchedulerState,
76    ) -> Result<&str, Errno> {
77        let thread_group = task.thread_group();
78        let thread_group_state = thread_group.read();
79        // Only inherit custom roles if the process has executed `execve`. Child processes created
80        // via `fork` only inherit standard scheduling policies (SCHED_NORMAL/SCHED_FIFO) with
81        // default profiles to maintain basic parity with Linux and prevent demand amplification.
82        if thread_group_state.did_exec {
83            let thread_name = task.command();
84            let process_name = if task.tid == task.pid {
85                thread_name.clone()
86            } else {
87                task.pid.get_task().map_err(|_| errno!(EINVAL))?.command()
88            };
89
90            return Ok(self.resolve_role_name(
91                &process_name,
92                &thread_name,
93                &task.read().cpuset_path,
94                task.is_leader(),
95                scheduler_state,
96            ));
97        }
98        Ok(scheduler_state.role_name())
99    }
100
101    fn resolve_role_name(
102        &self,
103        process_name: &TaskCommand,
104        thread_name: &TaskCommand,
105        cgroup_path: &str,
106        is_leader: bool,
107        scheduler_state: &SchedulerState,
108    ) -> &str {
109        if let Some(name) = self.role_overrides.get_role_name_with_filters(
110            process_name,
111            thread_name,
112            cgroup_path,
113            is_leader,
114            scheduler_state.normal_priority().as_nice(),
115        ) {
116            return name;
117        }
118        scheduler_state.role_name()
119    }
120
121    /// Resolves the target scheduler role for `task` and commits the corresponding profile to its
122    /// Zircon thread if it differs from the previously committed role.
123    pub(crate) fn commit_thread_role(
124        &self,
125        task: &Task,
126        scheduler_state: &mut SchedulerState,
127    ) -> Result<(), Errno> {
128        let Some(role_manager) = self.role_manager.as_ref() else {
129            log_debug!("no role manager for setting role");
130            return Ok(());
131        };
132
133        let thread_name = task.command();
134        let process_name = if task.tid == task.pid {
135            thread_name.clone()
136        } else {
137            task.pid
138                .get_task()
139                .map(|l| l.command())
140                .unwrap_or_else(|_| TaskCommand::new(b"<leader exited>"))
141        };
142        let role_name = self.resolve_role_name(
143            &process_name,
144            &thread_name,
145            &task.read().cpuset_path,
146            task.is_leader(),
147            scheduler_state,
148        );
149        if scheduler_state.committed_role_name.as_deref() == Some(role_name) {
150            return Ok(());
151        }
152
153        let zircon_thread = {
154            let running_state = match task.running_state() {
155                Ok(live) => live,
156                Err(_) => {
157                    log_debug!(
158                        "thread role update requested for task without live state, skipping"
159                    );
160                    return Ok(());
161                }
162            };
163            let Some(zircon_thread) = running_state.thread.get() else {
164                log_debug!("thread role update requested for task without thread, skipping");
165                return Ok(());
166            };
167            zircon_thread.clone()
168        };
169
170        Self::set_zx_thread_role(
171            role_manager,
172            &zircon_thread.thread,
173            role_name,
174            &self.profile_handle_cache,
175        )?;
176        scheduler_state.committed_role_name = Some(role_name.to_string());
177
178        Ok(())
179    }
180
181    fn set_zx_thread_role(
182        role_manager: &RoleManagerSynchronousProxy,
183        thread: &zx::Thread,
184        role_name: &str,
185        cache: &LockDepMutex<HashMap<String, zx::Profile>, ProfileHandleCacheLock>,
186    ) -> Result<(), Errno> {
187        Self::set_zx_thread_role_with_profile_setter(
188            role_manager,
189            thread,
190            role_name,
191            cache,
192            |profile| thread.set_profile(profile, 0),
193        )
194    }
195
196    fn set_zx_thread_role_with_profile_setter(
197        role_manager: &RoleManagerSynchronousProxy,
198        thread: &zx::Thread,
199        role_name: &str,
200        cache: &LockDepMutex<HashMap<String, zx::Profile>, ProfileHandleCacheLock>,
201        set_profile: impl FnOnce(&zx::Profile) -> Result<(), zx::Status>,
202    ) -> Result<(), Errno> {
203        log_debug!(role_name; "setting thread role");
204
205        {
206            let params = cache.lock();
207            if let Some(profile) = params.get(role_name) {
208                return Self::set_zx_thread_profile(role_name, profile, set_profile);
209            }
210        }
211
212        let request = RoleManagerGetProfileForRoleRequest {
213            role: Some(RoleName { role: role_name.to_string() }),
214            target: Some(RoleType::Task),
215            ..Default::default()
216        };
217        match role_manager.get_profile_for_role(request, zx::MonotonicInstant::INFINITE) {
218            Ok(Ok(response)) => {
219                let Some(profile) = response.profile else {
220                    log_warn!("GetRole returned success but no profile handle");
221                    return error!(EINVAL);
222                };
223
224                let mut params = cache.lock();
225                let profile = params.entry(role_name.to_string()).or_insert(profile);
226                Self::set_zx_thread_profile(role_name, profile, set_profile)
227            }
228            Ok(Err(e)) => {
229                log_warn!(e:%; "GetRole returned error");
230                Self::set_zx_thread_role_legacy(role_manager, thread, role_name)
231            }
232            Err(e) => {
233                log_warn!(e:%; "GetRole FIDL call failed");
234                Self::set_zx_thread_role_legacy(role_manager, thread, role_name)
235            }
236        }
237    }
238
239    /// Applies `profile` to the target thread via `set_profile`.
240    ///
241    /// Returns:
242    /// - `Ok(())` if the profile was applied to the thread, or if the thread was in
243    ///   `zx::Status::BAD_STATE` (`INITIAL`, `DYING`, or `DEAD`). For a `zx::Thread`, Zircon only
244    ///   returns `BAD_STATE` due to the thread's lifecycle state; profile parameters are validated
245    ///   when the `zx::Profile` is created.
246    /// - `Err(EINVAL)` if `profile` is invalid (`BAD_HANDLE`, `WRONG_TYPE`, `ACCESS_DENIED`) or
247    ///   another error occurred.
248    fn set_zx_thread_profile(
249        role_name: &str,
250        profile: &zx::Profile,
251        set_profile: impl FnOnce(&zx::Profile) -> Result<(), zx::Status>,
252    ) -> Result<(), Errno> {
253        match set_profile(profile) {
254            Ok(()) => Ok(()),
255            // A task's underlying Zircon thread can transition to DYING or DEAD concurrently
256            // after we snapshot its handle from `task.running_state()`. Ignore `BAD_STATE` just
257            // as `commit_thread_role` ignores tasks whose live state has already been cleared.
258            Err(zx::Status::BAD_STATE) => {
259                log_debug!(
260                    role_name;
261                    "thread role update requested for dead or dying thread, skipping"
262                );
263                Ok(())
264            }
265            Err(e) => {
266                log_warn!(role_name, e:%; "Failed to set thread profile from handle");
267                error!(EINVAL)
268            }
269        }
270    }
271
272    fn set_zx_thread_role_legacy(
273        role_manager: &RoleManagerSynchronousProxy,
274        thread: &zx::Thread,
275        role_name: &str,
276    ) -> Result<(), Errno> {
277        let thread = thread.duplicate_handle(zx::Rights::SAME_RIGHTS).map_err(impossible_error)?;
278        let request = RoleManagerSetRoleRequest {
279            target: Some(RoleTarget::Thread(thread)),
280            role: Some(RoleName { role: role_name.to_string() }),
281            ..Default::default()
282        };
283        let _ = role_manager.set_role(request, zx::MonotonicInstant::INFINITE).map_err(|err| {
284            log_warn!(err:%; "Unable to set thread role.");
285            errno!(EINVAL)
286        })?;
287        Ok(())
288    }
289}
290
291/// The task normal priority, used for favoring or disfavoring a task running
292/// with some non-real-time scheduling policies. Ranges from -20 to +19 in
293/// "user-space" representation and +1 to +40 in "kernel-internal"
294/// representation. See "The nice value" at sched(7) for full specification.
295#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
296pub struct NormalPriority {
297    /// 1 (weakest) to 40 (strongest) (in "kernel-internal" representation),
298    /// from setpriority()
299    value: u8,
300}
301
302impl NormalPriority {
303    const MIN_VALUE: u8 = 1;
304    const DEFAULT_VALUE: u8 = 20;
305    const MAX_VALUE: u8 = 40;
306
307    /// Creates a normal priority from a value to be interpreted according
308    /// to the "user-space nice" (-20..=19) scale, clamping values outside
309    /// that scale.
310    ///
311    /// It would be strange for this to be called from anywhere outside of
312    /// the setpriority system call.
313    pub(crate) fn from_setpriority_syscall(user_nice: i32) -> Self {
314        Self {
315            value: (Self::DEFAULT_VALUE as i32)
316                .saturating_sub(user_nice)
317                .clamp(Self::MIN_VALUE as i32, Self::MAX_VALUE as i32) as u8,
318        }
319    }
320
321    /// Returns this normal priority's integer representation according
322    /// to the "user-space nice" (-20..=19) scale.
323    pub fn as_nice(&self) -> i8 {
324        (Self::DEFAULT_VALUE as i8) - (self.value as i8)
325    }
326
327    /// Returns this normal priority's integer representation according
328    /// to the "kernel space nice" (1..=40) scale.
329    pub(crate) fn raw_priority(&self) -> u8 {
330        self.value
331    }
332
333    /// Returns whether this normal priority exceeds the given limit.
334    pub(crate) fn exceeds(&self, limit: u64) -> bool {
335        limit < (self.value as u64)
336    }
337}
338
339impl std::default::Default for NormalPriority {
340    fn default() -> Self {
341        Self { value: Self::DEFAULT_VALUE }
342    }
343}
344
345/// The task real-time priority, used for favoring or disfavoring a task
346/// running with real-time scheduling policies. See "Scheduling policies"
347/// at sched(7) for full specification.
348#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
349pub(crate) struct RealtimePriority {
350    /// 1 (weakest) to 99 (strongest), from sched_setscheduler() and
351    /// sched_setparam(). Only meaningfully used for Fifo and
352    /// Round-Robin; set to 0 for other policies.
353    value: u8,
354}
355
356impl RealtimePriority {
357    const NON_REAL_TIME_VALUE: u8 = 0;
358    const MIN_VALUE: u8 = 1;
359    const MAX_VALUE: u8 = 99;
360
361    const NON_REAL_TIME: RealtimePriority = RealtimePriority { value: Self::NON_REAL_TIME_VALUE };
362
363    pub(crate) fn exceeds(&self, limit: u64) -> bool {
364        limit < (self.value as u64)
365    }
366}
367
368/// The scheduling policies described in "Scheduling policies" at sched(7).
369#[derive(Clone, Copy, Debug, Eq, PartialEq)]
370pub(crate) enum SchedulingPolicy {
371    Normal,
372    Batch,
373    Idle,
374    Fifo,
375    RoundRobin,
376}
377
378impl SchedulingPolicy {
379    fn realtime_priority_min(&self) -> u8 {
380        match self {
381            Self::Normal | Self::Batch | Self::Idle => RealtimePriority::NON_REAL_TIME_VALUE,
382            Self::Fifo | Self::RoundRobin => RealtimePriority::MIN_VALUE,
383        }
384    }
385
386    fn realtime_priority_max(&self) -> u8 {
387        match self {
388            Self::Normal | Self::Batch | Self::Idle => RealtimePriority::NON_REAL_TIME_VALUE,
389            Self::Fifo | Self::RoundRobin => RealtimePriority::MAX_VALUE,
390        }
391    }
392
393    pub(crate) fn is_valid_priority(&self, priority: RealtimePriority) -> bool {
394        (self.realtime_priority_min()..=self.realtime_priority_max()).contains(&priority.value)
395    }
396
397    pub(crate) fn realtime_priority_from(&self, priority: i32) -> Result<RealtimePriority, Errno> {
398        let priority =
399            RealtimePriority { value: u8::try_from(priority).map_err(|_| errno!(EINVAL))? };
400        if !self.is_valid_priority(priority) {
401            return error!(EINVAL);
402        }
403        Ok(priority)
404    }
405}
406
407impl TryFrom<u32> for SchedulingPolicy {
408    type Error = Errno;
409
410    fn try_from(value: u32) -> Result<Self, Errno> {
411        Ok(match value {
412            SCHED_NORMAL => Self::Normal,
413            SCHED_BATCH => Self::Batch,
414            SCHED_IDLE => Self::Idle,
415            SCHED_FIFO => Self::Fifo,
416            SCHED_RR => Self::RoundRobin,
417            _ => {
418                return error!(EINVAL);
419            }
420        })
421    }
422}
423
424#[derive(Clone, Debug, Eq, PartialEq)]
425pub struct SchedulerState {
426    pub(crate) policy: SchedulingPolicy,
427    /// Although nice is only used for Normal and Batch, normal priority
428    /// ("nice") is still maintained, observable, and alterable when a
429    /// task is using Idle, Fifo, and RoundRobin.
430    pub(crate) normal_priority: NormalPriority,
431    /// 1 (weakest) to 99 (strongest), from sched_setscheduler() and
432    /// sched_setparam(). Only used for Fifo and Round-Robin.
433    pub(crate) realtime_priority: RealtimePriority,
434    pub(crate) reset_on_fork: bool,
435    /// The last committed scheduler role name.
436    committed_role_name: Option<String>,
437}
438
439impl SchedulerState {
440    pub(crate) fn is_default(&self) -> bool {
441        let default = Self::default();
442        self.policy == default.policy
443            && self.normal_priority == default.normal_priority
444            && self.realtime_priority == default.realtime_priority
445            && self.reset_on_fork == default.reset_on_fork
446            && self.committed_role_name.as_deref().is_none_or(|role| role == default.role_name())
447    }
448
449    pub(crate) fn fork(&self) -> Self {
450        if self.reset_on_fork {
451            let (policy, normal_priority, realtime_priority) = if self.is_realtime() {
452                // If the calling task has a real-time scheduling policy, the
453                // policy given to child processes is SCHED_OTHER and the nice is
454                // NormalPriority::default() (in all such cases and without caring
455                // about whether the caller's nice had been stronger or weaker than
456                // NormalPriority::default()).
457                (
458                    SchedulingPolicy::Normal,
459                    NormalPriority::default(),
460                    RealtimePriority::NON_REAL_TIME,
461                )
462            } else {
463                // If the calling task has a non-real-time scheduling policy, the
464                // state given to child processes is the same as that of the
465                // caller except with the caller's nice clamped to
466                // NormalPriority::default() at the strongest.
467                (
468                    self.policy,
469                    std::cmp::min(self.normal_priority, NormalPriority::default()),
470                    RealtimePriority::NON_REAL_TIME,
471                )
472            };
473            Self {
474                policy,
475                normal_priority,
476                realtime_priority,
477                // This flag is disabled in child processes created by fork(2).
478                reset_on_fork: false,
479                committed_role_name: None,
480            }
481        } else {
482            Self { committed_role_name: None, ..*self }
483        }
484    }
485
486    /// Return the policy as an integer (SCHED_NORMAL, SCHED_BATCH, &c) bitwise-ored
487    /// with the current reset-on-fork status (SCHED_RESET_ON_FORK or 0, depending).
488    ///
489    /// It would be strange for this to need to be called anywhere outside the
490    /// implementation of the sched_getscheduler system call.
491    pub(crate) fn policy_for_sched_getscheduler(&self) -> u32 {
492        let mut base = match self.policy {
493            SchedulingPolicy::Normal => SCHED_NORMAL,
494            SchedulingPolicy::Batch => SCHED_BATCH,
495            SchedulingPolicy::Idle => SCHED_IDLE,
496            SchedulingPolicy::Fifo => SCHED_FIFO,
497            SchedulingPolicy::RoundRobin => SCHED_RR,
498        };
499        if self.reset_on_fork {
500            base |= SCHED_RESET_ON_FORK;
501        }
502        base
503    }
504
505    /// Return the priority as a field in a sched_param struct.
506    ///
507    /// It would be strange for this to need to be called anywhere outside the
508    /// implementation of the sched_getparam system call.
509    pub(crate) fn get_sched_param(&self) -> sched_param {
510        sched_param {
511            sched_priority: (if self.is_realtime() {
512                self.realtime_priority.value
513            } else {
514                RealtimePriority::NON_REAL_TIME_VALUE
515            }) as i32,
516        }
517    }
518
519    pub fn normal_priority(&self) -> NormalPriority {
520        self.normal_priority
521    }
522
523    /// Returns a monotonic priority key (`0..=139`) for ordering `FUTEX_WAIT` waiters:
524    /// - `0`: `SCHED_IDLE`
525    /// - `1..=40`: `SCHED_NORMAL` / `SCHED_BATCH` (`20` is default nice `0`)
526    /// - `41..=139`: `SCHED_FIFO` / `SCHED_RR` (`40 + realtime_priority`)
527    pub(crate) fn futex_priority(&self) -> u8 {
528        match self.policy {
529            SchedulingPolicy::Idle => 0,
530            SchedulingPolicy::Normal | SchedulingPolicy::Batch => {
531                self.normal_priority.raw_priority()
532            }
533            SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => {
534                NormalPriority::MAX_VALUE.saturating_add(self.realtime_priority.value)
535            }
536        }
537    }
538
539    /// Returns the task priority value as reported in `/proc/<pid>/stat` (see `task_prio` in
540    /// Linux `kernel/sched/core.c` and `proc_pid_stat(5)`).
541    ///
542    /// For non-real-time scheduling policies (`SCHED_NORMAL`, `SCHED_BATCH`, `SCHED_IDLE`),
543    /// this is `20 + nice` in the range `0..=39` (corresponding to nice `-20..=19`).
544    /// For real-time scheduling policies (`SCHED_FIFO`, `SCHED_RR`), this is
545    /// `-1 - rt_priority` in the range `-100..=-2` (corresponding to real-time priorities
546    /// `1..=99`).
547    pub fn task_priority(&self) -> i64 {
548        match self.policy {
549            SchedulingPolicy::Normal | SchedulingPolicy::Batch | SchedulingPolicy::Idle => {
550                (NormalPriority::DEFAULT_VALUE as i64) + (self.normal_priority.as_nice() as i64)
551            }
552            SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => {
553                -1 - (self.realtime_priority.value as i64)
554            }
555        }
556    }
557
558    fn is_realtime(&self) -> bool {
559        match self.policy {
560            SchedulingPolicy::Normal | SchedulingPolicy::Batch | SchedulingPolicy::Idle => false,
561            SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => true,
562        }
563    }
564
565    /// Returns a number 0-31 (inclusive) mapping Linux scheduler priority to a Zircon priority
566    /// level for the fair scheduler.
567    ///
568    /// The range of 32 Zircon priorities is divided into a region for each flavor of Linux
569    /// scheduling:
570    ///
571    /// 1. 0 is used for SCHED_IDLE, the lowest priority Linux tasks.
572    /// 2. 6-15 (inclusive) is used for lower-than-default-priority SCHED_OTHER/SCHED_BATCH tasks.
573    /// 3. 16 is used for the default priority SCHED_OTHER/SCHED_BATCH, the same as Zircon's
574    ///    default for Fuchsia processes.
575    /// 4. 17-26 (inclusive) is used for higher-than-default-priority SCHED_OTHER/SCHED_BATCH tasks.
576    /// 5. Realtime tasks receive their own profile name.
577    fn role_name(&self) -> &'static str {
578        match self.policy {
579            // Mapped to 0; see "the [...] nice value has no influence for [the SCHED_IDLE] policy"
580            // at sched(7).
581            SchedulingPolicy::Idle => FAIR_PRIORITY_ROLE_NAMES[0],
582
583            // Configured with nice 0-40 and mapped to 6-26. 20 is the default nice which we want to
584            // map to 16.
585            SchedulingPolicy::Normal => {
586                FAIR_PRIORITY_ROLE_NAMES[(self.normal_priority.value as usize / 2) + 6]
587            }
588            SchedulingPolicy::Batch => {
589                track_stub!(TODO("https://fxbug.dev/308055542"), "SCHED_BATCH hinting");
590                FAIR_PRIORITY_ROLE_NAMES[(self.normal_priority.value as usize / 2) + 6]
591            }
592
593            // Configured with priority 1-99, mapped to a constant bandwidth profile. Priority
594            // between realtime tasks is ignored because we don't currently have a way to tell the
595            // scheduler that a given realtime task is more important than another without
596            // specifying an earlier deadline for the higher priority task. We can't specify
597            // deadlines at runtime, so we'll treat their priorities all the same.
598            SchedulingPolicy::Fifo | SchedulingPolicy::RoundRobin => REALTIME_ROLE_NAME,
599        }
600    }
601}
602
603impl std::default::Default for SchedulerState {
604    fn default() -> Self {
605        Self {
606            policy: SchedulingPolicy::Normal,
607            normal_priority: NormalPriority::default(),
608            realtime_priority: RealtimePriority::NON_REAL_TIME,
609            reset_on_fork: false,
610            committed_role_name: None,
611        }
612    }
613}
614
615pub(crate) fn min_priority_for_sched_policy(policy: u32) -> Result<u8, Errno> {
616    Ok(match policy {
617        SCHED_DEADLINE => RealtimePriority::NON_REAL_TIME_VALUE,
618        _ => SchedulingPolicy::try_from(policy)?.realtime_priority_min(),
619    })
620}
621
622pub(crate) fn max_priority_for_sched_policy(policy: u32) -> Result<u8, Errno> {
623    Ok(match policy {
624        SCHED_DEADLINE => RealtimePriority::NON_REAL_TIME_VALUE,
625        _ => SchedulingPolicy::try_from(policy)?.realtime_priority_max(),
626    })
627}
628
629/// Names of RoleManager roles for each static Zircon priority in the fair scheduler.
630/// The index in the array is equal to the static priority.
631// LINT.IfChange
632// Each default role in `starnix.profiles` sets `"affinity": "all"` so reverting from an
633// affinity-restricted `RoleOverrides` profile clears the thread's Zircon soft affinity mask.
634const FAIR_PRIORITY_ROLE_NAMES: [&str; 32] = [
635    "fuchsia.starnix.fair.0",
636    "fuchsia.starnix.fair.1",
637    "fuchsia.starnix.fair.2",
638    "fuchsia.starnix.fair.3",
639    "fuchsia.starnix.fair.4",
640    "fuchsia.starnix.fair.5",
641    "fuchsia.starnix.fair.6",
642    "fuchsia.starnix.fair.7",
643    "fuchsia.starnix.fair.8",
644    "fuchsia.starnix.fair.9",
645    "fuchsia.starnix.fair.10",
646    "fuchsia.starnix.fair.11",
647    "fuchsia.starnix.fair.12",
648    "fuchsia.starnix.fair.13",
649    "fuchsia.starnix.fair.14",
650    "fuchsia.starnix.fair.15",
651    "fuchsia.starnix.fair.16",
652    "fuchsia.starnix.fair.17",
653    "fuchsia.starnix.fair.18",
654    "fuchsia.starnix.fair.19",
655    "fuchsia.starnix.fair.20",
656    "fuchsia.starnix.fair.21",
657    "fuchsia.starnix.fair.22",
658    "fuchsia.starnix.fair.23",
659    "fuchsia.starnix.fair.24",
660    "fuchsia.starnix.fair.25",
661    "fuchsia.starnix.fair.26",
662    "fuchsia.starnix.fair.27",
663    "fuchsia.starnix.fair.28",
664    "fuchsia.starnix.fair.29",
665    "fuchsia.starnix.fair.30",
666    "fuchsia.starnix.fair.31",
667];
668const REALTIME_ROLE_NAME: &str = "fuchsia.starnix.realtime";
669// LINT.ThenChange(//src/starnix/config/starnix.profiles)
670
671#[cfg(test)]
672mod tests {
673    use super::*;
674
675    #[fuchsia::test]
676    fn default_role_name() {
677        assert_eq!(SchedulerState::default().role_name(), "fuchsia.starnix.fair.16");
678    }
679
680    #[fuchsia::test]
681    fn scheduler_state_is_default() {
682        assert!(SchedulerState::default().is_default());
683        assert!(
684            SchedulerState {
685                committed_role_name: Some("fuchsia.starnix.fair.16".to_string()),
686                ..SchedulerState::default()
687            }
688            .is_default()
689        );
690        assert!(
691            !SchedulerState {
692                committed_role_name: Some("overridden_role".to_string()),
693                ..SchedulerState::default()
694            }
695            .is_default()
696        );
697        assert!(
698            !SchedulerState { policy: SchedulingPolicy::Idle, ..SchedulerState::default() }
699                .is_default()
700        );
701        assert!(
702            !SchedulerState {
703                normal_priority: NormalPriority { value: 10 },
704                ..SchedulerState::default()
705            }
706            .is_default()
707        );
708        assert!(!SchedulerState { reset_on_fork: true, ..SchedulerState::default() }.is_default());
709    }
710
711    #[fuchsia::test]
712    fn scheduler_state_fork_clears_committed_role_name() {
713        let state = SchedulerState {
714            policy: SchedulingPolicy::Fifo,
715            normal_priority: NormalPriority { value: 30 },
716            realtime_priority: RealtimePriority { value: 50 },
717            reset_on_fork: false,
718            committed_role_name: Some("fuchsia.starnix.realtime".to_string()),
719        };
720        assert_eq!(
721            state.fork(),
722            SchedulerState {
723                policy: SchedulingPolicy::Fifo,
724                normal_priority: NormalPriority { value: 30 },
725                realtime_priority: RealtimePriority { value: 50 },
726                reset_on_fork: false,
727                committed_role_name: None,
728            }
729        );
730
731        let reset_realtime_state = SchedulerState { reset_on_fork: true, ..state };
732        assert_eq!(
733            reset_realtime_state.fork(),
734            SchedulerState {
735                policy: SchedulingPolicy::Normal,
736                normal_priority: NormalPriority::default(),
737                realtime_priority: RealtimePriority::NON_REAL_TIME,
738                reset_on_fork: false,
739                committed_role_name: None,
740            }
741        );
742
743        let reset_normal_state = SchedulerState {
744            policy: SchedulingPolicy::Normal,
745            normal_priority: NormalPriority { value: 30 },
746            realtime_priority: RealtimePriority::NON_REAL_TIME,
747            reset_on_fork: true,
748            committed_role_name: Some("fuchsia.starnix.fair.21".to_string()),
749        };
750        assert_eq!(
751            reset_normal_state.fork(),
752            SchedulerState {
753                policy: SchedulingPolicy::Normal,
754                normal_priority: NormalPriority::default(),
755                realtime_priority: RealtimePriority::NON_REAL_TIME,
756                reset_on_fork: false,
757                committed_role_name: None,
758            }
759        );
760    }
761
762    #[fuchsia::test]
763    fn normal_with_non_default_nice_role_name() {
764        assert_eq!(
765            SchedulerState {
766                policy: SchedulingPolicy::Normal,
767                normal_priority: NormalPriority { value: 10 },
768                realtime_priority: RealtimePriority::NON_REAL_TIME,
769                reset_on_fork: false,
770                committed_role_name: None,
771            }
772            .role_name(),
773            "fuchsia.starnix.fair.11"
774        );
775        assert_eq!(
776            SchedulerState {
777                policy: SchedulingPolicy::Normal,
778                normal_priority: NormalPriority { value: 27 },
779                realtime_priority: RealtimePriority::NON_REAL_TIME,
780                reset_on_fork: false,
781                committed_role_name: None,
782            }
783            .role_name(),
784            "fuchsia.starnix.fair.19"
785        );
786    }
787
788    #[fuchsia::test]
789    fn fifo_role_name() {
790        assert_eq!(
791            SchedulerState {
792                policy: SchedulingPolicy::Fifo,
793                normal_priority: NormalPriority::default(),
794                realtime_priority: RealtimePriority { value: 1 },
795                reset_on_fork: false,
796                committed_role_name: None,
797            }
798            .role_name(),
799            "fuchsia.starnix.realtime",
800        );
801        assert_eq!(
802            SchedulerState {
803                policy: SchedulingPolicy::Fifo,
804                normal_priority: NormalPriority::default(),
805                realtime_priority: RealtimePriority { value: 2 },
806                reset_on_fork: false,
807                committed_role_name: None,
808            }
809            .role_name(),
810            "fuchsia.starnix.realtime",
811        );
812        assert_eq!(
813            SchedulerState {
814                policy: SchedulingPolicy::Fifo,
815                normal_priority: NormalPriority::default(),
816                realtime_priority: RealtimePriority { value: 99 },
817                reset_on_fork: false,
818                committed_role_name: None,
819            }
820            .role_name(),
821            "fuchsia.starnix.realtime",
822        );
823    }
824
825    #[fuchsia::test]
826    fn idle_role_name() {
827        assert_eq!(
828            SchedulerState {
829                policy: SchedulingPolicy::Idle,
830                normal_priority: NormalPriority { value: 1 },
831                realtime_priority: RealtimePriority::NON_REAL_TIME,
832                reset_on_fork: false,
833                committed_role_name: None,
834            }
835            .role_name(),
836            "fuchsia.starnix.fair.0"
837        );
838        assert_eq!(
839            SchedulerState {
840                policy: SchedulingPolicy::Idle,
841                normal_priority: NormalPriority::default(),
842                realtime_priority: RealtimePriority::NON_REAL_TIME,
843                reset_on_fork: false,
844                committed_role_name: None,
845            }
846            .role_name(),
847            "fuchsia.starnix.fair.0"
848        );
849        assert_eq!(
850            SchedulerState {
851                policy: SchedulingPolicy::Idle,
852                normal_priority: NormalPriority { value: 40 },
853                realtime_priority: RealtimePriority::NON_REAL_TIME,
854                reset_on_fork: false,
855                committed_role_name: None,
856            }
857            .role_name(),
858            "fuchsia.starnix.fair.0"
859        );
860    }
861
862    #[fuchsia::test]
863    async fn role_overrides_non_realtime() {
864        crate::testing::spawn_kernel_and_run_sync(|current_task| {
865            let mut builder = RoleOverrides::new();
866            builder.add("my_task", "my_task", None, "overridden_role");
867            let overrides = builder.build().unwrap();
868            let manager = SchedulerManager {
869                role_manager: None,
870                role_overrides: overrides,
871                profile_handle_cache: Default::default(),
872            };
873
874            // Set did_exec = true so custom role overrides are applied.
875            current_task.thread_group().write().did_exec = true;
876            current_task.set_command_name(starnix_task_command::TaskCommand::new(b"my_task"));
877
878            let mut state = SchedulerState::default();
879            state.policy = SchedulingPolicy::Normal;
880
881            let role = manager.role_name(current_task, &state).expect("role_name");
882            assert_eq!(role, "overridden_role");
883        })
884        .await;
885    }
886
887    /// Creates a `zx::Profile` wrapping a `zx::Socket` handle.
888    ///
889    /// Creating a real `zx::Profile` requires the root `ProfileResource`, which is not available in
890    /// unit tests. When passed to the real `zx::Thread::set_profile` syscall, Zircon rejects this
891    /// handle with `zx::Status::WRONG_TYPE`, exercising the invalid-profile error path; tests for
892    /// `Ok(())` and `zx::Status::BAD_STATE` use `set_zx_thread_role_with_profile_setter`.
893    fn invalid_profile_for_test() -> zx::Profile {
894        let (client, _server) = zx::Socket::create_stream();
895        zx::Profile::from(zx::NullableHandle::from(client))
896    }
897
898    #[fuchsia::test]
899    async fn commit_thread_role_failure_does_not_update_committed_role_name() {
900        crate::testing::spawn_kernel_and_run_sync(|current_task| {
901            let (role_manager, server_end) =
902                fidl::endpoints::create_sync_proxy::<RoleManagerMarker>();
903            drop(server_end);
904
905            let manager = SchedulerManager {
906                role_manager: Some(role_manager),
907                role_overrides: RoleOverrides::new().build().unwrap(),
908                profile_handle_cache: LockDepMutex::new(HashMap::from([(
909                    "fuchsia.starnix.fair.16".to_string(),
910                    invalid_profile_for_test(),
911                )])),
912            };
913
914            assert_eq!(current_task.scheduler_state.lock().committed_role_name, None);
915            assert_eq!(
916                manager.commit_thread_role(current_task, &mut current_task.scheduler_state.lock()),
917                error!(EINVAL)
918            );
919            assert_eq!(current_task.scheduler_state.lock().committed_role_name, None);
920
921            current_task.scheduler_state.lock().committed_role_name =
922                Some("previous_role".to_string());
923            assert_eq!(
924                manager.commit_thread_role(current_task, &mut current_task.scheduler_state.lock()),
925                error!(EINVAL)
926            );
927            assert_eq!(
928                current_task.scheduler_state.lock().committed_role_name.as_deref(),
929                Some("previous_role")
930            );
931        })
932        .await;
933    }
934
935    #[fuchsia::test]
936    fn set_zx_thread_role_cached_profile_ok() {
937        let (role_manager, server_end) = fidl::endpoints::create_sync_proxy::<RoleManagerMarker>();
938        // Drop the server endpoint so any fallback FIDL call would fail with PEER_CLOSED.
939        drop(server_end);
940
941        let cache = LockDepMutex::new(HashMap::from([(
942            "fuchsia.starnix.fair.16".to_string(),
943            invalid_profile_for_test(),
944        )]));
945
946        fuchsia_runtime::with_thread_self(|thread| {
947            let mut called = false;
948            let result = SchedulerManager::set_zx_thread_role_with_profile_setter(
949                &role_manager,
950                thread,
951                "fuchsia.starnix.fair.16",
952                &cache,
953                |_| {
954                    called = true;
955                    Ok(())
956                },
957            );
958            assert_eq!(result, Ok(()));
959            assert!(called);
960        });
961    }
962
963    #[fuchsia::test]
964    fn set_zx_thread_role_cached_profile_bad_state_is_ignored() {
965        let (role_manager, server_end) = fidl::endpoints::create_sync_proxy::<RoleManagerMarker>();
966        // Drop the server endpoint so any fallback FIDL call to RoleManager would fail.
967        drop(server_end);
968
969        let cache = LockDepMutex::new(HashMap::from([(
970            "fuchsia.starnix.fair.16".to_string(),
971            invalid_profile_for_test(),
972        )]));
973
974        fuchsia_runtime::with_thread_self(|thread| {
975            let result = SchedulerManager::set_zx_thread_role_with_profile_setter(
976                &role_manager,
977                thread,
978                "fuchsia.starnix.fair.16",
979                &cache,
980                |_| Err(zx::Status::BAD_STATE),
981            );
982            assert_eq!(result, Ok(()));
983        });
984    }
985
986    #[fuchsia::test]
987    fn set_zx_thread_role_cached_invalid_profile_does_not_call_role_manager() {
988        let (role_manager, server_end) = fidl::endpoints::create_sync_proxy::<RoleManagerMarker>();
989        let server_channel = server_end.into_channel();
990        let watcher = std::thread::spawn(move || {
991            let signals = server_channel
992                .wait_one(
993                    zx::Signals::CHANNEL_READABLE | zx::Signals::CHANNEL_PEER_CLOSED,
994                    zx::MonotonicInstant::INFINITE,
995                )
996                .to_result()
997                .expect("wait_one");
998            signals.contains(zx::Signals::CHANNEL_READABLE)
999        });
1000
1001        let cache = LockDepMutex::new(HashMap::from([(
1002            "fuchsia.starnix.fair.16".to_string(),
1003            invalid_profile_for_test(),
1004        )]));
1005
1006        fuchsia_runtime::with_thread_self(|thread| {
1007            let result = SchedulerManager::set_zx_thread_role(
1008                &role_manager,
1009                thread,
1010                "fuchsia.starnix.fair.16",
1011                &cache,
1012            );
1013            assert_eq!(result, error!(EINVAL));
1014        });
1015
1016        drop(role_manager);
1017        let received_fidl_request = watcher.join().expect("watcher thread");
1018        assert!(!received_fidl_request);
1019    }
1020
1021    #[fuchsia::test]
1022    fn set_zx_thread_profile_handles_outcomes() {
1023        let profile = invalid_profile_for_test();
1024        assert_eq!(
1025            SchedulerManager::set_zx_thread_profile(
1026                "fuchsia.starnix.fair.16",
1027                &profile,
1028                |_| Ok(())
1029            ),
1030            Ok(())
1031        );
1032        assert_eq!(
1033            SchedulerManager::set_zx_thread_profile("fuchsia.starnix.fair.16", &profile, |_| Err(
1034                zx::Status::BAD_STATE
1035            )),
1036            Ok(())
1037        );
1038        assert_eq!(
1039            SchedulerManager::set_zx_thread_profile("fuchsia.starnix.fair.16", &profile, |_| Err(
1040                zx::Status::WRONG_TYPE
1041            )),
1042            error!(EINVAL)
1043        );
1044    }
1045
1046    #[fuchsia::test]
1047    fn futex_priority_orders_policies_and_priorities() {
1048        let idle = SchedulerState {
1049            policy: SchedulingPolicy::Idle,
1050            normal_priority: NormalPriority::from_setpriority_syscall(-20),
1051            ..SchedulerState::default()
1052        };
1053        let normal_low = SchedulerState {
1054            policy: SchedulingPolicy::Normal,
1055            normal_priority: NormalPriority::from_setpriority_syscall(19),
1056            ..SchedulerState::default()
1057        };
1058        let normal_default = SchedulerState::default();
1059        let batch_high = SchedulerState {
1060            policy: SchedulingPolicy::Batch,
1061            normal_priority: NormalPriority::from_setpriority_syscall(-20),
1062            ..SchedulerState::default()
1063        };
1064        let fifo_min = SchedulerState {
1065            policy: SchedulingPolicy::Fifo,
1066            realtime_priority: RealtimePriority { value: 1 },
1067            ..SchedulerState::default()
1068        };
1069        let rr_max = SchedulerState {
1070            policy: SchedulingPolicy::RoundRobin,
1071            realtime_priority: RealtimePriority { value: 99 },
1072            ..SchedulerState::default()
1073        };
1074
1075        assert_eq!(idle.futex_priority(), 0);
1076        assert_eq!(normal_low.futex_priority(), 1);
1077        assert_eq!(normal_default.futex_priority(), 20);
1078        assert_eq!(batch_high.futex_priority(), 40);
1079        assert_eq!(fifo_min.futex_priority(), 41);
1080        assert_eq!(rr_max.futex_priority(), 139);
1081    }
1082
1083    #[fuchsia::test]
1084    fn task_priority_values() {
1085        assert_eq!(SchedulerState::default().task_priority(), 20);
1086        assert_eq!(
1087            SchedulerState {
1088                normal_priority: NormalPriority::from_setpriority_syscall(1),
1089                ..SchedulerState::default()
1090            }
1091            .task_priority(),
1092            21
1093        );
1094        assert_eq!(
1095            SchedulerState {
1096                normal_priority: NormalPriority::from_setpriority_syscall(-20),
1097                ..SchedulerState::default()
1098            }
1099            .task_priority(),
1100            0
1101        );
1102        assert_eq!(
1103            SchedulerState {
1104                normal_priority: NormalPriority::from_setpriority_syscall(19),
1105                ..SchedulerState::default()
1106            }
1107            .task_priority(),
1108            39
1109        );
1110        assert_eq!(
1111            SchedulerState {
1112                policy: SchedulingPolicy::Batch,
1113                normal_priority: NormalPriority::from_setpriority_syscall(-20),
1114                ..SchedulerState::default()
1115            }
1116            .task_priority(),
1117            0
1118        );
1119        assert_eq!(
1120            SchedulerState {
1121                policy: SchedulingPolicy::Idle,
1122                normal_priority: NormalPriority::from_setpriority_syscall(-20),
1123                ..SchedulerState::default()
1124            }
1125            .task_priority(),
1126            0
1127        );
1128        assert_eq!(
1129            SchedulerState {
1130                policy: SchedulingPolicy::Idle,
1131                normal_priority: NormalPriority::from_setpriority_syscall(5),
1132                ..SchedulerState::default()
1133            }
1134            .task_priority(),
1135            25
1136        );
1137        assert_eq!(
1138            SchedulerState {
1139                policy: SchedulingPolicy::Fifo,
1140                realtime_priority: RealtimePriority { value: 1 },
1141                ..SchedulerState::default()
1142            }
1143            .task_priority(),
1144            -2
1145        );
1146        assert_eq!(
1147            SchedulerState {
1148                policy: SchedulingPolicy::RoundRobin,
1149                realtime_priority: RealtimePriority { value: 99 },
1150                ..SchedulerState::default()
1151            }
1152            .task_priority(),
1153            -100
1154        );
1155    }
1156}