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