1use 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 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 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 #[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 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 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 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 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 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#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
296pub struct NormalPriority {
297 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 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 pub fn as_nice(&self) -> i8 {
324 (Self::DEFAULT_VALUE as i8) - (self.value as i8)
325 }
326
327 pub(crate) fn raw_priority(&self) -> u8 {
330 self.value
331 }
332
333 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#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
349pub(crate) struct RealtimePriority {
350 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#[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 pub(crate) normal_priority: NormalPriority,
431 pub(crate) realtime_priority: RealtimePriority,
434 pub(crate) reset_on_fork: bool,
435 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 (
458 SchedulingPolicy::Normal,
459 NormalPriority::default(),
460 RealtimePriority::NON_REAL_TIME,
461 )
462 } else {
463 (
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 reset_on_fork: false,
479 committed_role_name: None,
480 }
481 } else {
482 Self { committed_role_name: None, ..*self }
483 }
484 }
485
486 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 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 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 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 fn role_name(&self) -> &'static str {
578 match self.policy {
579 SchedulingPolicy::Idle => FAIR_PRIORITY_ROLE_NAMES[0],
582
583 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 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
629const 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#[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 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 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(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(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}