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