| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| use std::fs::File; | |
| use std::io::Read; | |
| use std::os::fd::OwnedFd; | |
| use std::os::unix::net::UnixDatagram; | |
| use std::sync::mpsc::channel; | |
| use std::sync::Arc; | |
| use std::sync::Barrier; | |
| use std::thread::sleep; | |
| use std::thread::JoinHandle; | |
| use std::time::Duration; | |
| use crate::proc::ThreadChecker; | |
| pub use crate::sched_attr::assert_sched_attr; | |
| pub use crate::sched_attr::SchedAttrChecker; | |
| use crate::CgroupContext; | |
| use crate::ProcessId; | |
| use crate::ThreadId; | |
| #[derive(Debug)] | |
| pub struct FakeCgroupFiles { | |
| pub cpu_normal: File, | |
| pub cpu_background: File, | |
| pub cpuset_all: File, | |
| pub cpuset_efficient: File, | |
| } | |
| fn create_fake_file_pair() -> (File, File) { | |
| let (s1, s2) = UnixDatagram::pair().unwrap(); | |
| s1.set_nonblocking(true).unwrap(); | |
| s2.set_nonblocking(true).unwrap(); | |
| let fd1: OwnedFd = s1.into(); | |
| let fd2: OwnedFd = s2.into(); | |
| (fd1.into(), fd2.into()) | |
| } | |
| /// Create fake cgroup files backed by unix datagram sockets. | |
| /// | |
| /// unix datagram socket is useful because there is no delimiters between process/thread ids. | |
| /// | |
| /// [FakeCgroupFiles] must be retained while [CgroupContext] is used. Otherwise writes fail as | |
| /// `ECONNREFUSED`. | |
| pub fn create_fake_cgroup_context_pair() -> (CgroupContext, FakeCgroupFiles) { | |
| let cpu_normal = create_fake_file_pair(); | |
| let cpu_background = create_fake_file_pair(); | |
| let cpuset_all = create_fake_file_pair(); | |
| let cpuset_efficient = create_fake_file_pair(); | |
| ( | |
| CgroupContext { | |
| cpu_normal: cpu_normal.0, | |
| cpu_background: cpu_background.0, | |
| cpuset_all: cpuset_all.0, | |
| cpuset_efficient: cpuset_efficient.0, | |
| }, | |
| FakeCgroupFiles { | |
| cpu_normal: cpu_normal.1, | |
| cpu_background: cpu_background.1, | |
| cpuset_all: cpuset_all.1, | |
| cpuset_efficient: cpuset_efficient.1, | |
| }, | |
| ) | |
| } | |
| pub fn read_number(file: &mut File) -> Option<u32> { | |
| let mut buf = [0; 1024]; | |
| match file.read(buf.as_mut_slice()) { | |
| Ok(0) => None, | |
| Ok(n) => Some( | |
| String::from_utf8(buf[..n].to_vec()) | |
| .unwrap() | |
| .parse::<u32>() | |
| .unwrap(), | |
| ), | |
| Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => None, | |
| Err(e) => panic!("failed to read file: {:#}", e), | |
| } | |
| } | |
| pub struct CgroupFileIterator<'a>(&'a mut File); | |
| impl Iterator for CgroupFileIterator<'_> { | |
| type Item = u32; | |
| fn next(&mut self) -> Option<Self::Item> { | |
| read_number(self.0) | |
| } | |
| } | |
| pub fn read_numbers(file: &mut File) -> CgroupFileIterator { | |
| CgroupFileIterator(file) | |
| } | |
| pub fn drain_file(file: &mut File) { | |
| while read_number(file).is_some() {} | |
| } | |
| pub fn get_current_thread_id() -> ThreadId { | |
| ThreadId(unsafe { libc::gettid() } as u32) | |
| } | |
| pub struct ThreadForTest { | |
| join_handle: Option<JoinHandle<()>>, | |
| barrier: Arc<Barrier>, | |
| } | |
| impl Drop for ThreadForTest { | |
| fn drop(&mut self) { | |
| self.barrier.wait(); | |
| self.join_handle.take().unwrap().join().unwrap(); | |
| } | |
| } | |
| pub fn spawn_thread_for_test() -> (ThreadId, ThreadForTest) { | |
| let (sender, receiver) = channel(); | |
| let barrier = Arc::new(Barrier::new(2)); | |
| let barrier_on_thread = barrier.clone(); | |
| let join_handle = std::thread::spawn(move || { | |
| sender.send(get_current_thread_id()).unwrap(); | |
| barrier_on_thread.wait(); | |
| }); | |
| let thread_id = receiver.recv().unwrap(); | |
| ( | |
| thread_id, | |
| ThreadForTest { | |
| join_handle: Some(join_handle), | |
| barrier, | |
| }, | |
| ) | |
| } | |
| /// [std::thread::JoinHandle] does not guarantee that the thread is removed from | |
| /// procfs. | |
| /// | |
| /// Poll the procfs file until the files for the thread is removed. If the files | |
| /// are not removed, this returns [false]. | |
| pub fn wait_for_thread_removed(process_id: ProcessId, thread_id: ThreadId) -> bool { | |
| let mut checker = ThreadChecker::new(process_id); | |
| for _ in 0..100 { | |
| if !checker.thread_exists(thread_id) { | |
| return true; | |
| } | |
| sleep(Duration::from_millis(1)); | |
| } | |
| false | |
| } | |
| pub struct ProcessForTest { | |
| process_id: ProcessId, | |
| } | |
| impl Drop for ProcessForTest { | |
| fn drop(&mut self) { | |
| let process_id = self.process_id.0 as libc::pid_t; | |
| unsafe { | |
| libc::kill(process_id, libc::SIGKILL); | |
| libc::waitpid(process_id, std::ptr::null_mut(), 0); | |
| } | |
| } | |
| } | |
| pub fn fork_process_for_test() -> (ProcessId, ThreadId, ProcessForTest) { | |
| let child_process_id = unsafe { libc::fork() }; | |
| if child_process_id == 0 { | |
| loop { | |
| std::thread::sleep(Duration::from_secs(1)); | |
| } | |
| } | |
| assert!(child_process_id > 0); | |
| let child_process_id = ProcessId(child_process_id as u32); | |
| ( | |
| child_process_id, | |
| ThreadId(child_process_id.0), | |
| ProcessForTest { | |
| process_id: child_process_id, | |
| }, | |
| ) | |
| } |