1mod exec_identity;
13#[cfg(feature = "physical-mm-test-support")]
14pub mod initial_mm_probe;
15pub(crate) mod mapping_capture;
16pub mod native_exec;
17mod parked;
18pub(crate) mod replay_departure;
19use std::cmp::Ordering;
20use std::collections::BTreeMap;
21use std::collections::BTreeSet;
22use std::collections::HashMap;
23use std::collections::HashSet;
24use std::collections::btree_map::Entry;
25use std::fmt::Debug;
26use std::fs;
27use std::fs::File;
28use std::io::Write;
29use std::num::NonZeroUsize;
30use std::os::fd::FromRawFd;
31use std::path::PathBuf;
32use std::sync::Arc;
33use std::sync::Mutex;
34use std::sync::OnceLock;
35use std::sync::PoisonError;
36use std::sync::atomic::AtomicBool;
37use std::sync::atomic::AtomicI32;
38use std::sync::atomic::AtomicU16;
39use std::sync::atomic::AtomicU64;
40use std::sync::atomic::Ordering::SeqCst;
41use std::task::Poll;
42use std::time::Duration;
43use std::time::SystemTime;
44
45use anyhow::bail;
46use chrono::DateTime;
47use chrono::Utc;
48use detcore_model::host_input::HostFileIdentity;
49use detcore_model::host_input::HostInputRecord;
50use detcore_model::host_input::HostInputSet;
51use detcore_model::network_engine::NetworkEngine;
52use detcore_model::network_engine::NetworkEngineError;
53use detcore_model::network_engine::NetworkReply;
54use detcore_model::network_engine::NetworkRequest;
55use detcore_model::network_trace::NetworkTraceMode;
56use detcore_model::network_trace::NetworkTraceV2;
57use detcore_model::procfs::mount_ids_are_ordered_subset;
58use detcore_model::summary::PreemptionSummary;
59use detcore_model::summary::RunSummary;
60use detcore_model::summary::TimesliceStats;
61use detcore_model::syscall_histogram::SparseSyscallHistogram;
62pub use detcore_model::syscall_histogram::SyscallObservationGap;
63use detcore_model::syscall_histogram::SyscallObservationScope;
64pub(crate) use exec_identity::reconnect_exec;
65pub(crate) use exec_identity::retire_exec;
66use nix::sys::signal;
67use nix::sys::signal::Signal;
68use nix::unistd::Pid;
69pub(crate) use parked::child_wait_request;
70pub(crate) use parked::parked_wait_request;
71pub(crate) use parked::polled_read_request;
72pub(crate) use parked::signal_dequeued;
73use reverie::GlobalRPC;
74use reverie::GlobalTool;
75use reverie::Guest;
76use reverie::Tid;
77use reverie::syscalls::AddrMut;
78use reverie::syscalls::CloneFlags;
79use reverie::syscalls::MemoryAccess;
80use reverie::syscalls::Sysno;
81use serde::Deserialize;
82use serde::Serialize;
83use tracing::debug;
84use tracing::error;
85use tracing::info;
86use tracing::trace;
87use tracing::warn;
88
89use crate::config::Config;
90#[cfg(test)]
91use crate::consts::ROOT_DETPID;
92use crate::fd::SharedOpenFileError;
93use crate::ivar::Ivar;
94use crate::preemptions::ThreadHistory;
95use crate::preemptions::boundary_recording::HistoryReader;
96use crate::procfs_inode;
97use crate::procfs_inode::ProcfsName;
98use crate::record_or_replay::RecordOrReplay;
99use crate::resources::ChaosEpochTransition;
100use crate::resources::ExternalOpId;
101use crate::resources::Permission;
102use crate::resources::ResourceID;
103use crate::resources::Resources;
104use crate::scheduler::AdmitIntent;
105use crate::scheduler::AdmitSide;
106use crate::scheduler::ConsumeResult;
107use crate::scheduler::DEFAULT_PRIORITY;
108use crate::scheduler::ExecReconnect;
109use crate::scheduler::FutexSignalWatch;
110use crate::scheduler::HostTimedSignalScope;
111use crate::scheduler::MaybePrintStack;
112use crate::scheduler::OwnProcessSignalInputs;
113use crate::scheduler::Priority;
114use crate::scheduler::SchedResponse;
115use crate::scheduler::SchedValue;
116use crate::scheduler::Scheduler;
117use crate::scheduler::ThreadNextTurn;
118use crate::scheduler::entropy_to_priority;
119use crate::scheduler::parked::*;
120use crate::scheduler::process_signal_receiver::ProcessSignalReceiver;
121use crate::scheduler::real_timer::DequeueAck;
122use crate::scheduler::real_timer::ItimerSnapshot;
123use crate::scheduler::real_timer::TimerFailure;
124use crate::scheduler::runqueue::FIRST_PRIORITY;
125use crate::scheduler::runqueue::LAST_PRIORITY;
126use crate::scheduler::runqueue::REPLAY_DEFERRED_PRIORITY;
127use crate::scheduler::runqueue::REPLAY_FOREGROUND_PRIORITY;
128use crate::scheduler::runqueue::is_ordinary_priority;
129use crate::scheduler::sched_loop;
130use crate::scheduler::sched_loop_external;
131use crate::scheduler::timed_waiters::SignalTarget;
132use crate::shared_open_files::OpenFileControl;
133use crate::shared_open_files::OpenFileControlReply;
134use crate::shared_open_files::SharedOpenFileStore;
135use crate::tool_local::Detcore;
136use crate::tool_local::ExecFdBlockingOverrides;
137use crate::tool_local::ExecFdState;
138use crate::tool_local::RobustListWake;
139use crate::types::*;
140
141pub(crate) async fn yield_once() {
142 let mut yielded = false;
143 std::future::poll_fn(|context| {
144 if yielded {
145 Poll::Ready(())
146 } else {
147 yielded = true;
148 context.waker().wake_by_ref();
149 Poll::Pending
150 }
151 })
152 .await;
153}
154
155#[derive(Debug)]
156struct InodePool {
157 inodes: HashMap<RawInode, DetInode>,
159 detinodes_info: HashMap<DetInode, DetInodeInfo>,
160 next_inode: u64,
166 pending_mtimes: HashMap<RawInode, LogicalTime>,
172 retired: HashMap<RawInode, DetInode>,
175 retired_unnumbered: HashMap<RawInode, Option<LogicalTime>>,
181 retired_at: HashMap<RawInode, u64>,
185 retirements: u64,
188 generations: HashMap<RawInode, Vec<Generation>>,
193 #[cfg(test)]
196 listed_probes: u64,
197 reused: u64,
201 procfs_keys: HashMap<RawInode, RawInode>,
205 incarnations: HashMap<i32, Vec<u64>>,
209 threads_counted: u64,
212 rendered_file_states: HashMap<u64, crate::procfs::listing::RenderedFileState>,
216 placeholders: Vec<(
220 crate::procfs::fd_directory::PlaceholderDirectory,
221 OpenFileId,
222 )>,
223}
224
225#[derive(PartialEq, Debug, Eq, Clone, Copy, Serialize, Deserialize)]
228pub enum InodeSighting {
229 Descriptor,
233 Name,
238 Listed(u64),
245}
246
247#[derive(PartialEq, Debug, Eq, Clone, Copy, Default, Serialize, Deserialize)]
250pub struct SnapshotMark {
251 pub retirements: u64,
254 pub threads_counted: u64,
258}
259
260#[derive(Debug)]
263struct Generation {
264 number: Option<DetInode>,
266 retired: Option<u64>,
268 pending: Option<LogicalTime>,
270}
271
272#[derive(Debug)]
274struct DetInodeInfo {
275 mtime: Option<LogicalTime>,
282}
283
284pub const CANONICAL_FILE_MTIME_SECONDS: [i64; 2] = [0, 1];
304
305#[derive(PartialEq, Debug, Eq, Clone, Copy, Serialize, Deserialize)]
308pub enum ObservedMtime {
309 Unobserved,
311 HostSpecific,
313 Canonical(LogicalTime),
315}
316
317impl ObservedMtime {
318 pub fn from_host_mtime(tv_sec: i64, tv_nsec: i64) -> Self {
320 match u64::try_from(tv_sec) {
321 Ok(secs) if tv_nsec == 0 && CANONICAL_FILE_MTIME_SECONDS.contains(&tv_sec) => {
322 ObservedMtime::Canonical(LogicalTime::from_secs(secs))
323 }
324 _ => ObservedMtime::HostSpecific,
325 }
326 }
327
328 fn first_seen_mtime(self, epoch: LogicalTime) -> Option<LogicalTime> {
332 match self {
333 ObservedMtime::Unobserved => None,
334 ObservedMtime::HostSpecific => Some(epoch),
335 ObservedMtime::Canonical(mtime) => Some(mtime),
336 }
337 }
338}
339
340struct ChildRegistration {
345 parent_dettid: DetTid,
346 parent_detpid: DetPid,
347 child_dettid: DetTid,
348 child_tid_addr: usize,
349 flags: Option<CloneFlags>,
350 exit_signal: libc::c_int,
351 physical_ids: Option<(i32, i32)>,
352 maybe_priority: Option<Priority>,
353 parent_is_kernel_blocked: bool,
354}
355
356#[derive(Debug)]
357struct PendingInheritedInput {
358 lease: reverie::InheritedInputLease,
359 root: Option<(DetTid, MmId)>,
360}
361
362#[derive(Clone, Debug, PartialEq, Eq)]
363struct PendingExecState {
364 caller: DetTid,
365 process: DetPid,
366 mm: MmId,
367 fd_blocking: ExecFdState,
368}
369
370pub struct BackendFailureCleanup {
372 pub scheduler: Result<(), tokio::task::JoinError>,
374 pub preemption_recording: Result<(), String>,
376}
377
378#[derive(Clone, Copy)]
379struct RpcIncarnation {
380 dettid: DetTid,
381 mm: MmId,
382}
383
384impl Default for InodePool {
385 fn default() -> Self {
386 InodePool::new()
387 }
388}
389
390impl InodePool {
391 fn new() -> Self {
392 InodePool {
393 inodes: HashMap::new(),
394 detinodes_info: HashMap::new(),
395 next_inode: crate::consts::DET_INODE_OFFSET.as_raw(),
398 pending_mtimes: HashMap::new(),
399 retired: HashMap::new(),
400 retired_unnumbered: HashMap::new(),
401 retired_at: HashMap::new(),
402 retirements: 0,
403 generations: HashMap::new(),
404 #[cfg(test)]
405 listed_probes: 0,
406 reused: 0,
407 procfs_keys: HashMap::new(),
408 incarnations: HashMap::new(),
409 threads_counted: 0,
410 rendered_file_states: HashMap::new(),
411 placeholders: Vec::new(),
412 }
413 }
414
415 fn register_placeholder(
427 &mut self,
428 directory: crate::procfs::fd_directory::PlaceholderDirectory,
429 open_file: OpenFileId,
430 ) -> Option<u64> {
431 let tag = crate::procfs::fd_directory::tag(self.placeholders.len())?;
432 self.placeholders.push((directory, open_file));
433 Some(tag)
434 }
435
436 fn placeholder(
439 &self,
440 tag: u64,
441 ) -> Option<(
442 crate::procfs::fd_directory::PlaceholderDirectory,
443 OpenFileId,
444 )> {
445 let index = crate::procfs::fd_directory::tag_index(tag)?;
446 self.placeholders.get(index).copied()
447 }
448
449 fn incarnation(&self, tid: i32) -> u32 {
453 self.incarnations.get(&tid).map_or(0, |counted| {
454 u32::try_from(counted.len()).unwrap_or(u32::MAX)
455 })
456 }
457
458 fn procfs_task_answer(
463 &self,
464 named: DetTid,
465 thread: Option<DetTid>,
466 (exists, without_mm): (bool, bool),
467 ) -> crate::procfs::listing::ProcfsTaskAnswer {
468 crate::procfs::listing::ProcfsTaskAnswer {
469 exists,
470 without_mm,
471 process_incarnation: self.incarnation(named.as_raw()),
472 thread_incarnation: thread.map(|thread| self.incarnation(thread.as_raw())),
473 mm: None,
474 runtime_private_memory: None,
475 }
476 }
477
478 fn rendered_file_state(
498 &mut self,
499 inode: u64,
500 change: crate::procfs::listing::RenderedFileChange,
501 ) -> crate::procfs::listing::RenderedFileState {
502 use crate::procfs::listing::RenderedFileChange;
503 let state = match change {
504 RenderedFileChange::Read => {
505 return self
506 .rendered_file_states
507 .get(&inode)
508 .copied()
509 .unwrap_or_default();
510 }
511 _ => self.rendered_file_states.entry(inode).or_default(),
512 };
513 match change {
514 RenderedFileChange::Read => {}
515 RenderedFileChange::Mtime(seconds, nanoseconds) => {
516 state.mtime = Some((seconds, nanoseconds));
517 }
518 RenderedFileChange::WriteHint(hint) => state.write_hint = hint,
519 }
520 *state
521 }
522
523 fn procfs_key(&mut self, host: RawInode, path: RawInode) -> RawInode {
535 let key = match self.procfs_keys.get(&host) {
536 Some(key) => *key,
537 None if self.inodes.contains_key(&host)
538 || self.retired.contains_key(&host)
539 || self.retired_unnumbered.contains_key(&host) =>
540 {
541 host
542 }
543 None => self.procfs_keys.get(&path).copied().unwrap_or(host),
544 };
545 self.procfs_keys.insert(host, key);
546 self.procfs_keys.insert(path, key);
547 if key != host
550 && let Some(mtime) = self.pending_mtimes.remove(&host)
551 {
552 self.set_mtime(key, mtime);
553 }
554 key
555 }
556
557 fn key(&self, raw_inode: RawInode) -> RawInode {
560 self.procfs_keys
561 .get(&raw_inode)
562 .copied()
563 .unwrap_or(raw_inode)
564 }
565
566 fn count_incarnation(&mut self, tid: i32) {
571 self.threads_counted += 1;
572 self.incarnations
573 .entry(tid)
574 .or_default()
575 .push(self.threads_counted);
576 }
577
578 fn procfs_path_key(&self, name: &ProcfsName) -> RawInode {
585 name.tasks.iter().fold(name.path, |key, task| {
586 let counted = self.incarnations.get(task).map_or(&[][..], Vec::as_slice);
587 let incarnation = match name.threads_counted {
588 Some(snapshot) => counted.partition_point(|at| *at <= snapshot),
589 None => counted.len(),
590 };
591 match u32::try_from(incarnation).unwrap_or(u32::MAX) {
592 0 => key,
593 incarnation => procfs_inode::with_incarnation(key, *task, incarnation),
594 }
595 })
596 }
597
598 fn add_sighted_inode(
635 &mut self,
636 raw_inode: RawInode,
637 observed: ObservedMtime,
638 epoch: LogicalTime,
639 sighting: InodeSighting,
640 ) -> (DetInode, LogicalTime) {
641 let raw_inode = self.key(raw_inode);
642 let ordinal = self.next_inode;
643 self.next_inode += 1;
644 match sighting {
645 InodeSighting::Name => self.forget_retired(raw_inode),
646 InodeSighting::Listed(snapshot) => {
647 if let Some(answer) = self.listed_generation(raw_inode, snapshot, ordinal) {
648 let mtime = self
649 .detinodes_info
650 .get_mut(&answer)
651 .expect("Internal invariant broken, det_ino missing entry");
652 if mtime.mtime.is_none() {
653 mtime.mtime = observed.first_seen_mtime(epoch);
654 }
655 return (answer, mtime.mtime.unwrap_or(epoch));
656 }
657 if self
658 .retired_at
659 .get(&raw_inode)
660 .is_some_and(|retired| *retired < snapshot)
661 {
662 self.forget_retired(raw_inode);
663 }
664 }
665 InodeSighting::Descriptor => {}
666 }
667 let dino = match self
668 .inodes
669 .get(&raw_inode)
670 .or_else(|| self.retired.get(&raw_inode))
671 {
672 Some(dino) => *dino,
673 None => {
674 let new = DetInode::mint(ordinal);
679 let mtime = match self.retired_unnumbered.remove(&raw_inode) {
682 Some(mtime) => {
683 self.retired.insert(raw_inode, new);
684 if let Some(last) = self
685 .generations
686 .get_mut(&raw_inode)
687 .and_then(|generations| generations.last_mut())
688 {
689 last.number = Some(new);
690 }
691 mtime
692 }
693 None => {
694 assert!(self.inodes.insert(raw_inode, new).is_none());
695 self.generations
696 .entry(raw_inode)
697 .or_default()
698 .push(Generation {
699 number: Some(new),
700 retired: None,
701 pending: None,
702 });
703 self.pending_mtimes.remove(&raw_inode)
704 }
705 };
706 let prev = self.detinodes_info.insert(new, DetInodeInfo { mtime });
707 assert!(prev.is_none()); new
709 }
710 };
711 let info = self
712 .detinodes_info
713 .get_mut(&dino)
714 .expect("Internal invariant broken, det_ino missing entry");
715 if info.mtime.is_none() {
716 info.mtime = observed.first_seen_mtime(epoch);
717 }
718 (dino, info.mtime.unwrap_or(epoch))
719 }
720
721 fn add_inode(
723 &mut self,
724 raw_inode: RawInode,
725 observed: ObservedMtime,
726 epoch: LogicalTime,
727 ) -> (DetInode, LogicalTime) {
728 self.add_sighted_inode(raw_inode, observed, epoch, InodeSighting::Descriptor)
729 }
730
731 fn set_mtime(&mut self, raw_inode: RawInode, mtime: LogicalTime) {
739 let raw_inode = self.key(raw_inode);
740 match self
744 .inodes
745 .get(&raw_inode)
746 .or_else(|| self.retired.get(&raw_inode))
747 {
748 Some(dino) => {
749 self.detinodes_info
750 .get_mut(dino)
751 .expect("Invariant violation: det inode missing from map.")
753 .mtime = Some(mtime);
754 }
755 None => match self.retired_unnumbered.get_mut(&raw_inode) {
756 Some(retired) => {
757 *retired = Some(mtime);
758 if let Some(last) = self
762 .generations
763 .get_mut(&raw_inode)
764 .and_then(|generations| generations.last_mut())
765 {
766 last.pending = Some(mtime);
767 }
768 }
769 None => {
770 self.pending_mtimes.insert(raw_inode, mtime);
771 }
772 },
773 }
774 }
775
776 fn retire(&mut self, raw_inode: RawInode) {
783 let raw_inode = self.key(raw_inode);
784 let stamp = self.retirements;
785 self.retired_at.insert(raw_inode, stamp);
786 self.retirements += 1;
787 let generations = self.generations.entry(raw_inode).or_default();
788 match self.inodes.remove(&raw_inode) {
789 Some(dino) => {
790 self.retired.insert(raw_inode, dino);
791 match generations.last_mut() {
792 Some(last) if last.number == Some(dino) && last.retired.is_none() => {
793 last.retired = Some(stamp);
794 }
795 _ => generations.push(Generation {
796 number: Some(dino),
797 retired: Some(stamp),
798 pending: None,
799 }),
800 }
801 }
802 None => {
806 let pending = self.pending_mtimes.remove(&raw_inode);
807 self.retired_unnumbered.insert(raw_inode, pending);
808 generations.push(Generation {
809 number: None,
810 retired: Some(stamp),
811 pending,
812 });
813 }
814 }
815 }
816
817 fn listed_generation(
829 &mut self,
830 raw_inode: RawInode,
831 snapshot: u64,
832 ordinal: u64,
833 ) -> Option<DetInode> {
834 let generations = self.generations.get_mut(&raw_inode)?;
835 let last_retired = match generations.last()?.retired {
845 Some(retired) => Some(retired),
846 None => generations.iter().rev().nth(1).and_then(|g| g.retired),
847 };
848 if last_retired.is_none_or(|retired| snapshot > retired) {
849 return None;
850 }
851 #[cfg(test)]
852 let mut probes = 0;
853 let index = generations.partition_point(|generation| {
854 #[cfg(test)]
855 {
856 probes += 1;
857 }
858 generation.retired.is_some_and(|retired| retired < snapshot)
859 });
860 #[cfg(test)]
861 {
862 self.listed_probes += probes;
863 }
864 generations.get(index)?.retired?;
867 let current = index + 1 == generations.len();
868 let generation = &mut generations[index];
869 if let Some(number) = generation.number {
870 return Some(number);
871 }
872 let new = DetInode::mint(ordinal);
873 generation.number = Some(new);
874 let mut mtime = generation.pending;
875 if current && let Some(pending) = self.retired_unnumbered.remove(&raw_inode) {
878 self.retired.insert(raw_inode, new);
879 mtime = pending;
880 }
881 let prev = self.detinodes_info.insert(new, DetInodeInfo { mtime });
882 assert!(prev.is_none()); Some(new)
884 }
885
886 fn forget_retired(&mut self, raw_inode: RawInode) {
891 let raw_inode = self.key(raw_inode);
892 self.retired_at.remove(&raw_inode);
893 if self.retired.remove(&raw_inode).is_some() {
896 self.reused += 1;
897 }
898 if self.retired_unnumbered.remove(&raw_inode).is_some() {
899 self.reused += 1;
900 }
901 }
902}
903
904#[derive(Debug)]
930struct DevicePool {
931 devices: HashMap<u64, u64>,
932 next_device: u64,
933}
934
935#[derive(Debug)]
953enum MountIdPool {
954 Uninitialized,
955 Invalid,
956 Ready {
957 mount_ids: BTreeMap<u64, u64>,
960 assignment_order: Vec<u64>,
962 mountinfo_order: Vec<u64>,
964 allow_visible_subsets: bool,
965 },
966}
967
968pub struct MountIdentityProvenance {
971 pub mountinfo_order: Vec<u64>,
974 pub assignment_order: Vec<u64>,
976}
977
978impl MountIdPool {
979 fn from_config(mount_ids: &[u64], captured: bool, assignment_order: &[u64]) -> Self {
980 if !captured {
981 if !mount_ids.is_empty() || !assignment_order.is_empty() {
982 return Self::Invalid;
983 }
984 return Self::Uninitialized;
985 }
986 Self::from_orders(mount_ids, assignment_order, true).unwrap_or(Self::Invalid)
987 }
988
989 fn from_orders(
990 mount_ids: &[u64],
991 assignment_order: &[u64],
992 allow_visible_subsets: bool,
993 ) -> Option<Self> {
994 if !detcore_model::procfs::mount_identity_provenance_is_well_formed(
999 mount_ids,
1000 assignment_order,
1001 ) {
1002 return None;
1003 }
1004 let mut mappings = BTreeMap::new();
1005 for (index, raw) in assignment_order.iter().enumerate() {
1006 let virtual_mount_id = u64::try_from(index).ok()?.checked_add(1)?;
1007 if mappings.insert(*raw, virtual_mount_id).is_some() {
1008 return None;
1009 }
1010 }
1011 Some(Self::Ready {
1012 mount_ids: mappings,
1013 assignment_order: assignment_order.to_vec(),
1014 mountinfo_order: mount_ids.to_vec(),
1015 allow_visible_subsets,
1016 })
1017 }
1018
1019 fn validate_mountinfo_order(&mut self, mountinfo_order: &[u64]) -> bool {
1020 if matches!(self, Self::Uninitialized) {
1021 *self = Self::from_orders(mountinfo_order, &[], false).unwrap_or(Self::Invalid);
1022 }
1023 let Self::Ready {
1024 mountinfo_order: expected,
1025 allow_visible_subsets,
1026 ..
1027 } = self
1028 else {
1029 return false;
1030 };
1031 if *allow_visible_subsets {
1032 mount_ids_are_ordered_subset(mountinfo_order, expected)
1033 } else {
1034 mountinfo_order == expected
1035 }
1036 }
1037
1038 fn assign(&mut self, raw_mount_id: u64) -> Option<u64> {
1041 let Self::Ready {
1042 mount_ids,
1043 assignment_order,
1044 ..
1045 } = self
1046 else {
1047 return None;
1048 };
1049 if let Some(virtual_mount_id) = mount_ids.get(&raw_mount_id) {
1050 return Some(*virtual_mount_id);
1051 }
1052 let virtual_mount_id = u64::try_from(assignment_order.len()).ok()?.checked_add(1)?;
1053 mount_ids.insert(raw_mount_id, virtual_mount_id);
1054 assignment_order.push(raw_mount_id);
1055 Some(virtual_mount_id)
1056 }
1057
1058 fn determinize(&mut self, raw_mount_id: u64, mountinfo_order: Option<&[u64]>) -> Option<u64> {
1059 if raw_mount_id == 0 {
1062 return Some(0);
1063 }
1064 if let Some(order) = mountinfo_order {
1065 if !self.validate_mountinfo_order(order) {
1066 return None;
1067 }
1068 } else if matches!(self, Self::Uninitialized) {
1069 return None;
1070 }
1071 self.assign(raw_mount_id)
1072 }
1073
1074 fn resolve_mountinfo(
1083 &mut self,
1084 mountinfo_order: &[u64],
1085 observation_order: &[u64],
1086 ) -> Option<Vec<u64>> {
1087 let entries = mountinfo_order.iter().copied().collect::<BTreeSet<_>>();
1088 let mut observed = BTreeSet::new();
1089 if entries.len() != mountinfo_order.len()
1090 || observation_order.len() != mountinfo_order.len()
1091 || !observation_order
1092 .iter()
1093 .all(|raw| entries.contains(raw) && observed.insert(*raw))
1094 {
1095 return None;
1096 }
1097 if !self.validate_mountinfo_order(mountinfo_order) {
1098 return None;
1099 }
1100 observation_order
1101 .iter()
1102 .map(|raw| self.assign(*raw))
1103 .collect()
1104 }
1105
1106 fn provenance(&self) -> Result<Option<MountIdentityProvenance>, &'static str> {
1107 match self {
1108 Self::Uninitialized => Ok(None),
1109 Self::Invalid => Err("mount identity provenance is invalid"),
1110 Self::Ready {
1111 mountinfo_order,
1112 assignment_order,
1113 ..
1114 } => Ok(Some(MountIdentityProvenance {
1115 mountinfo_order: mountinfo_order.clone(),
1116 assignment_order: assignment_order.clone(),
1117 })),
1118 }
1119 }
1120}
1121
1122impl Default for DevicePool {
1123 fn default() -> Self {
1124 DevicePool::new()
1125 }
1126}
1127
1128impl DevicePool {
1129 fn new() -> Self {
1130 DevicePool {
1133 devices: HashMap::new(),
1134 next_device: 1,
1135 }
1136 }
1137
1138 fn for_this_mount_namespace() -> Self {
1146 let mut pool = Self::new();
1147 match std::fs::metadata("/proc") {
1148 Ok(proc) => {
1149 pool.devices.insert(
1150 std::os::unix::fs::MetadataExt::dev(&proc),
1151 procfs_inode::VIRTUAL_PROC_DEVICE,
1152 );
1153 }
1154 Err(error) => warn!(
1155 "[detcore] /proc could not be read, so its device is numbered when first seen: {error}"
1156 ),
1157 }
1158 pool
1159 }
1160
1161 fn determinize(&mut self, raw_device: u64) -> u64 {
1165 match self.devices.get(&raw_device) {
1166 Some(dev) => *dev,
1167 None => {
1168 if self.next_device == procfs_inode::VIRTUAL_PROC_DEVICE {
1169 self.next_device += 1;
1170 }
1171 let new = self.next_device;
1172 self.next_device += 1;
1173 self.devices.insert(raw_device, new);
1174 new
1175 }
1176 }
1177 }
1178}
1179
1180#[derive(Debug)]
1185pub struct GlobalState {
1186 sched: Arc<Mutex<Scheduler>>,
1187 #[cfg(feature = "physical-mm-test-support")]
1188 initial_mm_probe: Mutex<Option<Arc<initial_mm_probe::Probe>>>,
1189
1190 inodes: Arc<Mutex<InodePool>>,
1191
1192 devices: Arc<Mutex<DevicePool>>,
1195
1196 mount_ids: Mutex<MountIdPool>,
1198
1199 next_port: AtomicU16,
1201
1202 next_guest_id: AtomicI32,
1207
1208 host_id_tasks: AtomicU64,
1214
1215 used_ports: Mutex<HashSet<u16>>,
1217
1218 unsupported_syscalls: Mutex<BTreeSet<String>>,
1220
1221 syscall_observation_gaps: Mutex<BTreeSet<SyscallObservationGap>>,
1225 #[cfg(target_arch = "aarch64")]
1228 store_exclusive: Mutex<crate::store_exclusive::RunTally>,
1229
1230 announced_handlers_not_installed: Mutex<BTreeSet<i32>>,
1233 announced_refused_signal_controls: Mutex<(u64, bool)>,
1237 host_diskstats: std::sync::OnceLock<Result<Vec<u8>, i32>>,
1243 procfs_sequences: Mutex<BTreeMap<OpenFileId, crate::procfs::ProcfsSequence>>,
1250
1251 policy_refusal: Mutex<Option<String>>,
1254
1255 host_inputs: Mutex<Vec<HostInputRecord>>,
1260
1261 host_mutations: Mutex<BTreeSet<String>>,
1266
1267 replay_host_inputs: Option<Mutex<crate::host_inputs::ReplayHostInputs>>,
1272
1273 host_input_sources: Mutex<Vec<(usize, PathBuf, HostFileIdentity)>>,
1280
1281 host_inputs_settled: AtomicBool,
1283
1284 shared_open_files: Arc<Mutex<SharedOpenFileStore>>,
1288
1289 unsupported_syscall_report_fd: Option<Mutex<File>>,
1291
1292 open_file_to_port: Mutex<HashMap<OpenFileId, u16>>,
1294
1295 inotify_cookies: Mutex<crate::syscalls::inotify::CookieTable>,
1298
1299 port_start_range: AtomicU16,
1300 port_end_range: AtomicU16,
1301
1302 past_first_execve: AtomicBool,
1304 initial_descriptor_image_complete: AtomicBool,
1306 inherited_input: Mutex<Option<PendingInheritedInput>>,
1309 mapping_capture_run: Option<reverie::MappingCaptureRun>,
1311 mapping_capture_pending: Mutex<Option<mapping_capture::Pending>>,
1312
1313 pending_exec_states: Arc<Mutex<BTreeMap<DetPid, PendingExecState>>>,
1318 native_exec_registry: OnceLock<Mutex<native_exec::NativeExecRegistry>>,
1320 completed_exec_transfers: Arc<Mutex<BTreeMap<DetPid, exec_identity::ExecTransferReceipt>>>,
1325
1326 post_exec_fd_blocking: Arc<Mutex<BTreeMap<DetTid, ExecFdState>>>,
1328
1329 exec_images: Arc<Mutex<BTreeMap<DetPid, crate::exec_record::ExecRecord>>>,
1334 process_auxv: Arc<Mutex<ProcessAuxvTable>>,
1337 lock_ledger: Mutex<crate::procfs::locks::LockLedger>,
1342 held_tables: Mutex<BTreeMap<DetTid, std::sync::Weak<Mutex<crate::tool_local::FileMetadata>>>>,
1353 root_dettid: OnceLock<DetTid>,
1356 root_image_marked: AtomicBool,
1357 launcher_identity: OnceLock<Option<(u64, u64)>>,
1359
1360 sched_handle: Option<tokio::task::JoinHandle<()>>,
1361
1362 global_time: Arc<Mutex<GlobalTime>>,
1371
1372 cfg: Config,
1374
1375 preemptions_to_replay: Option<HistoryReader>,
1377
1378 realtime_start: SystemTime,
1380
1381 stopless_slice_ends_at_start: u64,
1384
1385 network: Option<Mutex<NetworkEngine>>,
1389}
1390
1391#[derive(Clone, Debug, Default)]
1396struct ProcessExitCleanup {
1397 shared_open_files: Arc<Mutex<SharedOpenFileStore>>,
1398 pending_exec_states: Arc<Mutex<BTreeMap<DetPid, PendingExecState>>>,
1399 completed_exec_transfers: Arc<Mutex<BTreeMap<DetPid, exec_identity::ExecTransferReceipt>>>,
1400 post_exec_fd_blocking: Arc<Mutex<BTreeMap<DetTid, ExecFdState>>>,
1401 exec_images: Arc<Mutex<BTreeMap<DetPid, crate::exec_record::ExecRecord>>>,
1402 process_auxv: Arc<Mutex<ProcessAuxvTable>>,
1403}
1404
1405#[derive(Debug, Default)]
1432pub(crate) struct ProcessAuxvTable {
1433 processes: BTreeMap<DetPid, Vec<u8>>,
1435 cmdline_areas: BTreeMap<DetPid, crate::procfs::CmdlineArea>,
1441 roots: FsRoots,
1443 kernel_file_warned: [bool; 2],
1447}
1448
1449#[derive(Debug, Default)]
1456struct FsRoots {
1457 groups: BTreeMap<DetPid, u64>,
1459 changed: BTreeSet<u64>,
1461 next: u64,
1463}
1464
1465impl FsRoots {
1466 fn new_group(&mut self, changed: bool) -> u64 {
1467 let group = self.next;
1468 self.next += 1;
1469 if changed {
1470 self.changed.insert(group);
1471 }
1472 group
1473 }
1474
1475 fn clone_task(&mut self, parent: DetPid, child: DetPid, shares_fs: bool) {
1476 let group = match self.groups.get(&parent).copied() {
1477 Some(group) if shares_fs => group,
1478 parent_group => {
1479 let changed = parent_group.is_some_and(|group| self.changed.contains(&group));
1480 let group = self.new_group(changed);
1481 if shares_fs {
1482 self.groups.insert(parent, group);
1485 }
1486 group
1487 }
1488 };
1489 self.groups.insert(child, group);
1490 }
1491
1492 fn mark_changed(&mut self, task: DetPid) {
1493 let group = match self.groups.get(&task).copied() {
1494 Some(group) => group,
1495 None => {
1496 let group = self.new_group(false);
1497 self.groups.insert(task, group);
1498 group
1499 }
1500 };
1501 self.changed.insert(group);
1502 }
1503
1504 fn changed(&self, task: DetPid) -> bool {
1505 self.groups
1506 .get(&task)
1507 .is_some_and(|group| self.changed.contains(group))
1508 }
1509
1510 fn transfer(&mut self, former: DetPid, current: DetPid) {
1511 if let Some(group) = self.groups.remove(&former) {
1512 self.groups.insert(current, group);
1513 }
1514 }
1515
1516 fn forget(&mut self, task: DetPid) {
1517 if let Some(group) = self.groups.remove(&task)
1518 && !self.groups.values().any(|other| *other == group)
1519 {
1520 self.changed.remove(&group);
1521 }
1522 }
1523}
1524
1525#[derive(PartialEq, Debug, Eq, Clone, Serialize, Deserialize)]
1527pub struct ProcessAuxv {
1528 pub process: DetPid,
1530 pub bytes: Vec<u8>,
1532}
1533
1534#[derive(PartialEq, Debug, Eq, Clone, Copy, Serialize, Deserialize)]
1537pub enum AuxvFromKernel {
1538 NoInitialStackPointer,
1540 Implausible,
1542}
1543
1544impl ProcessAuxvTable {
1545 pub(crate) fn record(&mut self, process: DetPid, bytes: Vec<u8>) {
1548 self.processes.insert(process, bytes);
1549 }
1550
1551 pub(crate) fn current(&self, process: DetPid) -> Option<ProcessAuxv> {
1553 Some(ProcessAuxv {
1554 process,
1555 bytes: self.processes.get(&process)?.clone(),
1556 })
1557 }
1558
1559 pub(crate) fn inherit(&mut self, parent: DetPid, child: DetPid) {
1563 match self.processes.get(&parent).cloned() {
1564 Some(bytes) => self.processes.insert(child, bytes),
1565 None => self.processes.remove(&child),
1566 };
1567 match self.cmdline_areas.get(&parent).copied() {
1568 Some(area) => self.cmdline_areas.insert(child, area),
1569 None => self.cmdline_areas.remove(&child),
1570 };
1571 }
1572
1573 pub(crate) fn record_cmdline(
1579 &mut self,
1580 process: DetPid,
1581 area: Option<crate::procfs::CmdlineArea>,
1582 ) {
1583 match area {
1584 Some(area) => self.cmdline_areas.insert(process, area),
1585 None => self.cmdline_areas.remove(&process),
1586 };
1587 }
1588
1589 pub(crate) fn current_cmdline(&self, process: DetPid) -> Option<crate::procfs::CmdlineArea> {
1592 self.cmdline_areas.get(&process).copied()
1593 }
1594
1595 pub(crate) fn clone_root(&mut self, parent: DetPid, child: DetPid, shares_fs: bool) {
1599 self.roots.clone_task(parent, child, shares_fs);
1600 }
1601
1602 pub(crate) fn mark_changed_root(&mut self, task: DetPid) {
1605 self.roots.mark_changed(task);
1606 }
1607
1608 pub(crate) fn root_changed(&self, task: DetPid) -> bool {
1610 self.roots.changed(task)
1611 }
1612
1613 pub(crate) fn transfer_root(&mut self, former: DetPid, current: DetPid) {
1620 self.roots.transfer(former, current);
1621 }
1622
1623 pub(crate) fn forget(&mut self, process: DetPid) {
1628 self.processes.remove(&process);
1629 }
1630
1631 pub(crate) fn forget_process(&mut self, process: DetPid) {
1633 self.processes.remove(&process);
1634 self.cmdline_areas.remove(&process);
1635 self.roots.forget(process);
1636 }
1637
1638 #[cfg(test)]
1640 pub(crate) fn len(&self) -> usize {
1641 self.processes.len()
1642 }
1643
1644 pub(crate) fn first_kernel_file(&mut self, why: AuxvFromKernel) -> bool {
1647 let index = match why {
1648 AuxvFromKernel::NoInitialStackPointer => 0,
1649 AuxvFromKernel::Implausible => 1,
1650 };
1651 !std::mem::replace(&mut self.kernel_file_warned[index], true)
1652 }
1653}
1654
1655impl ProcessExitCleanup {
1656 #[cfg_attr(not(test), allow(dead_code))]
1661 fn complete_observed_locked(
1662 &self,
1663 sched: &mut Scheduler,
1664 watch: &Arc<crate::scheduler::ProcessExitWatch>,
1665 ) -> Option<(bool, Option<String>)> {
1666 if !sched.process_exit_watch_is_current(watch) {
1667 return None;
1668 }
1669 sched.finish_process_exit_watch(watch);
1670 Some(self.complete_locked(sched, watch.process, true))
1671 }
1672
1673 fn complete_locked(
1674 &self,
1675 sched: &mut Scheduler,
1676 process: DetPid,
1677 consume_unreported: bool,
1678 ) -> (bool, Option<String>) {
1679 let loss = (consume_unreported && sched.has_unreported_exit(process)).then(|| {
1680 let reason = format!(
1681 "process {process} exited without deregistering; its exit was not scheduled"
1682 );
1683 crate::detlog::record_determinism_loss(&reason);
1686 reason
1687 });
1688 self.pending_exec_states.lock().unwrap().remove(&process);
1689 self.completed_exec_transfers
1690 .lock()
1691 .unwrap()
1692 .remove(&process);
1693 self.post_exec_fd_blocking.lock().unwrap().remove(&process);
1694 self.exec_images.lock().unwrap().remove(&process);
1695 self.process_auxv.lock().unwrap().forget_process(process);
1696 if consume_unreported {
1697 let consumed = sched.consume_unreported_exit(process);
1698 debug_assert_eq!(consumed, loss.is_some());
1699 }
1700 self.shared_open_files
1701 .lock()
1702 .unwrap()
1703 .end_leases_of(process);
1704 (sched.complete_physical_process_exit(process), loss)
1705 }
1706}
1707
1708impl Default for GlobalState {
1709 fn default() -> Self {
1710 panic!("Detcore GlobalState Default impl should not be called");
1713 }
1714}
1715
1716impl Drop for GlobalState {
1717 fn drop(&mut self) {
1718 if let Some(message) =
1720 format_unsupported_syscall_warning(&self.unsupported_syscalls.lock().unwrap())
1721 {
1722 warn!("{}", message);
1723 }
1724 info!("detcore shut down, destroying global state");
1725 }
1726}
1727
1728impl GlobalState {
1729 pub fn take_unsupported_syscalls(&mut self) -> BTreeSet<String> {
1733 std::mem::take(self.unsupported_syscalls.get_mut().unwrap())
1734 }
1735
1736 #[cfg(target_arch = "aarch64")]
1740 fn note_store_exclusive(
1741 &self,
1742 report: &crate::store_exclusive::Report,
1743 ) -> (Option<LogicalTime>, GlobalResponse) {
1744 self.store_exclusive.lock().unwrap().note(&self.cfg, report);
1745 (None, GlobalResponse::StoreExclusive(()))
1746 }
1747
1748 async fn lock_rpc_scheduler(
1753 &self,
1754 consuming_cleanup: bool,
1755 ) -> std::sync::MutexGuard<'_, Scheduler> {
1756 std::future::poll_fn(|_| {
1757 let sched = self.sched.lock().unwrap();
1758 if !consuming_cleanup && sched.backend_failed() {
1759 Poll::Pending
1760 } else {
1761 Poll::Ready(sched)
1762 }
1763 })
1764 .await
1765 }
1766
1767 pub fn retired_inodes(&self) -> Vec<DetInode> {
1772 let mut retired: Vec<DetInode> = self
1773 .inodes
1774 .lock()
1775 .unwrap()
1776 .retired
1777 .values()
1778 .copied()
1779 .collect();
1780 retired.sort();
1781 retired
1782 }
1783
1784 pub fn reused_inode_count(&self) -> u64 {
1788 self.inodes.lock().unwrap().reused
1789 }
1790
1791 pub fn mount_identity_provenance(
1796 &self,
1797 ) -> Result<Option<MountIdentityProvenance>, &'static str> {
1798 self.mount_ids.lock().unwrap().provenance()
1799 }
1800
1801 #[cfg(test)]
1804 pub(crate) fn for_tests(cfg: &Config) -> Self {
1805 Self::initialize(cfg, false)
1806 }
1807
1808 fn initialize(cfg: &Config, spawn_scheduler: bool) -> Self {
1809 crate::record_or_replay::admit_chosen_input_stream(cfg, reverie::ToolStreamRole::Plain);
1813 if let Some(refusal) = crate::preemptions::annotated_replay_mode_refusal(cfg) {
1814 crate::scheduler::exit_on_scheduler_refusal(&refusal);
1815 }
1816 let sched = Arc::new(Mutex::new(Scheduler::new(cfg)));
1817 let global_time = Arc::new(Mutex::new(GlobalTime::new(cfg)));
1818 let process_exit_cleanup = ProcessExitCleanup::default();
1819
1820 let preemptions_to_replay: Option<HistoryReader> =
1821 cfg.replay_preemptions_from.as_ref().map(|path| {
1822 let history =
1827 HistoryReader::read_for_replay(path, cfg.guest_ids).unwrap_or_else(|error| {
1828 if error
1829 .chain()
1830 .any(|cause| cause.is::<crate::preemptions::RecordAdmissionRefusal>())
1831 {
1832 crate::scheduler::exit_on_scheduler_refusal(&format!("{error:#}"));
1833 }
1834 panic!("{error:?}")
1835 });
1836 history
1837 .require_replay_mode(cfg.replay_edited_preemptions)
1838 .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error));
1839 #[cfg(target_arch = "aarch64")]
1842 crate::store_exclusive::warn_at_replay_start(history.store_exclusive_failures());
1843 if let Some(event) = history.replay_departed_at() {
1846 tracing::warn!(
1847 "HERMIT_INCOMPLETE_REPLAY_RECORD: explicit edited history exploration of {} \
1848 retains replay_departed_at={}; it does not certify faithful replay",
1849 path.display(), event,
1850 );
1851 if let Some(writer) = &mut sched.lock().unwrap().preemption_writer {
1852 writer.mark_replay_departed(event);
1853 }
1854 }
1855 sched
1859 .lock()
1860 .unwrap()
1861 .install_priority_checkpoints(history.callback_checkpoints())
1862 .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error));
1863 history
1864 });
1865 if preemptions_to_replay.is_none() && cfg.annotated_priority_replay {
1866 crate::scheduler::exit_on_scheduler_refusal(
1867 &"HERMIT_CALLBACK_CHECKPOINT_UNQUALIFIED_MODE: the run was prepared to replay a \
1868 callback-annotated priority history, but names no --replay-preemptions-from",
1869 );
1870 }
1871
1872 let handle = if cfg.sequentialize_threads && spawn_scheduler {
1873 info!("[scheduler] daemon task starting up, waiting for guest thread start..");
1880 Some(tokio::spawn(crate::scheduler::exit_on_scheduler_panic(
1881 sched_loop(sched.clone(), global_time.clone()),
1882 )))
1883 } else {
1884 None
1885 };
1886
1887 let replay_host_inputs = cfg
1890 .record_host_inputs
1891 .then(|| {
1892 preemptions_to_replay
1893 .as_ref()
1894 .and_then(HistoryReader::host_inputs)
1895 .cloned()
1896 .or_else(|| sched.lock().unwrap().recorded_host_inputs.clone())
1897 })
1898 .flatten()
1899 .map(|recorded| Mutex::new(crate::host_inputs::ReplayHostInputs::new(recorded)));
1900 let range = Self::read_port_range();
1901 let network = Self::network_engine(cfg).map(Mutex::new);
1902
1903 let unsupported_syscall_report_fd = cfg.unsupported_syscall_report_fd.and_then(|fd| {
1904 let duplicate = unsafe { libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, fd) };
1915 if duplicate == -1 {
1916 warn!(
1917 "failed to duplicate unsupported-syscall report fd {fd}: {}",
1918 std::io::Error::last_os_error()
1919 );
1920 None
1921 } else {
1922 Some(Mutex::new(unsafe { File::from_raw_fd(duplicate) }))
1924 }
1925 });
1926
1927 Self {
1928 sched,
1929 #[cfg(feature = "physical-mm-test-support")]
1930 initial_mm_probe: Mutex::new(None),
1931 next_port: AtomicU16::new(range[0]),
1932 next_guest_id: AtomicI32::new(
1933 cfg.guest_ids
1934 .base()
1935 .filter(|base| *base > 0)
1936 .and_then(|base| base.checked_add(1))
1937 .unwrap_or(0),
1938 ),
1939 host_id_tasks: AtomicU64::new(0),
1940 used_ports: Mutex::new(HashSet::new()),
1941 unsupported_syscalls: Mutex::new(BTreeSet::new()),
1942 syscall_observation_gaps: Mutex::new(BTreeSet::new()),
1943 #[cfg(target_arch = "aarch64")]
1944 store_exclusive: Mutex::new(Default::default()),
1945 announced_handlers_not_installed: Mutex::new(BTreeSet::new()),
1946 announced_refused_signal_controls: Mutex::new((0, false)),
1947 host_diskstats: std::sync::OnceLock::new(),
1948 procfs_sequences: Mutex::new(BTreeMap::new()),
1949 policy_refusal: Mutex::new(None),
1950 host_inputs: Mutex::new(Vec::new()),
1951 host_mutations: Mutex::new(BTreeSet::new()),
1952 replay_host_inputs,
1953 host_input_sources: Mutex::new(Vec::new()),
1954 host_inputs_settled: AtomicBool::new(false),
1955 shared_open_files: process_exit_cleanup.shared_open_files,
1956 unsupported_syscall_report_fd,
1957 port_start_range: AtomicU16::new(range[0]),
1958 port_end_range: AtomicU16::new(range[1]),
1959 open_file_to_port: Mutex::new(HashMap::new()),
1960 inotify_cookies: Mutex::new(Default::default()),
1961 past_first_execve: AtomicBool::new(false),
1962 initial_descriptor_image_complete: AtomicBool::new(false),
1963 inherited_input: Mutex::new(None),
1964 mapping_capture_run: None,
1965 mapping_capture_pending: Mutex::new(None),
1966 pending_exec_states: process_exit_cleanup.pending_exec_states,
1967 completed_exec_transfers: process_exit_cleanup.completed_exec_transfers,
1968 post_exec_fd_blocking: process_exit_cleanup.post_exec_fd_blocking,
1969 exec_images: process_exit_cleanup.exec_images,
1970 process_auxv: process_exit_cleanup.process_auxv,
1971 lock_ledger: Mutex::new(Default::default()),
1972 held_tables: Mutex::new(BTreeMap::new()),
1973 root_dettid: OnceLock::new(),
1974 root_image_marked: AtomicBool::new(false),
1975 launcher_identity: OnceLock::new(),
1976 native_exec_registry: OnceLock::new(),
1977 inodes: Arc::new(Mutex::new(InodePool::new())),
1978 devices: Arc::new(Mutex::new(if cfg.recordreplay_modes {
1981 DevicePool::new()
1982 } else {
1983 DevicePool::for_this_mount_namespace()
1984 })),
1985 mount_ids: Mutex::new(MountIdPool::from_config(
1986 &cfg.mountinfo_mount_ids,
1987 cfg.mountinfo_mount_ids_captured,
1988 &cfg.mount_id_assignment_order,
1989 )),
1990 sched_handle: handle,
1991 cfg: cfg.clone(),
1992 realtime_start: SystemTime::now(),
1993 stopless_slice_ends_at_start: crate::STOPLESS_SLICE_ENDS
1994 .load(std::sync::atomic::Ordering::Relaxed),
1995 global_time,
1996 preemptions_to_replay,
1997 network,
1998 }
1999 }
2000
2001 fn network_engine(cfg: &Config) -> Option<NetworkEngine> {
2004 let path = cfg.network_trace.path.as_ref();
2005 match cfg.network_trace.mode {
2006 NetworkTraceMode::Off => None,
2007 NetworkTraceMode::Record => Some(NetworkEngine::new_record(cfg.epoch)),
2008 NetworkTraceMode::Replay => {
2009 let path = path.expect("network trace replay requires a trace path");
2010 let trace = File::open(path)
2011 .map_err(|error| error.to_string())
2012 .and_then(|mut file| {
2013 NetworkTraceV2::read_framed(&mut file).map_err(|error| error.to_string())
2014 })
2015 .unwrap_or_else(|error| {
2016 panic!("cannot read network trace {}: {error}", path.display())
2017 });
2018 Some(NetworkEngine::new_replay(trace))
2019 }
2020 }
2021 }
2022
2023 fn finish_access_trace(&mut self) {
2028 let (writer, events) = {
2029 let mut sched = self.sched.lock().unwrap();
2030 (sched.access_trace.take(), sched.recorded_event_count)
2031 };
2032 if let Some(writer) = writer
2034 && let Err(error) = writer.finish(events)
2035 {
2036 let path = self.cfg.access_trace.as_ref().map(|p| p.display());
2037 warn!(
2038 "hermit: the access trace {} is incomplete: {error}",
2039 path.map_or_else(String::new, |p| p.to_string())
2040 );
2041 }
2042 }
2043
2044 fn finish_network_trace(&mut self) -> Option<String> {
2047 let engine = self.network.take()?;
2048 let engine = engine.into_inner().unwrap();
2049 if !engine.is_record() {
2050 return engine
2051 .finish_replay()
2052 .err()
2053 .map(|error| format!("hermit: {error}. {}", error.remedy()));
2054 }
2055 let path = self
2056 .cfg
2057 .network_trace
2058 .path
2059 .as_ref()
2060 .expect("network trace recording requires a trace path");
2061 let result = engine
2064 .finish()
2065 .map_err(|error| error.to_string())
2066 .and_then(|trace| trace.encode_framed().map_err(|error| error.to_string()))
2067 .and_then(|bytes| {
2068 let mut file = File::create(path).map_err(|error| error.to_string())?;
2069 file.write_all(&bytes).map_err(|error| error.to_string())
2070 });
2071 if let Err(error) = result {
2072 panic!("cannot write network trace {}: {error}", path.display());
2073 }
2074 None
2075 }
2076
2077 pub fn init_for_external_scheduler(cfg: &Config) -> Self {
2080 assert!(
2081 cfg.sequentialize_threads,
2082 "an external scheduler is only meaningful when threads are sequentialized"
2083 );
2084 Self::initialize(cfg, false)
2085 }
2086
2087 pub fn run_external_scheduler(
2095 &self,
2096 observer: Arc<dyn Fn(&'static str) + Send + Sync>,
2097 ) -> impl Future<Output = ()> + use<> {
2098 info!("[scheduler] daemon task starting up, waiting for guest thread start..");
2102 sched_loop_external(self.sched.clone(), self.global_time.clone(), observer)
2103 }
2104
2105 pub fn complete_physical_process_exit(&self, raw_pid: i32) {
2116 let detpid = DetPid::from_raw(raw_pid);
2117 let (completed, loss) = {
2118 let mut sched = self.sched.lock().unwrap();
2119 self.process_exit_cleanup().complete_locked(
2120 &mut sched,
2121 detpid,
2122 self.cfg.backend.process_exits_complete_asynchronously,
2123 )
2124 };
2125 if completed {
2126 trace!(
2127 "[detcore, dpid {}] backend completed final physical process exit",
2128 detpid
2129 );
2130 }
2131 if let Some(loss) = loss {
2132 crate::detlog::write_loss_notice(&loss);
2133 }
2134 }
2135
2136 fn process_exit_cleanup(&self) -> ProcessExitCleanup {
2137 ProcessExitCleanup {
2138 shared_open_files: self.shared_open_files.clone(),
2139 pending_exec_states: self.pending_exec_states.clone(),
2140 completed_exec_transfers: self.completed_exec_transfers.clone(),
2141 post_exec_fd_blocking: self.post_exec_fd_blocking.clone(),
2142 exec_images: self.exec_images.clone(),
2143 process_auxv: self.process_auxv.clone(),
2144 }
2145 }
2146
2147 fn launcher_identity(&self) -> Option<(u64, u64)> {
2150 *self.launcher_identity.get_or_init(|| {
2151 use std::os::unix::fs::MetadataExt;
2152 let launcher = self.cfg.launch_image.as_ref()?.launcher.as_ref()?;
2153 let meta = std::fs::metadata(launcher).ok()?;
2154 Some((meta.dev(), meta.ino()))
2155 })
2156 }
2157
2158 fn shared_open_file_control(
2167 &self,
2168 thread: DetTid,
2169 control: OpenFileControl,
2170 ) -> Result<OpenFileControlReply, SharedOpenFileError> {
2171 let (result, wakes) = {
2174 let mut sched = self.sched.lock().unwrap();
2175 let process = sched.registered_process(thread);
2176 let result = if !sched.holds_serial_grant(thread) {
2177 Err(SharedOpenFileError(format!(
2178 "thread {thread} sent {control:?} without holding the serial grant"
2179 )))
2180 } else if let Some(process) = process {
2181 self.shared_open_files
2182 .lock()
2183 .unwrap()
2184 .handle(process, thread, control)
2185 } else {
2186 Err(SharedOpenFileError(format!(
2187 "thread {thread} belongs to no registered process"
2188 )))
2189 };
2190 let wakes = result.as_ref().err().map(|error| {
2191 tracing::error!("[detcore] shared open file control refused: {error}");
2192 let wake = sched.report_backend_failure(reverie::BackendFailure {
2193 pid: reverie::Pid::from_raw(process.unwrap_or(thread).as_raw()),
2194 tid: Tid::from_raw(thread.as_raw()),
2195 phase: "shared open file control",
2196 });
2197 (wake, sched.take_signal_failure_wakes())
2198 });
2199 (result, wakes)
2200 };
2201 if let Some((wake, deferred)) = wakes {
2204 for wake in deferred {
2205 let _ = wake.send(());
2206 }
2207 if let Some(wake) = wake {
2208 let _ = wake.send(());
2209 }
2210 }
2211 result
2212 }
2213
2214 pub fn release_all_physical_process_exits(&self) {
2217 self.pending_exec_states.lock().unwrap().clear();
2218 self.completed_exec_transfers.lock().unwrap().clear();
2219 self.post_exec_fd_blocking.lock().unwrap().clear();
2220 self.exec_images.lock().unwrap().clear();
2221 *self.process_auxv.lock().unwrap() = ProcessAuxvTable::default();
2222 let released = self
2223 .sched
2224 .lock()
2225 .unwrap()
2226 .release_all_physical_process_exits();
2227 if released != 0 {
2228 trace!("released {released} final physical process-exit barrier(s)");
2229 }
2230 }
2231
2232 pub fn note_syscall_observation_gap(&self, gap: SyscallObservationGap) {
2236 self.syscall_observation_gaps.lock().unwrap().insert(gap);
2237 }
2238
2239 pub fn policy_refusal(&self) -> Option<String> {
2242 self.policy_refusal.lock().unwrap().clone()
2243 }
2244
2245 pub fn force_shutdown_with_error(&self) {
2248 let start = std::time::Instant::now();
2249 let sched = loop {
2250 if start.elapsed().as_millis() > 1000 {
2251 eprintln!(
2252 "Could not acquire scheduler lock during forced shutdown (timeout)... proceeding anyway."
2253 );
2254 return;
2255 }
2256 match self.sched.try_lock() {
2257 Ok(guard) => {
2258 break guard;
2259 }
2260 Err(std::sync::TryLockError::WouldBlock) => {
2261 std::thread::yield_now();
2262 continue;
2263 }
2264 Err(e) => {
2265 eprintln!(
2266 "Could not acquire scheduler lock during forced shutdown ({})... proceeding anyway.",
2267 e
2268 );
2269 return;
2270 }
2271 }
2272 };
2273 info!("Scheduler state at exit:\n{}", sched.full_summary());
2274 }
2275
2276 pub async fn cancel_internal_scheduler(&mut self) {
2282 if let Some(handle) = self.sched_handle.take() {
2283 handle.abort();
2284 match handle.await {
2285 Ok(()) => {}
2286 Err(error) if error.is_cancelled() => {}
2287 Err(error) => panic!("cancelled scheduler task panicked: {error}"),
2288 }
2289 }
2290 }
2291
2292 pub fn terminal_outcome(&self) -> Option<crate::TerminalOutcome> {
2296 self.sched.lock().unwrap().terminal_outcome()
2297 }
2298
2299 pub async fn clean_up_after_backend_failure(mut self) -> BackendFailureCleanup {
2305 crate::scheduler::note_backend_failure_cleanup();
2308 let scheduler = if let Some(handle) = self.sched_handle.take() {
2309 handle.await
2310 } else {
2311 Ok(())
2312 };
2313 let mut writer = self
2316 .sched
2317 .lock()
2318 .unwrap_or_else(std::sync::PoisonError::into_inner)
2319 .preemption_writer
2320 .take();
2321 if let (Some(writer), true) = (writer.as_mut(), self.cfg.record_preemptions_to.is_some())
2322 && let Some(inputs) = self.host_input_set()
2323 {
2324 writer.set_host_inputs(inputs);
2325 }
2326 let preemption_recording = if self.cfg.record_preemptions_to.is_some() {
2327 writer.map_or(Ok(()), |writer| writer.flush())
2328 } else {
2329 drop(writer);
2332 Ok(())
2333 };
2334 BackendFailureCleanup {
2335 scheduler,
2336 preemption_recording,
2337 }
2338 }
2339
2340 pub async fn clean_up(
2354 self,
2355 to_stderr: bool,
2356 print_summary_to_json_file: &Option<PathBuf>,
2357 ) -> anyhow::Result<()> {
2358 self.clean_up_with_dispatch_stats(to_stderr, print_summary_to_json_file, None)
2359 .await
2360 }
2361
2362 pub async fn clean_up_with_dispatch_stats(
2366 mut self,
2367 to_stderr: bool,
2368 print_summary_to_json_file: &Option<PathBuf>,
2369 dispatch_stats: Option<reverie::DispatchStats>,
2370 ) -> anyhow::Result<()> {
2371 if let Some(handle) = self.sched_handle.take() {
2372 debug!("Global state cleanup, confirming scheduler has shut down...");
2373 handle.await.expect("Global scheduler clean shutdown");
2374 debug!("Global state cleanup, continuing...");
2375 }
2376 let departure = {
2377 let scheduler = self.sched.lock().unwrap();
2378 let summary = scheduler.replay_departure().cloned();
2379 let publication_refusal = scheduler.replay_publication_refusal();
2380 (summary.is_some() || publication_refusal.is_some()).then_some(
2381 crate::ReplayDepartureError {
2382 summary,
2383 publication_refusal,
2384 },
2385 )
2386 };
2387 let network_refusal = self.finish_network_trace();
2388 self.finish_access_trace();
2389 let banner =
2390 " ------------------------------ hermit run report ------------------------------";
2391 let recording_destination = self.cfg.record_preemptions_to.clone();
2392 if let Some(log) = &self.cfg.host_input_log {
2394 self.settle_host_inputs();
2395 crate::host_inputs::write(
2396 log,
2397 &self.host_inputs.lock().unwrap(),
2398 &self.host_mutations.lock().unwrap(),
2399 );
2400 }
2401 #[cfg(target_arch = "aarch64")]
2404 for warning in self.store_exclusive.lock().unwrap().warnings(&self.cfg) {
2405 warn!("{}", warning);
2406 }
2407 let summary_result = self
2408 .finalize_run_summary()
2409 .and_then(|(mut summary, description)| {
2410 summary.dispatch_stats = dispatch_stats;
2411 if let Some(path) = print_summary_to_json_file {
2414 let json = serde_json::to_string_pretty(&summary)?;
2415 fs::write(path, json + "\n")?;
2416 }
2417 Ok((summary, description))
2418 });
2419 let (mut summary, info_reprio_descrip) = match summary_result {
2422 Ok(summary) => summary,
2423 Err(error) => {
2424 return crate::combine_finalization(
2425 crate::combine_finalization(
2426 Err(error),
2427 network_refusal.map_or(Ok(()), |refusal| Err(anyhow::anyhow!(refusal))),
2428 ),
2429 departure.map_or(Ok(()), |refusal| Err(refusal.into())),
2430 );
2431 }
2432 };
2433 if to_stderr {
2435 {
2443 use std::io::Write;
2444 let _ = write!(crate::util::RetryingStderr, "{}\n{}", banner, summary);
2445 }
2446 } else {
2447 let rt = summary.realtime_elapsed.take();
2449 log_run_summary(
2450 banner,
2451 &summary,
2452 info_reprio_descrip.as_deref(),
2453 recording_destination.as_deref(),
2454 );
2455 if let Some(x) = rt {
2456 debug!("Nondeterministic realtime elapsed: {:?}", x);
2457 }
2458 }
2459 if let Some(refusal) = network_refusal {
2461 {
2462 use std::io::Write;
2463 let _ = writeln!(crate::util::RetryingStderr, "{refusal}");
2464 }
2465 std::process::exit(detcore_model::HERMIT_POLICY_REFUSAL_EXIT);
2466 }
2467 departure.map_or(Ok(()), |refusal| Err(refusal.into()))
2468 }
2469
2470 #[cfg(test)]
2471 fn into_run_summary(self) -> anyhow::Result<RunSummary> {
2472 self.finalize_run_summary().map(|(summary, _)| summary)
2473 }
2474
2475 fn finalize_run_summary(&self) -> anyhow::Result<(RunSummary, Option<String>)> {
2479 let summary = self.run_summary_for_log();
2480 let writer = self.sched.lock().unwrap().preemption_writer.take();
2481 let flushed = if self.cfg.record_preemptions_to.is_some() {
2483 writer.map_or(Ok(()), |writer| writer.flush().map_err(anyhow::Error::msg))
2484 } else {
2485 drop(writer);
2486 Ok(())
2487 };
2488 crate::combine_finalization(summary, flushed)
2489 }
2490
2491 fn host_input_set(&self) -> Option<HostInputSet> {
2494 if !self.cfg.record_host_inputs {
2495 return None;
2496 }
2497 self.settle_host_inputs();
2498 let mutated = self
2499 .host_mutations
2500 .lock()
2501 .unwrap_or_else(PoisonError::into_inner);
2502 if mutated.contains("/") {
2506 return None;
2507 }
2508 Some(crate::host_inputs::input_set(
2509 &self
2510 .host_inputs
2511 .lock()
2512 .unwrap_or_else(PoisonError::into_inner),
2513 &mutated,
2514 ))
2515 }
2516
2517 fn settle_host_inputs(&self) {
2529 if !self.cfg.record_host_inputs || self.host_inputs_settled.swap(true, SeqCst) {
2530 return;
2531 }
2532 let mutated = self
2533 .host_mutations
2534 .lock()
2535 .unwrap_or_else(PoisonError::into_inner)
2536 .clone();
2537 let sources = std::mem::take(
2538 &mut *self
2539 .host_input_sources
2540 .lock()
2541 .unwrap_or_else(PoisonError::into_inner),
2542 );
2543 let mut records = self
2544 .host_inputs
2545 .lock()
2546 .unwrap_or_else(PoisonError::into_inner);
2547 let mut first: HashMap<String, usize> = HashMap::new();
2548 for (index, record) in records.iter().enumerate() {
2549 first.entry(record.path.clone()).or_insert(index);
2550 }
2551 let mut authenticated_snapshots: HashMap<HostFileIdentity, bool> = HashMap::new();
2552 for (index, source, physical) in &sources {
2553 let record = &mut records[*index];
2554 if first.get(&record.path) != Some(index) {
2555 continue;
2556 }
2557 record.content =
2558 crate::host_inputs::content_at_end(source, &record.path, *physical, &mutated);
2559 if record.identity != *physical {
2560 let authenticated = self.cfg.host_input_snapshots.iter().any(|snapshot| {
2561 snapshot.physical == *physical
2562 && record.content.as_deref() == Some(snapshot.content.as_str())
2563 });
2564 authenticated_snapshots
2565 .entry(*physical)
2566 .and_modify(|valid| *valid &= authenticated)
2567 .or_insert(authenticated);
2568 }
2569 }
2570 let mut failed_snapshots = Vec::new();
2571 for (index, _, physical) in sources {
2572 let record = &mut records[index];
2573 if record.identity != physical && authenticated_snapshots.get(&physical) != Some(&true)
2574 {
2575 record.identity = physical;
2579 if first.get(&record.path) == Some(&index) {
2580 failed_snapshots.push((record.path.clone(), physical));
2581 }
2582 }
2583 }
2584 let mut contents: HashMap<String, Option<String>> = HashMap::new();
2585 for record in records.iter() {
2586 contents
2587 .entry(record.path.clone())
2588 .or_insert_with(|| record.content.clone());
2589 }
2590 drop(records);
2591 let Some(replay) = &self.replay_host_inputs else {
2592 return;
2593 };
2594 let mut replay = replay.lock().unwrap_or_else(PoisonError::into_inner);
2595 for (path, physical) in failed_snapshots {
2596 replay.restore_snapshot_identity(&path, physical);
2597 }
2598 let mut changes = Vec::new();
2599 changes.extend_from_slice(
2600 replay.finish(|path| contents.get(path).cloned().flatten(), &mutated),
2601 );
2602 drop(replay);
2603 for change in &changes {
2604 report_host_input_change(change);
2605 }
2606 }
2607
2608 fn run_summary_for_log(&self) -> anyhow::Result<(RunSummary, Option<String>)> {
2609 let host_inputs = self.host_input_set();
2612 let (mut summary, info_reprio_descrip) = {
2614 let mut sched = self.sched.lock().unwrap();
2615 if let (Some(writer), Some(inputs), true) = (
2618 sched.preemption_writer.as_mut(),
2619 &host_inputs,
2620 self.cfg.record_preemptions_to.is_some(),
2621 ) {
2622 writer.set_host_inputs(inputs.clone());
2623 }
2624 #[cfg(target_arch = "aarch64")]
2627 if let Some(writer) = sched.preemption_writer.as_mut() {
2628 writer.record_store_exclusive_failures(
2629 self.store_exclusive
2630 .lock()
2631 .unwrap()
2632 .failures_for_recording(),
2633 );
2634 }
2635 sched.generate_partial_run_summary_for_log(self.cfg.record_preemptions_to.as_ref())?
2636 };
2637 #[cfg(target_arch = "aarch64")]
2638 self.store_exclusive
2639 .lock()
2640 .unwrap()
2641 .fill_summary(&mut summary);
2642 summary.preemption = Some(PreemptionSummary {
2647 max_timeslice_ns: self.cfg.max_timeslice.map(|maximum| maximum.get()),
2648 timer_enabled: Some(self.cfg.use_preemption_timer()),
2649 stopless_slice_ends: self.cfg.replays_lone_preemptions().then(|| {
2650 crate::STOPLESS_SLICE_ENDS
2651 .load(std::sync::atomic::Ordering::Relaxed)
2652 .saturating_sub(self.stopless_slice_ends_at_start)
2653 }),
2654 });
2655 summary.host_inputs = host_inputs;
2658 if let Some(replay) = &self.replay_host_inputs {
2659 summary.host_inputs_changed = replay.lock().unwrap().changes().to_vec();
2660 }
2661 summary.guest_ids = (self.cfg.guest_ids != detcore_model::pid::GuestIds::Namespace)
2662 .then_some(self.cfg.guest_ids);
2663 summary.host_id_tasks = self.host_id_tasks.load(SeqCst);
2664 summary.realtime_elapsed = Some(self.realtime_start.elapsed()?);
2669 if let Some(histogram) = &mut summary.syscall_histogram {
2670 let mut gaps = self.syscall_observation_gaps.lock().unwrap().clone();
2671 if self.cfg.passthru_opt {
2672 gaps.insert(SyscallObservationGap::PassthruOpt);
2673 }
2674 histogram.observation_scope = SyscallObservationScope::from_gaps(gaps);
2675 }
2676
2677 if self.cfg.virtualize_time {
2678 let final_time = self.global_time.lock().unwrap();
2679 summary.virttime_final = final_time.as_nanos().as_nanos();
2680 summary.virttime_elapsed = match final_time.elapsed_nanos() {
2681 Some(elapsed) => elapsed.as_nanos(),
2682 None => bail!(
2683 "Internal invariant violated! Global time {} is before its epoch baseline",
2684 final_time.as_nanos()
2685 ),
2686 };
2687 }
2688
2689 Ok((summary, info_reprio_descrip))
2690 }
2691}
2692
2693fn log_run_summary(
2694 banner: &str,
2695 summary: &RunSummary,
2696 info_reprio_descrip: Option<&str>,
2697 recording_destination: Option<&std::path::Path>,
2698) {
2699 info!("\n{}\n{}", banner, summary.info(info_reprio_descrip));
2700 debug!(
2701 replayed_events = summary.schedevent_replayed,
2702 ?recording_destination,
2703 "Run recording/replay bookkeeping"
2704 );
2705}
2706
2707#[reverie::global_tool]
2708impl GlobalTool for GlobalState {
2709 type Config = Config;
2710
2711 type Request = (DetTime, MmId, GlobalRequest);
2717
2718 type Response = (Option<LogicalTime>, GlobalResponse);
2726
2727 async fn init_global_state(cfg: &Config) -> GlobalState {
2729 crate::syscalls::refuse_host_signal_state_reads(&cfg.backend);
2732 if cfg.guest_ids != detcore_model::pid::GuestIds::Namespace {
2735 info!("[detcore] guest process IDs: {}", cfg.guest_ids);
2736 }
2737 GlobalState::initialize(cfg, true)
2738 }
2739
2740 fn install_inherited_input(&mut self, input: reverie::InheritedInputLease) {
2741 *self.inherited_input.get_mut().unwrap() = Some(PendingInheritedInput {
2744 lease: input,
2745 root: None,
2746 });
2747 }
2748
2749 fn install_mapping_capture_run(&mut self, run: reverie::MappingCaptureRun) {
2750 self.mapping_capture_run = Some(run);
2751 }
2752
2753 fn install_reserved_input_control(
2754 &mut self,
2755 control: reverie::reserved_input::ReservedInputControl,
2756 ) -> Result<(), reverie::Error> {
2757 let mut scheduler = self.sched.lock().unwrap();
2758 if scheduler.reserved_input_control.is_some() {
2759 return Err(anyhow::anyhow!("reserved-input control installed twice").into());
2760 }
2761 crate::reserved_input::install_native_history(&control)?;
2762 scheduler.reserved_input_operations =
2763 Some(crate::reserved_input::NativeOperations::install(&control)?);
2764 scheduler.reserved_input_control = Some(control);
2765 Ok(())
2766 }
2767
2768 fn on_reserved_input_task(
2769 &self,
2770 task: reverie::reserved_input::InputTaskRegistration,
2771 ) -> Result<(), reverie::Error> {
2772 let tool_tid = task
2776 .tool_tid
2777 .filter(|tid| *tid > 0)
2778 .ok_or_else(|| anyhow::anyhow!("reserved-input report lacks Tool thread identity"))?;
2779 let _tool_pid = task
2780 .tool_pid
2781 .filter(|pid| *pid > 0)
2782 .ok_or_else(|| anyhow::anyhow!("reserved-input report lacks Tool process identity"))?;
2783 let mut scheduler = self.sched.lock().unwrap();
2784 let control = scheduler.reserved_input_control.as_ref().ok_or_else(|| {
2785 anyhow::anyhow!("reserved-input task reported before control installation")
2786 })?;
2787 if control.registration(task.task) != Some(task) {
2788 return Err(
2789 anyhow::anyhow!("reserved-input report differs from retained generation").into(),
2790 );
2791 }
2792 scheduler
2793 .reserved_input_native_tasks
2794 .insert(task.native_tid, task.task);
2795 scheduler
2798 .reserved_input_tool_tasks
2799 .insert(DetTid::from_raw(tool_tid), task.task);
2800 Ok(())
2801 }
2802
2803 fn admit_reserved_input_initial_state(
2804 &self,
2805 facts: &reverie::reserved_input::InputNativeFacts,
2806 ) -> Result<reverie::reserved_input::InputAdmission, reverie::Error> {
2807 let control = self
2808 .sched
2809 .lock()
2810 .unwrap()
2811 .reserved_input_control
2812 .clone()
2813 .ok_or_else(|| {
2814 anyhow::anyhow!("initial native admission precedes control installation")
2815 })?;
2816 crate::reserved_input::register_initial_history(&control, facts.task)?;
2817 let admission = crate::reserved_input::admit_initial(facts);
2818 let descriptors = crate::reserved_input::observe_initial_descriptors(&control, facts)?;
2819 Ok(match (admission, descriptors) {
2820 (reverie::reserved_input::InputAdmission::Qualified, Some(cause)) => {
2821 reverie::reserved_input::InputAdmission::Unqualified(cause)
2822 }
2823 (admission, _) => admission,
2824 })
2825 }
2826
2827 fn on_reserved_input_setup(
2828 &self,
2829 receipt: reverie::reserved_input::InputSetupReceipt,
2830 ) -> Result<(), reverie::Error> {
2831 if let Some(writer) = self.sched.lock().unwrap().preemption_writer.as_mut() {
2832 writer
2833 .observe_reserved_input_setup(&receipt)
2834 .map_err(anyhow::Error::new)?;
2835 }
2836 Ok(())
2837 }
2838
2839 fn install_backend_signal_control(
2840 &self,
2841 control: Option<reverie::BackendSignalControl>,
2842 ) -> Result<reverie::BackendSignalControlMode, reverie::Error> {
2843 self.sched
2844 .lock()
2845 .unwrap()
2846 .install_signal_control(control, self.cfg.sequentialize_threads)
2847 }
2848
2849 fn observe_signal_dequeues(&self, mode: reverie::BackendSignalControlMode) -> bool {
2852 mode == reverie::BackendSignalControlMode::ToolControlled
2853 }
2854
2855 fn skip_empty_signal_boundaries(&self) -> bool {
2942 true
2943 }
2944
2945 fn authorize_backend_signal_boundary(
2946 &self,
2947 task: reverie::SignalTaskIdentity,
2948 ) -> Result<Option<reverie::SignalDeliveryPermit>, reverie::Error> {
2949 self.sched.lock().unwrap().authorize_signal_boundary(task)
2950 }
2951
2952 async fn on_backend_signal_boundary(
2958 &self,
2959 receipt: reverie::SignalBoundaryReceipt,
2960 ) -> Result<(), reverie::Error> {
2961 let death = self
2962 .sched
2963 .lock()
2964 .unwrap()
2965 .consume_signal_boundary(receipt)?;
2966 if let Some(death) = death {
2967 self.release_tables_at_death(death);
2968 }
2969 Ok(())
2970 }
2971
2972 fn on_backend_process_retired(
2973 &self,
2974 event: reverie::BackendProcessRetirement,
2975 ) -> Result<(), reverie::Error> {
2976 let (result, wakes) = {
2977 let mut sched = self.sched.lock().unwrap();
2978 let result = sched.consume_process_retirement(event);
2979 (result, sched.take_signal_failure_wakes())
2980 };
2981 for wake in wakes {
2982 let _ = wake.send(());
2983 }
2984 result
2985 }
2986
2987 fn on_backend_process_exited(&self, pid: i32) {
2990 if self.cfg.backend.reports_child_exit_publication {
2991 let released = self
2994 .sched
2995 .lock()
2996 .unwrap()
2997 .complete_child_exit_publication(DetPid::from_raw(pid));
2998 self.process_auxv
3004 .lock()
3005 .unwrap()
3006 .forget_process(DetPid::from_raw(pid));
3007 if released {
3008 trace!("[detcore, dpid {}] backend published the child exit", pid);
3009 }
3010 } else {
3011 self.complete_physical_process_exit(pid);
3012 }
3013 }
3014
3015 fn backend_pending_process_exits(&self) -> Vec<i32> {
3018 self.sched
3019 .lock()
3020 .unwrap()
3021 .pending_async_process_exits()
3022 .into_iter()
3023 .map(|detpid| detpid.as_raw())
3024 .collect()
3025 }
3026
3027 fn on_fatal_signal_exit(&self, exit: &reverie::FatalSignalExit) {
3028 if let Some(capture) = &self.cfg.fatal_core_capture {
3029 crate::fatal_core::capture_and_log(capture, exit);
3030 }
3031 }
3032
3033 fn report_backend_failure(&self, event: reverie::BackendFailure) {
3034 let (wake, deferred) = {
3035 let mut sched = self.sched.lock().unwrap();
3036 (
3037 sched.report_backend_failure(event),
3038 sched.take_signal_failure_wakes(),
3039 )
3040 };
3041 for wake in deferred {
3042 let _ = wake.send(());
3043 }
3044 if let Some(wake) = wake {
3045 let _ = wake.send(());
3048 }
3049 }
3050
3051 async fn wait_for_backend_failure(&self) {
3052 let wake = self.sched.lock().unwrap().backend_failure_waiter();
3053 wake.await
3054 .expect("GlobalState owns the failure sender until publication");
3055 }
3056
3057 async fn on_backend_child_wait_event(
3058 &self,
3059 event: reverie::BackendChildWaitEvent,
3060 ) -> Result<(), reverie::Error> {
3061 crate::scheduler::signal_control::ChildExitPublicationFuture::new(self.sched.clone(), event)
3067 .await
3068 }
3069
3070 async fn receive_rpc(&self, from: Tid, gr: Self::Request) -> Self::Response {
3071 if replay_departure::is_report(&gr.2) {
3072 return self.accept_replay_departure(from, gr);
3073 }
3074 #[cfg(target_arch = "aarch64")]
3075 if let GlobalRequest::StoreExclusive(report) = &gr.2 {
3076 return self.note_store_exclusive(report);
3077 }
3078 if let GlobalRequest::MappingCapture(request) = &gr.2 {
3079 return (
3080 None,
3081 GlobalResponse::MappingCapture(self.mapping_capture_request(from, gr.1, request)),
3082 );
3083 }
3084 if let GlobalRequest::CheckBrkMappings(check, fact) = &gr.2 {
3085 return (
3086 None,
3087 GlobalResponse::CheckBrkMappings(
3088 self.capture_brk_mappings(from, gr.1, *check, *fact),
3089 ),
3090 );
3091 }
3092 if let GlobalRequest::HoldTable(table) = &gr.2 {
3093 return self.hold_table(from, table);
3094 }
3095 if native_exec::is_native_exec_control(&gr.2) {
3098 return self.dispatch_native_exec_control(from, gr);
3099 }
3100 let dtid = DetTid::from_raw(from.as_raw());
3117 let request_mm = gr.1;
3118 let self_registration = registers_own_image(dtid, &gr.2);
3122 {
3123 let mut scheduler = self.sched.lock().unwrap();
3124 scheduler.take_grant(dtid);
3125 scheduler.take_lone_grant(dtid);
3126 }
3127 let (time, response) = if !resumes_from_grant(&gr.2) {
3128 self.dispatch_rpc(from, gr).await
3129 } else {
3130 self.sched.lock().unwrap().take_deadline_hint(dtid);
3131 let (time, response) = self.dispatch_rpc(from, gr).await;
3132 let hint = self.sched.lock().unwrap().take_deadline_hint(dtid);
3133 match hint {
3134 Some(remaining)
3135 if response != GlobalResponse::ThreadExited
3136 && response != GlobalResponse::Retired =>
3137 {
3138 (
3139 time,
3140 GlobalResponse::WithDeadline(remaining, Box::new(response)),
3141 )
3142 }
3143 _ => (time, response),
3144 }
3145 };
3146 let (granted, boundary_budget, boundary_owner, lone) = {
3147 let mut scheduler = self.sched.lock().unwrap();
3148 let granted = scheduler.take_grant(dtid);
3149 let lone = scheduler.take_lone_grant(dtid);
3150 let terminal =
3151 response == GlobalResponse::ThreadExited || response == GlobalResponse::Retired;
3152 let mut budget = scheduler
3153 .boundary_budgets
3154 .finish(dtid, request_mm, granted && !self_registration, terminal)
3155 .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error));
3156 if !terminal && matches!(response, GlobalResponse::MarkPastFirstExecve(_)) {
3160 assert!(
3161 budget.is_none(),
3162 "exec completion cannot steal an old grant budget"
3163 );
3164 budget = scheduler
3165 .boundary_budget_for_live_owner(dtid)
3166 .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error));
3167 }
3168 let owner = if !terminal
3169 && !self_registration
3170 && (granted || matches!(response, GlobalResponse::MarkPastFirstExecve(_)))
3171 {
3172 scheduler.boundary_owners.as_ref().map(|owners| {
3173 owners
3174 .owner(dtid, request_mm)
3175 .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error))
3176 })
3177 } else {
3178 None
3179 };
3180 (granted, budget, owner, lone)
3181 };
3182 let response = match boundary_budget {
3183 Some(budget) => GlobalResponse::WithSliceBoundaryBudget(budget, Box::new(response)),
3184 None => response,
3185 };
3186 let response = match boundary_owner {
3187 Some(owner) => GlobalResponse::WithSliceBoundaryOwner(owner, Box::new(response)),
3188 None => response,
3189 };
3190 if granted
3191 && response != GlobalResponse::ThreadExited
3192 && response != GlobalResponse::Retired
3193 {
3194 let response = GlobalResponse::Granted(Box::new(response));
3195 if lone {
3196 (time, GlobalResponse::WithLoneGrant(Box::new(response)))
3197 } else {
3198 (time, response)
3199 }
3200 } else {
3201 (time, response)
3202 }
3203 }
3204}
3205
3206impl GlobalState {
3207 fn hold_table(&self, from: Tid, table: &HeldTable) -> <Self as GlobalTool>::Response {
3217 let Some(table) = &table.0 else {
3218 return (
3219 None,
3220 GlobalResponse::HoldTable(Err(format!(
3221 "thread {from} named its descriptor table through a transport that encodes \
3222 its requests, which carries no table: {HELD_TABLE_IS_IN_PROCESS}, so the \
3223 terminal receipt of a death by a fatal signal could not let go of it"
3224 ))),
3225 );
3226 };
3227 self.held_tables
3228 .lock()
3229 .unwrap()
3230 .insert(DetTid::from_raw(from.as_raw()), Arc::downgrade(table));
3231 (None, GlobalResponse::HoldTable(Ok(())))
3232 }
3233
3234 fn release_port(&self, open_file: OpenFileId) -> Option<u16> {
3238 let mut used_ports = self.used_ports.lock().unwrap();
3239 let mut open_file_to_port = self.open_file_to_port.lock().unwrap();
3240 let port = open_file_to_port.remove(&open_file);
3241 if let Some(port) = port {
3242 used_ports.remove(&port);
3243 }
3244 port
3245 }
3246
3247 fn release_tables_at_death(&self, death: crate::scheduler::signal_control::SignalDeath) {
3273 use crate::syscalls::scm_rights;
3274 use crate::tool_local::TableAtDeath;
3275 if !scm_rights::releases_tables_at_exits(&self.cfg) {
3276 return;
3277 }
3278 let mut unnamed = None;
3279 let tables = {
3280 let mut held = self.held_tables.lock().unwrap();
3281 let mut tables: Vec<Arc<Mutex<crate::tool_local::FileMetadata>>> = Vec::new();
3282 for thread in &death.threads {
3283 match held.remove(thread).and_then(|table| table.upgrade()) {
3284 Some(table) => {
3285 if !tables.iter().any(|known| Arc::ptr_eq(known, &table)) {
3286 tables.push(table);
3287 }
3288 }
3289 None => {
3290 unnamed.get_or_insert(*thread);
3291 }
3292 }
3293 }
3294 tables
3295 };
3296 let mut undecided = unnamed
3302 .filter(|_| {
3303 tables.is_empty()
3304 || self
3305 .lock_ledger
3306 .lock()
3307 .unwrap()
3308 .has_private_tables(death.process)
3309 })
3310 .map(|thread| format!("its thread {thread} named no descriptor table"));
3311 let mut released = Vec::new();
3312 for table in tables {
3313 match table.lock().unwrap().release_at_death(death.process) {
3314 TableAtDeath::Released(rights) => released.extend(rights),
3315 TableAtDeath::Kept => {}
3316 TableAtDeath::Undecided => {
3317 undecided.get_or_insert_with(|| {
3318 "a table of its threads does not count it among its holders".to_owned()
3319 });
3320 }
3321 }
3322 }
3323 let scm_rights::Settled { lock_copies, ports } = scm_rights::settle(released);
3324 if scm_rights::follows_lock_copies(&self.cfg) {
3325 let mut ledger = self.lock_ledger.lock().unwrap();
3326 if !lock_copies.is_empty() {
3327 let _ = ledger.placeholder_copies(crate::procfs::locks::PlaceholderCopies::Unsent(
3329 lock_copies,
3330 ));
3331 }
3332 if let Some(reason) = undecided
3333 && ledger.counts_copies_in_flight()
3334 {
3335 ledger.refuse(format!(
3336 "process {} died by a fatal signal while a copy of a description was in \
3337 flight, and {reason}, so which locks its death released is unknown",
3338 death.process
3339 ));
3340 }
3341 }
3342 for open_file in ports {
3343 self.release_port(open_file);
3344 }
3345 }
3346
3347 async fn take_guest_id(&self) -> Option<i32> {
3355 self.cfg.guest_ids.base()?;
3356 loop {
3357 let (id, registered) = {
3358 let sched = self.lock_rpc_scheduler(false).await;
3359 let id = self
3360 .next_guest_id
3361 .try_update(SeqCst, SeqCst, |id| {
3362 (id > 0).then(|| id.checked_add(1).unwrap_or(0))
3363 })
3364 .ok()?;
3365 (id, sched.thread_was_registered(DetTid::from_raw(id)))
3366 };
3367 let clocked = self
3368 .global_time
3369 .lock()
3370 .unwrap()
3371 .contains_thread(DetTid::from_raw(id));
3372 if !registered && !clocked {
3373 return Some(id);
3374 }
3375 }
3376 }
3377
3378 fn capture_brk_mappings(
3383 &self,
3384 from: Tid,
3385 mm: MmId,
3386 check: crate::syscalls::memory::BrkMappingCheck,
3387 fact: reverie::ControllerCaptureFact,
3388 ) -> Option<bool> {
3389 use std::io::Read;
3390 use std::os::fd::AsRawFd;
3391 use std::os::unix::fs::MetadataExt;
3392
3393 if !self.cfg.sequentialize_threads {
3394 return None;
3395 }
3396 let sched = self.sched.lock().unwrap();
3397 let dettid = DetTid::from_raw(from.as_raw());
3398 let (pid, tid) = sched.controller_capture_task(dettid, mm, fact)?;
3399 let root = File::open("/proc").ok()?;
3400 let metadata = root.metadata().ok()?;
3401 if !metadata.is_dir()
3402 || metadata.dev() != fact.procfs_device
3403 || metadata.ino() != fact.procfs_inode
3404 {
3405 return None;
3406 }
3407 let mut filesystem = std::mem::MaybeUninit::<libc::statfs>::uninit();
3408 if unsafe { libc::fstatfs(root.as_raw_fd(), filesystem.as_mut_ptr()) } != 0
3410 || unsafe { filesystem.assume_init() }.f_type as u64
3411 != reverie::syscalls::linux_abi::PROC_SUPER_MAGIC as u64
3412 {
3413 return None;
3414 }
3415 let relative = std::ffi::CString::new(format!("{tid}/smaps")).ok()?;
3418 let raw = unsafe {
3420 libc::openat(
3421 root.as_raw_fd(),
3422 relative.as_ptr(),
3423 libc::O_RDONLY | libc::O_CLOEXEC,
3424 )
3425 };
3426 if raw < 0 {
3427 return None;
3428 }
3429 let mut smaps_file = unsafe { File::from_raw_fd(raw) };
3431 let mut smaps = Vec::new();
3432 smaps_file.read_to_end(&mut smaps).ok()?;
3433 if sched.controller_capture_task(dettid, mm, fact) != Some((pid, tid)) {
3436 return None;
3437 }
3438 Some(check.allows(&smaps))
3439 }
3442
3443 fn host_diskstats(&self) -> Result<Vec<u8>, i32> {
3450 self.host_diskstats
3451 .get_or_init(|| {
3452 std::fs::read("/proc/diskstats")
3453 .map_err(|error| error.raw_os_error().unwrap_or(libc::EIO))
3454 })
3455 .clone()
3456 }
3457
3458 fn announce_refused_signal_controls(&self, actions: u64, alternate_stack: bool) {
3462 let mut announced = self.announced_refused_signal_controls.lock().unwrap();
3463 let new_actions = actions & !announced.0;
3464 announced.0 |= actions;
3465 let new_stack = alternate_stack && !announced.1;
3466 announced.1 |= alternate_stack;
3467 drop(announced);
3468 for signum in (1..=64).filter(|signum| new_actions & (1 << (signum - 1)) != 0) {
3469 warn!(
3470 "{}",
3471 crate::refused_signal_controls::handler_refused_warning(signum)
3472 );
3473 }
3474 if new_stack {
3475 warn!(
3476 "{}",
3477 crate::refused_signal_controls::alternate_stack_refused_warning()
3478 );
3479 }
3480 }
3481
3482 async fn dispatch_rpc(
3483 &self,
3484 from: Tid,
3485 gr: <Self as GlobalTool>::Request,
3486 ) -> <Self as GlobalTool>::Response {
3487 if replay_departure::is_report(&gr.2) {
3488 return self.accept_replay_departure(from, gr);
3489 }
3490 if let GlobalRequest::MappingCapture(request) = &gr.2 {
3493 return (
3494 None,
3495 GlobalResponse::MappingCapture(self.mapping_capture_request(from, gr.1, request)),
3496 );
3497 }
3498 #[cfg(target_arch = "aarch64")]
3499 if let GlobalRequest::StoreExclusive(report) = &gr.2 {
3500 return self.note_store_exclusive(report);
3501 }
3502 if let GlobalRequest::CheckBrkMappings(check, fact) = &gr.2 {
3503 return (
3504 None,
3505 GlobalResponse::CheckBrkMappings(
3506 self.capture_brk_mappings(from, gr.1, *check, *fact),
3507 ),
3508 );
3509 }
3510 if let GlobalRequest::HoldTable(table) = &gr.2 {
3511 return self.hold_table(from, table);
3512 }
3513 if native_exec::is_native_exec_control(&gr.2) {
3516 return self.dispatch_native_exec_control(from, gr);
3517 }
3518 if let GlobalRequest::GuestLog(record, unsent_before) = &gr.2 {
3525 crate::guest_log::emit(record.as_ref(), *unsent_before);
3526 return (None, GlobalResponse::GuestLog(()));
3527 }
3528 if let GlobalRequest::TakeSigkillRobustList = &gr.2 {
3535 let list = self
3536 .sched
3537 .lock()
3538 .unwrap()
3539 .take_sigkill_robust_list(DetTid::from_raw(from.as_raw()), gr.1);
3540 return (None, GlobalResponse::TakeSigkillRobustList(list));
3541 }
3542 if let GlobalRequest::AnnounceRefusedSignalControls(actions, alternate_stack) = &gr.2 {
3547 self.announce_refused_signal_controls(*actions, *alternate_stack);
3548 return (None, GlobalResponse::AnnounceRefusedSignalControls(()));
3549 }
3550 if let GlobalRequest::HostDiskstats = &gr.2 {
3554 return (None, GlobalResponse::HostDiskstats(self.host_diskstats()));
3555 }
3556 if let GlobalRequest::ProcfsSequence(id) = &gr.2 {
3562 let sequence = self.procfs_sequences.lock().unwrap().get(id).cloned();
3563 return (None, GlobalResponse::ProcfsSequence(sequence));
3564 }
3565 if let GlobalRequest::StoreProcfsSequence(id, sequence) = &gr.2 {
3566 self.procfs_sequences
3567 .lock()
3568 .unwrap()
3569 .insert(*id, sequence.clone());
3570 return (None, GlobalResponse::StoreProcfsSequence(()));
3571 }
3572 crate::detlog::drain_forwarded_at_request(from.as_raw());
3576 type R = GlobalResponse;
3577 let dtid = DetTid::from_raw(from.into()); let (guest_time, request_mm, request) = gr;
3579 let request = match request {
3580 GlobalRequest::WithForwardedCount(produced, request) => {
3581 crate::detlog::check_forwarded_count(from.as_raw(), produced);
3582 *request
3583 }
3584 request => request,
3585 };
3586 let time_from_guest = guest_time.as_nanos();
3587 let work_span_cpu = self.cfg.work_span_report.is_some().then(|| {
3590 guest_time.user_cpu_time().as_nanos() + guest_time.system_cpu_time().as_nanos()
3591 });
3592 if let Some(cpu) = work_span_cpu {
3593 use crate::scheduler::work_span::Arrival;
3594 let arrival = match &request {
3595 GlobalRequest::StartNewThread(child, ..) if *child == dtid => Arrival::StartsThread,
3596 GlobalRequest::RequestResources(resources, _) if resources.poll_attempt > 0 => {
3597 Arrival::Retry(resources)
3598 }
3599 GlobalRequest::ReconnectExec { .. } | GlobalRequest::RetireExec { .. } => {
3600 Arrival::ExecTransfer
3601 }
3602 _ => Arrival::Other,
3603 };
3604 self.sched
3605 .lock()
3606 .unwrap()
3607 .work_span_note_arrival(dtid, cpu, arrival);
3608 }
3609 match &request {
3613 GlobalRequest::ReconnectExec { former, process } => {
3614 return self
3615 .recv_exec_transfer(from, guest_time, request_mm, *former, *process)
3616 .await;
3617 }
3618 GlobalRequest::RetireExec { thread, signaled } => {
3619 return self
3620 .recv_retire_exec_transfer(
3621 from,
3622 guest_time,
3623 request_mm,
3624 thread.clone(),
3625 *signaled,
3626 )
3627 .await;
3628 }
3629 _ => {}
3630 }
3631 let early_victim_request = match &request {
3644 GlobalRequest::RecordHostInput { .. }
3645 | GlobalRequest::RecordHostMutation { .. }
3646 | GlobalRequest::RecordDeterminismLoss(_)
3647 | GlobalRequest::Sigalrm(_) => self
3648 .sched
3649 .lock()
3650 .unwrap()
3651 .is_unaccounted_sigkill_victim(dtid),
3652 _ => false,
3653 };
3654 if early_victim_request && matches!(&request, GlobalRequest::Sigalrm(_)) {
3655 return (None, R::Retired);
3656 }
3657 if let GlobalRequest::RecordHostInput {
3661 path,
3662 syscall,
3663 identity,
3664 source,
3665 regular,
3666 exec,
3667 kernel_object,
3668 } = request
3669 {
3670 let physical = identity;
3671 let identity = source
3676 .as_ref()
3677 .filter(|_| regular && !kernel_object)
3678 .and_then(|_| {
3679 self.cfg
3680 .host_input_snapshots
3681 .iter()
3682 .find(|snapshot| snapshot.physical == physical)
3683 })
3684 .map_or(physical, |snapshot| snapshot.origin);
3685 let change = self.replay_host_inputs.as_ref().and_then(|replay| {
3686 let mutated = self
3687 .host_mutations
3688 .lock()
3689 .unwrap_or_else(PoisonError::into_inner);
3690 replay
3691 .lock()
3692 .unwrap_or_else(PoisonError::into_inner)
3693 .observe(&path, identity, regular, kernel_object, &mutated)
3694 .cloned()
3695 });
3696 if let Some(change) = change {
3697 report_host_input_change(&change);
3698 }
3699 let mut records = self
3700 .host_inputs
3701 .lock()
3702 .unwrap_or_else(PoisonError::into_inner);
3703 if let Some(source) = source {
3704 self.host_input_sources
3705 .lock()
3706 .unwrap_or_else(PoisonError::into_inner)
3707 .push((records.len(), PathBuf::from(source), physical));
3708 }
3709 records.push(HostInputRecord {
3710 path,
3711 dtid: dtid.as_raw() as u64,
3712 syscall,
3713 identity,
3714 content: None,
3716 exec,
3717 kernel_object,
3718 });
3719 drop(records);
3720 if early_victim_request {
3721 return (None, R::Retired);
3722 }
3723 return (None, R::RecordHostInput(()));
3724 }
3725 if let GlobalRequest::RecordHostMutation { path } = request {
3726 self.host_mutations.lock().unwrap().insert(path);
3727 if early_victim_request {
3728 return (None, R::Retired);
3729 }
3730 return (None, R::RecordHostMutation(()));
3731 }
3732 if let GlobalRequest::RecordDeterminismLoss(reason) = request {
3736 crate::detlog::write_loss_notice(&reason);
3737 if early_victim_request {
3738 return (None, R::Retired);
3739 }
3740 return (None, R::RecordDeterminismLoss(()));
3741 }
3742 if let GlobalRequest::CallbackCheckpointMismatch {
3743 checkpoint,
3744 hard,
3745 actual,
3746 } = request
3747 {
3748 let mut sched = self.lock_rpc_scheduler(false).await;
3749 if sched.thread_is_logically_killed(dtid)
3750 || !sched.rpc_incarnation_matches(dtid, request_mm)
3751 {
3752 return (None, R::ThreadExited);
3753 }
3754 sched.callback_checkpoint_mismatch(dtid, &checkpoint, hard, &actual);
3755 return (None, R::CallbackCheckpointMismatch(()));
3756 }
3757 if let GlobalRequest::SharedOpenFile(control) = request {
3761 return (
3762 None,
3763 R::SharedOpenFile(self.shared_open_file_control(dtid, control)),
3764 );
3765 }
3766 if let GlobalRequest::Sigalrm(control) = request {
3771 let refused = self.sched.lock().unwrap().sigalrm_control(dtid, control);
3772 return (None, R::Sigalrm(refused));
3773 }
3774 if let GlobalRequest::ChildExitSigchld(control) = request {
3779 let answer = self
3780 .sched
3781 .lock()
3782 .unwrap()
3783 .child_exit_sigchld_control_for_receiver(dtid, control);
3784 return (None, R::ChildExitSigchld(answer));
3785 }
3786 if let GlobalRequest::DiscardedSignal(control) = request {
3791 let answer = self
3792 .sched
3793 .lock()
3794 .unwrap()
3795 .discarded_signal_control(dtid, control);
3796 return (None, R::DiscardedSignal(answer));
3797 }
3798 if let GlobalRequest::PublishSignalDispositions(generation, dispositions) = request {
3805 let held = self.sched.lock().unwrap().publish_signal_dispositions(
3806 dtid,
3807 request_mm,
3808 generation,
3809 dispositions,
3810 );
3811 return (None, R::PublishSignalDispositions(held));
3812 }
3813 if let GlobalRequest::PublishSignalMask(mask) = request {
3816 let taken = self
3817 .sched
3818 .lock()
3819 .unwrap()
3820 .publish_signal_mask(dtid, request_mm, mask);
3821 return (None, R::PublishSignalMask(taken));
3822 }
3823 if let GlobalRequest::PublishRuntimePrivateMemory(facts) = request {
3824 let accepted = self
3825 .sched
3826 .lock()
3827 .unwrap()
3828 .publish_runtime_private_memory(dtid, request_mm, facts);
3829 return (None, R::PublishRuntimePrivateMemory(accepted));
3830 }
3831 if let GlobalRequest::SignalDequeued {
3832 detpid,
3833 identity,
3834 dequeue,
3835 } = &request
3836 {
3837 return self
3838 .recv_signal_dequeued(dtid, request_mm, guest_time, *detpid, *identity, *dequeue)
3839 .await;
3840 }
3841
3842 if let GlobalRequest::CreateVforkChildThread(parent, process, child, _, flags, ..) =
3846 &request
3847 {
3848 let mut sched = self.lock_rpc_scheduler(false).await;
3849 if sched.transferred_exec_tid_requires_registration(dtid) {
3850 if *child != dtid
3851 || !flags.contains(CloneFlags::CLONE_VFORK)
3852 || !sched.pending_vfork_registration_matches(
3853 *parent,
3854 *process,
3855 *child,
3856 request_mm,
3857 flags.contains(CloneFlags::CLONE_VM),
3858 )
3859 {
3860 return (None, R::ThreadExited);
3861 }
3862 sched.register_reused_transferred_exec_tid(dtid, request_mm);
3863 }
3864 }
3865
3866 if let GlobalRequest::StartNewThread(child, detpid, ..) = &request
3875 && *child == dtid
3876 {
3877 loop {
3878 {
3879 let sched = self.lock_rpc_scheduler(false).await;
3880 let awaits_registration = sched
3881 .transferred_exec_tid_requires_registration(dtid)
3882 || id_awaits_new_registration(&sched, dtid);
3883 let ends_unregistered = sched.thread_is_logically_killed(dtid)
3889 || sched.is_unaccounted_sigkill_victim(dtid)
3890 || (!sched.rpc_incarnation_matches(dtid, request_mm)
3891 && !sched.transferred_exec_tid_requires_registration(dtid))
3892 || (self.cfg.backend.process_exits_complete_asynchronously
3893 && sched.process_physically_gone(*detpid));
3894 if !awaits_registration || ends_unregistered {
3895 break;
3896 }
3897 }
3898 yield_once().await;
3899 }
3900 }
3901
3902 let is_deregister = matches!(&request, GlobalRequest::DeregisterThread(_));
3903 let consuming_cleanup =
3904 is_deregister || matches!(&request, GlobalRequest::RobustListWakes(_));
3905 let answered_for_sigkill_victim = consuming_cleanup
3909 || matches!(
3910 &request,
3911 GlobalRequest::ParkedProtocolFailure(_)
3912 | GlobalRequest::UnrecoverableShutdown
3913 | GlobalRequest::PolicyRefusal(_)
3914 | GlobalRequest::StoppingRefusal(_)
3915 );
3916
3917 let (exec_reconnect, is_exec_caller_after_local_mm_swap) = {
3918 let pending = self.pending_exec_states.lock().unwrap();
3919 let reconnect = match &request {
3920 GlobalRequest::CreateChildThread(child, process, _, None, _, _, _)
3921 if *child == dtid && *child == *process =>
3922 {
3923 pending.get(process).cloned()
3924 }
3925 _ => None,
3926 };
3927 let is_exec_caller_after_local_mm_swap = pending.values().any(|state| {
3928 state.caller == dtid && state.mm.for_exec(state.process) == request_mm
3929 });
3930 (reconnect, is_exec_caller_after_local_mm_swap)
3931 };
3932
3933 let mut tombstoned_deregistration = None;
3938 {
3939 let mut sched = self.lock_rpc_scheduler(consuming_cleanup).await;
3940 if exec_reconnect.is_none()
3941 && !is_exec_caller_after_local_mm_swap
3942 && !sched.rpc_incarnation_matches(dtid, request_mm)
3943 {
3944 debug!(
3945 "[detcore, dtid {}] rejecting {:?} RPC from retired exec incarnation {:?}",
3946 dtid, request, request_mm,
3947 );
3948 return if is_deregister {
3949 (None, R::DeregisterThread(()))
3950 } else {
3951 (None, R::ThreadExited)
3952 };
3953 }
3954 if let GlobalRequest::DeregisterThread(owner) = &request {
3955 assert_eq!(
3956 owner.dettid, dtid,
3957 "deregistration must belong to its sender"
3958 );
3959 assert_eq!(owner.mm, request_mm, "deregistration must retain its MmId");
3960 if let Some(origin) = owner.vfork_origin {
3965 sched.record_unregistered_vfork_child_exit(origin, owner.dettid);
3966 }
3967 if !sched.thread_was_registered(dtid) && !sched.thread_is_logically_killed(dtid) {
3970 assert!(
3971 sched.backend_failed() || !owner.thread_start_entered,
3972 "a started thread must have a scheduler registration before deregistration"
3973 );
3974 return (None, R::DeregisterThread(()));
3978 }
3979 }
3980 let child = match &request {
3981 GlobalRequest::CreateChildThread(child, ..)
3982 | GlobalRequest::CreateVforkChildThread(_, _, child, ..) => Some(*child),
3983 _ => None,
3984 };
3985 if sched.thread_is_logically_killed(dtid) && exec_reconnect.is_none() {
3986 trace!(
3987 "[detcore, dtid {}] rejecting RPC after permanent logical-thread removal",
3988 dtid
3989 );
3990 if let GlobalRequest::DeregisterThread(deregistration) = &request {
3991 tombstoned_deregistration = Some(deregistration.clone());
3992 } else {
3993 return (None, R::ThreadExited);
3994 }
3995 }
3996 if !answered_for_sigkill_victim && sched.is_unaccounted_sigkill_victim(dtid) {
3997 trace!(
3998 "[detcore, dtid {}] leaving a SIGKILLed thread's request unanswered",
3999 dtid
4000 );
4001 return (None, R::Retired);
4002 }
4003 if child.is_some_and(|child| sched.thread_is_logically_killed(child))
4004 && exec_reconnect.is_none()
4005 {
4006 trace!(
4007 "[detcore, dtid {}] rejecting registration that reuses a tombstoned child TID",
4008 dtid
4009 );
4010 return (None, R::ThreadExited);
4011 }
4012
4013 if let GlobalRequest::ResumeExec(process) = &request
4014 && (dtid != *process
4015 || sched.registered_process(dtid) != Some(*process)
4016 || !sched.next_turns.contains_key(&dtid))
4017 {
4018 return (None, R::ThreadExited);
4019 }
4020
4021 self.acknowledge_exec_transfer(dtid, request_mm);
4024
4025 let is_thread_reconnect = matches!(
4026 &request,
4027 GlobalRequest::StartNewThread(child_dettid, ..) if *child_dettid == dtid
4028 ) && self.global_time.lock().unwrap().contains_thread(dtid);
4029
4030 if matches!(&request, GlobalRequest::CreateVforkChildThread(_, _, child, ..) if *child == dtid)
4033 && self.clock_belongs_to_earlier_thread(&sched, dtid)
4034 {
4035 self.global_time.lock().unwrap().retire_thread(dtid);
4036 }
4037
4038 if tombstoned_deregistration.is_none()
4043 && exec_reconnect.is_none()
4044 && !is_thread_reconnect
4045 {
4046 self.global_time.lock().unwrap().update_global_time(
4047 dtid,
4048 time_from_guest,
4049 guest_time.inherited_nanos(),
4050 );
4051 }
4052 }
4053 if let Some(deregistration) = tombstoned_deregistration {
4054 self.recv_deregister_thread(from, deregistration).await;
4055 return (None, R::DeregisterThread(()));
4056 }
4057
4058 #[allow(clippy::unit_arg)]
4062 let resp = match request {
4063 GlobalRequest::PrepareNativeExec(_)
4064 | GlobalRequest::SealNativeExecStats { .. }
4065 | GlobalRequest::IssueNativeExec { .. }
4066 | GlobalRequest::CancelNativeExec { .. }
4067 | GlobalRequest::CompleteNativeExec { .. } => {
4068 unreachable!("native controls handled before ordinary admission")
4069 }
4070 GlobalRequest::ReconnectExec { .. } | GlobalRequest::RetireExec { .. } => {
4071 unreachable!("exec transfer handled before ordinary admission")
4072 }
4073 GlobalRequest::WithForwardedCount(..) => {
4074 unreachable!("a counted request is unwrapped on arrival")
4075 }
4076 GlobalRequest::ReplayDeparture { .. } => {
4077 unreachable!("replay findings use the authenticated neutral path")
4078 }
4079 GlobalRequest::ResumeExec(process) => {
4080 let mut resources = Resources::new(dtid);
4081 resources.insert(ResourceID::MemAddrSpace(process), Permission::RW);
4082 let (response, duration) = self
4083 .recv_request_resources(from, process, resources, Some(request_mm))
4084 .await;
4085 match response {
4086 SchedulerRpcResult::Continue(status) => R::ResumeExec(status, duration),
4087 SchedulerRpcResult::ThreadExited => R::ThreadExited,
4088 }
4089 }
4090 GlobalRequest::SignalDequeued { .. } => {
4091 unreachable!("consuming path handled before ordinary cancellation")
4092 }
4093 GlobalRequest::RecordHostInput { .. } | GlobalRequest::RecordHostMutation { .. } => {
4094 unreachable!("host-input observation answered before clock accounting")
4095 }
4096 GlobalRequest::SharedOpenFile(_) => {
4097 unreachable!("shared open file control answered before clock accounting")
4098 }
4099 GlobalRequest::GuestLog(..) => {
4100 unreachable!("a guest log record is answered before clock accounting")
4101 }
4102 GlobalRequest::TakeSigkillRobustList => {
4103 unreachable!("a SIGKILL victim's robust list is taken before clock accounting")
4104 }
4105 GlobalRequest::SigkillDyingThreads(token) => R::SigkillDyingThreads(
4106 self.lock_rpc_scheduler(false)
4107 .await
4108 .sigkill_dying_threads(token),
4109 ),
4110 GlobalRequest::RecordSigkillRobustLists(token, lists) => {
4111 self.lock_rpc_scheduler(false)
4112 .await
4113 .record_sigkill_robust_lists(token, lists);
4114 R::RecordSigkillRobustLists(())
4115 }
4116 GlobalRequest::AnnounceRefusedSignalControls(..) => {
4117 unreachable!(
4118 "refused signal controls are answered before the forwarded-record drain"
4119 )
4120 }
4121 GlobalRequest::HostDiskstats => {
4122 unreachable!("the host's diskstats are answered before the forwarded-record drain")
4123 }
4124 GlobalRequest::ProcfsSequence(..) | GlobalRequest::StoreProcfsSequence(..) => {
4125 unreachable!("record sequences are answered before the forwarded-record drain")
4126 }
4127 GlobalRequest::MappingCapture(_) => {
4128 unreachable!("coherent mapping capture precedes clock accounting")
4129 }
4130 GlobalRequest::CheckBrkMappings(..) => {
4131 unreachable!("mapping capture is answered before clock accounting")
4132 }
4133 GlobalRequest::HoldTable(_) => {
4134 unreachable!("a held table is answered before clock accounting")
4135 }
4136 #[cfg(target_arch = "aarch64")]
4137 GlobalRequest::StoreExclusive(_) => {
4138 unreachable!("a store-exclusive report is answered before clock accounting")
4139 }
4140 GlobalRequest::Sigalrm(_) => {
4141 unreachable!("SIGALRM ledger control answered before clock accounting")
4142 }
4143 GlobalRequest::ChildExitSigchld(_) => {
4144 unreachable!("child-exit SIGCHLD control answered before clock accounting")
4145 }
4146 GlobalRequest::DiscardedSignal(_) => {
4147 unreachable!("discarded-at-send control answered before clock accounting")
4148 }
4149 GlobalRequest::RecordDeterminismLoss(_) => {
4150 unreachable!("determinism loss answered before clock accounting")
4151 }
4152 GlobalRequest::CallbackCheckpointMismatch { .. } => {
4153 unreachable!("callback mismatch answered before clock accounting")
4154 }
4155 GlobalRequest::PublishSignalDispositions(..) => {
4156 unreachable!("published dispositions answered before clock accounting")
4157 }
4158 GlobalRequest::PublishSignalMask(_) => {
4159 unreachable!("a published mask is answered before clock accounting")
4160 }
4161 GlobalRequest::PublishRuntimePrivateMemory(_) => {
4162 unreachable!("private-memory publication precedes clock accounting")
4163 }
4164 GlobalRequest::ParkedRequest(rs, pid, capability) => {
4165 let (response, _, stack) = self
4166 .recv_resources_with_origin(
4167 from,
4168 pid,
4169 rs,
4170 Some(request_mm),
4171 RpcOrigin::DirectRequestResources,
4172 capability,
4173 )
4174 .await;
4175 match response {
4176 SchedulerRpcResult::Continue(r) => {
4177 Self::with_boundary_stack(R::ParkedRequest(r), stack)
4178 }
4179 SchedulerRpcResult::ThreadExited => R::ThreadExited,
4180 }
4181 }
4182 GlobalRequest::ResumeParkedRequest {
4183 ticket,
4184 current_site,
4185 } => {
4186 self.recv_resume_parked(from, request_mm, ticket, current_site)
4187 .await
4188 }
4189 GlobalRequest::FinishParkedObservation {
4190 wait,
4191 lease,
4192 site,
4193 finish,
4194 } => {
4195 let ack = Ivar::new();
4196 let intent = ControlIntent::Finish {
4197 wait,
4198 lease,
4199 site,
4200 finish,
4201 ack: ack.clone(),
4202 };
4203 let posted = self
4204 .sched
4205 .lock()
4206 .unwrap()
4207 .post_control(dtid, request_mm, intent);
4208 if let Err(error) = posted {
4209 self.sched.lock().unwrap().fail_parked(dtid, error);
4210 return (None, R::ThreadExited);
4211 }
4212 R::FinishParkedObservation(ack.await)
4213 }
4214 GlobalRequest::ParkedProtocolFailure(error) => {
4215 self.sched.lock().unwrap().fail_parked(dtid, error);
4216 return (None, R::ThreadExited);
4217 }
4218
4219 GlobalRequest::RequestResources(rs, pid) => {
4220 let (response, time, stack) = self
4221 .recv_resources_with_origin(
4222 from,
4223 pid,
4224 rs,
4225 Some(request_mm),
4226 RpcOrigin::DirectRequestResources,
4227 ControlCapability::None,
4228 )
4229 .await;
4230 match self.resource_grant_reply(from, response, time).0 {
4231 SchedulerRpcResult::Continue(response) => {
4232 Self::with_boundary_stack(R::RequestResources(response), stack)
4233 }
4234 SchedulerRpcResult::ThreadExited => R::ThreadExited,
4235 }
4236 }
4237 GlobalRequest::ReleaseResources(rs) => {
4238 R::ReleaseResources(self.recv_release_resources(from, rs).await)
4239 }
4240 GlobalRequest::ReleaseAllResources => {
4241 R::ReleaseAllResources(self.recv_release_all_resources(from).await)
4242 }
4243 GlobalRequest::PolicyRefusal(reason) => {
4244 self.policy_refusal.lock().unwrap().get_or_insert(reason);
4245 R::PolicyRefusal(())
4246 }
4247 GlobalRequest::StoppingRefusal(reason) => {
4269 if self.cfg.in_guest_signal_delivery {
4270 crate::scheduler::exit_on_scheduler_refusal(&reason)
4271 }
4272 warn!(
4273 "[detcore] a stopping refusal was filed without the in-guest delivery gate; recorded as a policy refusal, without the run-wide exit: {}",
4274 reason
4275 );
4276 self.policy_refusal.lock().unwrap().get_or_insert(reason);
4277 R::PolicyRefusal(())
4278 }
4279 GlobalRequest::ReportUnsupportedSyscall(name) => {
4281 let _sched = self.lock_rpc_scheduler(false).await;
4282 let inserted = self
4283 .unsupported_syscalls
4284 .lock()
4285 .unwrap()
4286 .insert(name.clone());
4287 if inserted
4288 && let Some(report) = &self.unsupported_syscall_report_fd
4289 && let Err(error) = writeln!(report.lock().unwrap(), "{name}")
4290 {
4291 warn!("failed to append unsupported-syscall report: {error}");
4292 }
4293 R::ReportUnsupportedSyscall(())
4294 }
4295 GlobalRequest::AnnounceSignalHandlerNotInstalled(signum) => {
4296 if self
4297 .announced_handlers_not_installed
4298 .lock()
4299 .unwrap()
4300 .insert(signum)
4301 {
4302 warn!("{}", signal_handler_not_installed_warning(signum));
4303 }
4304 R::AnnounceSignalHandlerNotInstalled(())
4305 }
4306 GlobalRequest::PrepareExec(process, mm, fd_blocking) => {
4307 let mut sched = self.lock_rpc_scheduler(false).await;
4308 if mm != request_mm
4309 || fd_blocking.initialize_inherited_stdio
4310 || fd_blocking.initial_input.is_some()
4311 {
4312 return (None, R::ThreadExited);
4313 }
4314 if self.cfg.sequentialize_threads && !sched.prepare_exec_teardown(dtid, process, mm)
4315 {
4316 return (None, R::ThreadExited);
4317 }
4318 trace!(
4319 "[detcore, dtid {}] preparing exec for process {} with mm {:?} and logically blocking descriptors {:?}",
4320 dtid, process, mm, fd_blocking,
4321 );
4322 self.pending_exec_states.lock().unwrap().insert(
4323 process,
4324 PendingExecState {
4325 caller: dtid,
4326 process,
4327 mm,
4328 fd_blocking: *fd_blocking,
4329 },
4330 );
4331 R::PrepareExec(())
4332 }
4333 GlobalRequest::CancelExec(process) => {
4334 let mut sched = self.lock_rpc_scheduler(false).await;
4335 let mut pending = self.pending_exec_states.lock().unwrap();
4336 if pending
4337 .get(&process)
4338 .is_some_and(|state| state.caller == dtid)
4339 {
4340 let prepared = pending.remove(&process).unwrap();
4341 sched.finish_exec_teardown(prepared.caller, process, prepared.mm, false);
4342 }
4343 R::CancelExec(())
4344 }
4345 GlobalRequest::MarkPastFirstExecve(
4346 detpid,
4347 signal_identity,
4348 unprepared_exe,
4349 kernel_image,
4350 ) => {
4351 let mut sched = self.lock_rpc_scheduler(false).await;
4352 self.lock_ledger.lock().unwrap().exec(detpid);
4355 let prepared = self.pending_exec_states.lock().unwrap().get(&dtid).cloned();
4359 if sched.signal_control_installed() {
4360 let result = (|| {
4361 let identity = signal_identity.ok_or(ProtocolFailure::Identity)?;
4362 let pid = sched
4363 .registered_process(dtid)
4364 .ok_or(ProtocolFailure::Identity)?;
4365 let mut pending = self.pending_exec_states.lock().unwrap();
4366 if let Some(prepared) = pending.get(&pid) {
4367 if prepared.caller != dtid || prepared.process != pid {
4368 return Err(ProtocolFailure::Identity);
4369 }
4370 sched.complete_signal_exec(
4371 pid,
4372 dtid,
4373 prepared.mm,
4374 request_mm,
4375 identity,
4376 )?;
4377 pending.remove(&pid);
4378 } else {
4379 sched
4383 .real_timers
4384 .validate_task(pid, dtid, request_mm, identity)?;
4385 }
4386 Ok(())
4387 })();
4388 if let Err(error) = result {
4389 sched.fail_parked(dtid, error);
4390 return (None, R::ThreadExited);
4391 }
4392 }
4393 sched.delete_exec_posix_timers(detpid);
4397 sched.record_successful_exec(detpid);
4400 if let Some(prepared) = &prepared
4404 && self.cfg.sequentialize_threads
4405 && prepared.caller == dtid
4406 && prepared.process == dtid
4407 && prepared.mm.for_exec(dtid) == request_mm
4408 {
4409 assert!(
4415 prepared.fd_blocking.robust_wakes.is_empty(),
4416 "a state-preserving exec carried robust-list wakes to the coordinator"
4417 );
4418 sched.reconnect_after_exec(ExecReconnect {
4419 caller: dtid,
4420 new_leader: dtid,
4421 detpid: dtid,
4422 pre_exec_mm: prepared.mm,
4423 post_exec_mm: request_mm,
4424 child_tid_addr: 0,
4425 reconnect_priority: None,
4426 });
4427 self.pending_exec_states.lock().unwrap().remove(&dtid);
4428 if let Some(registry) = self.native_exec_registry.get()
4432 && registry
4433 .lock()
4434 .unwrap()
4435 .mark_past_first_exec(dtid, request_mm)
4436 .is_err()
4437 {
4438 return (None, R::ThreadExited);
4439 }
4440 }
4441 if self.cfg.sequentialize_threads {
4445 sched.release_pi_futexes_of(dtid);
4446 }
4447 self.past_first_execve.store(true, SeqCst);
4448 let first_image = !self.initial_descriptor_image_complete.swap(true, SeqCst);
4449 let mut overrides = self
4450 .post_exec_fd_blocking
4451 .lock()
4452 .unwrap()
4453 .remove(&dtid)
4454 .unwrap_or_default();
4455 trace!(
4456 "[detcore, dtid {}] restoring logically blocking descriptors after exec: {:?}",
4457 dtid, overrides,
4458 );
4459 overrides.initialize_inherited_stdio =
4460 first_image && self.cfg.backend.tool_shares_guest_descriptor_table;
4461 if !self.cfg.sequentialize_threads {
4472 overrides.exe = None;
4473 } else if overrides.exe.is_none() {
4474 overrides.exe = prepared
4475 .filter(|prepared| prepared.caller == dtid)
4476 .and_then(|prepared| prepared.fd_blocking.exe)
4477 .or(unprepared_exe);
4481 }
4482 if self.root_dettid.get() == Some(&dtid)
4483 && !self.root_image_marked.swap(true, SeqCst)
4484 && let Some(launch) = &self.cfg.launch_image
4485 {
4486 let launched = launch.exe.as_deref().and_then(|exe| {
4487 crate::exec_record::ExecRecord::at(std::path::Path::new("/"), exe)
4488 });
4489 overrides.exe = if launch.launcher.is_some() {
4490 overrides
4496 .exe
4497 .take()
4498 .filter(|record| Some(record.identity()) != self.launcher_identity())
4499 .or(launched)
4500 } else {
4501 launched
4502 };
4503 }
4504 if let Some(kernel) = kernel_image {
4511 overrides.exe = overrides.exe.filter(|record| {
4512 record.identity() == kernel || self.launcher_identity() == Some(kernel)
4513 });
4514 }
4515 overrides.exe = overrides
4518 .exe
4519 .filter(|record| Some(record.identity()) != self.launcher_identity());
4520 {
4521 let mut images = self.exec_images.lock().unwrap();
4522 match &overrides.exe {
4523 Some(record) => images.insert(detpid, record.clone()),
4524 None => images.remove(&detpid),
4525 };
4526 }
4527 if self.cfg.backend.tool_shares_guest_descriptor_table {
4528 let mut input = self.inherited_input.lock().unwrap();
4529 if input
4530 .as_ref()
4531 .is_some_and(|input| input.root == Some((dtid, request_mm)))
4532 {
4533 overrides.initial_input =
4538 input.take().map(|input| input.lease.identity().clone());
4539 }
4540 }
4541 sched
4546 .publish_reserved_input_exec_authority(dtid, detpid, request_mm)
4547 .unwrap_or_else(|error| {
4548 crate::scheduler::exit_on_scheduler_refusal(&format!(
4549 "HERMIT_CHOSEN_INPUT_AUTHORITY: {error}"
4550 ))
4551 });
4552 R::MarkPastFirstExecve(Box::new(overrides))
4553 }
4554 GlobalRequest::CreateChildThread(
4556 dettid,
4557 parent_detpid,
4558 ctid,
4559 flags,
4560 exit_signal,
4561 physical_ids,
4562 priority,
4563 ) => {
4564 if let Some(prepared) = &exec_reconnect {
4565 let mut sched = self.lock_rpc_scheduler(false).await;
4566 let (mut pending, post_exec_mm) = {
4567 let mut states = self.pending_exec_states.lock().unwrap();
4568 let Some(pending) = states.remove(&parent_detpid) else {
4569 return (None, R::ThreadExited);
4570 };
4571 assert_eq!(&pending, prepared);
4572 let post_exec_mm = pending.mm.for_exec(pending.process);
4573 (pending, post_exec_mm)
4574 };
4575 assert_eq!(pending.process, parent_detpid);
4576 if let Some((physical_pid, physical_tid)) = physical_ids
4577 && let Err(open_error) = sched.register_physical_thread(
4578 dettid,
4579 post_exec_mm,
4580 physical_pid,
4581 physical_tid,
4582 )
4583 {
4584 error!(
4585 "[detcore, dtid {}] failed to register post-exec host process {} thread {}: {}",
4586 dettid, physical_pid, physical_tid, open_error,
4587 );
4588 return (None, R::ThreadExited);
4589 }
4590 let retired = sched.reconnect_after_exec(ExecReconnect {
4591 caller: pending.caller,
4592 new_leader: dettid,
4593 detpid: parent_detpid,
4594 pre_exec_mm: pending.mm,
4595 post_exec_mm,
4596 child_tid_addr: ctid,
4597 reconnect_priority: priority,
4598 });
4599 if pending.caller != dettid {
4600 self.global_time
4601 .lock()
4602 .unwrap()
4603 .reassign_thread(pending.caller, dettid);
4604 }
4605 let robust_wakes = std::mem::take(&mut pending.fd_blocking.robust_wakes);
4614 wake_exec_robust_waiters(&mut sched, &robust_wakes, "exec");
4615 if !pending.fd_blocking.is_empty() {
4616 self.post_exec_fd_blocking
4617 .lock()
4618 .unwrap()
4619 .insert(dettid, pending.fd_blocking);
4620 }
4621 debug!(
4622 "[detcore, dtid {}] reconciled successful exec from caller {}; retired prior identities {:?}",
4623 dtid, pending.caller, retired
4624 );
4625 R::CreateChildThread(Some(post_exec_mm))
4626 } else {
4627 let registration = self
4628 .recv_create_child_thread(
4629 from,
4630 request_mm,
4631 ChildRegistration {
4632 parent_dettid: DetTid::from_raw(from.into()),
4633 parent_detpid,
4634 child_dettid: dettid,
4635 child_tid_addr: ctid,
4636 flags,
4637 exit_signal,
4638 physical_ids,
4639 maybe_priority: priority,
4640 parent_is_kernel_blocked: false,
4641 },
4642 )
4643 .await;
4644 #[cfg(feature = "physical-mm-test-support")]
4645 self.initial_mm_registration_result(dettid, ®istration);
4646 match registration {
4647 SchedulerRpcResult::Continue(()) => R::CreateChildThread(None),
4648 SchedulerRpcResult::ThreadExited => R::ThreadExited,
4649 }
4650 }
4651 }
4652 GlobalRequest::CreateVforkChildThread(
4654 parent_dettid,
4655 parent_detpid,
4656 child_dettid,
4657 ctid,
4658 flags,
4659 exit_signal,
4660 priority,
4661 ) => match self
4662 .recv_create_child_thread(
4663 from,
4664 request_mm,
4665 ChildRegistration {
4666 parent_dettid,
4667 parent_detpid,
4668 child_dettid,
4669 child_tid_addr: ctid,
4670 flags: Some(flags),
4671 exit_signal,
4672 physical_ids: None,
4673 maybe_priority: priority,
4674 parent_is_kernel_blocked: true,
4675 },
4676 )
4677 .await
4678 {
4679 SchedulerRpcResult::Continue(()) => R::CreateChildThread(None),
4680 SchedulerRpcResult::ThreadExited => R::ThreadExited,
4681 },
4682 GlobalRequest::StartNewThread(dettid, detpid, physical_ids, signal_identity) => {
4684 match self
4685 .recv_start_new_thread(
4686 from,
4687 dettid,
4688 detpid,
4689 request_mm,
4690 physical_ids,
4691 signal_identity,
4692 )
4693 .await
4694 {
4695 SchedulerRpcResult::Continue(history) => {
4696 let mut sched = self.lock_rpc_scheduler(false).await;
4697 if let Err(refusal) = sched.authenticate_priority_birth(dettid) {
4700 crate::scheduler::exit_on_scheduler_refusal(&refusal);
4701 }
4702 sched.drop_released_callback_checkpoints(dettid);
4705 let startup = crate::preemptions::callback_history::Startup {
4706 checkpoints: sched
4707 .callback_checkpoints
4708 .get(&dettid)
4709 .into_iter()
4710 .flatten()
4711 .cloned()
4712 .collect(),
4713 progress: sched
4714 .callback_progress
4715 .get(&dettid)
4716 .cloned()
4717 .unwrap_or_default(),
4718 };
4719 let response = R::StartNewThread(history);
4720 if sched.callback_history_active {
4721 R::WithCallbackHistory(startup, Box::new(response))
4722 } else {
4723 response
4724 }
4725 }
4726 SchedulerRpcResult::ThreadExited => R::ThreadExited,
4727 }
4728 }
4729 GlobalRequest::DeregisterThread(deregistration) => {
4730 self.held_tables
4732 .lock()
4733 .unwrap()
4734 .remove(&deregistration.dettid);
4735 R::DeregisterThread(self.recv_deregister_thread(from, deregistration).await)
4736 }
4737 GlobalRequest::PublishedGuestSigmask(thread) => {
4738 let scheduler = self.lock_rpc_scheduler(false).await;
4739 if self.cfg.in_guest_signal_delivery {
4743 R::GuestSigmaskForStatus(scheduler.status_guest_mask(thread))
4744 } else {
4745 R::PublishedGuestSigmask(scheduler.published_guest_sigmask(thread))
4746 }
4747 }
4748 GlobalRequest::PublishTemporaryMask(control) => R::PublishTemporaryMask(
4749 self.lock_rpc_scheduler(false)
4750 .await
4751 .temporary_mask_control(dtid, control),
4752 ),
4753 GlobalRequest::VirtualInotifyCookies(host) => {
4754 let mut table = self
4755 .inotify_cookies
4756 .lock()
4757 .expect("inotify cookie table lock poisoned");
4758 R::VirtualInotifyCookies(
4759 host.into_iter()
4760 .map(|cookie| table.virtualize(cookie))
4761 .collect(),
4762 )
4763 }
4764 GlobalRequest::NextGuestId => R::NextGuestId(self.take_guest_id().await),
4765 GlobalRequest::GaveHostId => {
4766 self.host_id_tasks.fetch_add(1, SeqCst);
4767 if let Some(writer) = &mut self.lock_rpc_scheduler(false).await.preemption_writer {
4770 writer.set_guest_ids(detcore_model::pid::GuestIds::Host);
4771 }
4772 R::GaveHostId(())
4773 }
4774 GlobalRequest::InPidNamespaceInit(thread) => {
4775 let scheduler = self.lock_rpc_scheduler(false).await;
4776 R::InPidNamespaceInit(
4777 scheduler
4778 .thread_tree
4779 .process_of(thread)
4780 .is_some_and(|process| {
4781 scheduler.thread_tree.is_pid_namespace_init(process)
4782 }),
4783 )
4784 }
4785 GlobalRequest::SetSavedGuestSigmask(mask) => {
4786 let updated = self
4787 .lock_rpc_scheduler(false)
4788 .await
4789 .set_saved_guest_sigmask(dtid, mask);
4790 if updated {
4791 R::SetSavedGuestSigmask(())
4792 } else {
4793 R::ThreadExited
4794 }
4795 }
4796 GlobalRequest::SetSigtimedwaitSet(set) => {
4797 let updated = self
4798 .lock_rpc_scheduler(false)
4799 .await
4800 .set_sigtimedwait_set(dtid, set);
4801 if updated {
4802 R::SetSigtimedwaitSet(())
4803 } else {
4804 R::ThreadExited
4805 }
4806 }
4807 GlobalRequest::RecordGuestMarkerSignal => {
4808 self.lock_rpc_scheduler(false)
4809 .await
4810 .record_guest_marker_signal();
4811 R::RecordGuestMarkerSignal(())
4812 }
4813 GlobalRequest::OwnProcessSignalInputs => R::OwnProcessSignalInputs(
4814 self.lock_rpc_scheduler(false)
4815 .await
4816 .own_process_signal_inputs(dtid),
4817 ),
4818 GlobalRequest::ProcessSignalReceiver(process, named, signo) => {
4819 R::ProcessSignalReceiver(
4820 self.lock_rpc_scheduler(false)
4821 .await
4822 .process_signal_receiver(process, named, dtid, signo),
4823 )
4824 }
4825 GlobalRequest::OwnProcessSignalChoice(named, signo) => {
4826 self.lock_rpc_scheduler(false)
4827 .await
4828 .own_process_signal_choice(dtid, named, signo);
4829 R::OwnProcessSignalChoice(())
4830 }
4831 GlobalRequest::ProcessSignalSent(process) => {
4832 self.lock_rpc_scheduler(false)
4833 .await
4834 .process_signal_sent(dtid, process);
4835 R::ProcessSignalSent(())
4836 }
4837 GlobalRequest::SetChildTidAddress(address) => {
4838 let updated = self
4839 .lock_rpc_scheduler(false)
4840 .await
4841 .set_child_tid_address(dtid, address);
4842 if updated {
4843 R::SetChildTidAddress(())
4844 } else {
4845 R::ThreadExited
4846 }
4847 }
4848 GlobalRequest::FutexAction(dettid, action, futexid, init_read, mask, passed_out) => {
4849 R::FutexAction(
4850 self.recv_futex_action(
4851 RpcIncarnation {
4852 dettid,
4853 mm: request_mm,
4854 },
4855 action,
4856 futexid,
4857 init_read,
4858 mask,
4859 passed_out,
4860 )
4861 .await,
4862 )
4863 }
4864 GlobalRequest::RobustListWakes(wakes) => {
4865 R::RobustListWakes(self.recv_robust_list_wakes(dtid, wakes))
4866 }
4867 GlobalRequest::DeterminizeInode(ino, observed, sighting, procfs) => {
4868 R::DeterminizeInode(
4869 self.recv_determinize_inode(from, ino, procfs, observed, sighting)
4870 .await,
4871 )
4872 }
4873 GlobalRequest::DeterminizeMappingInodes(inodes) => R::DeterminizeMappingInodes(
4874 self.recv_determinize_mapping_inodes(from, inodes).await,
4875 ),
4876 GlobalRequest::DeterminizeDevice(dev) => {
4879 R::DeterminizeDevice(self.recv_determinize_device(from, dev).await)
4880 }
4881 GlobalRequest::DeterminizeMountId(raw_mount_id, fallback_order) => {
4882 R::DeterminizeMountId(
4883 self.recv_determinize_mount_id(from, raw_mount_id, fallback_order.as_deref())
4884 .await,
4885 )
4886 }
4887 GlobalRequest::ResolveMountinfoIds(mountinfo_order, observation_order) => {
4888 R::ResolveMountinfoIds(
4889 self.recv_resolve_mountinfo_ids(from, &mountinfo_order, &observation_order)
4890 .await,
4891 )
4892 }
4893 GlobalRequest::RetireInode(ino) => {
4894 R::RetireInode(self.recv_retire_inode(from, ino).await)
4895 }
4896 GlobalRequest::ForgetRetiredInode(ino, procfs) => {
4897 R::ForgetRetiredInode(self.recv_forget_retired_inode(from, ino, procfs).await)
4898 }
4899 GlobalRequest::DirectorySnapshotMark => {
4900 let _sched = self.lock_rpc_scheduler(false).await;
4901 let inodes = self.inodes.lock().unwrap();
4902 R::DirectorySnapshotMark(SnapshotMark {
4903 retirements: inodes.retirements,
4904 threads_counted: inodes.threads_counted,
4905 })
4906 }
4907 GlobalRequest::ReadyChannel(channel, wants) => R::ReadyChannel(
4908 self.lock_rpc_scheduler(false)
4909 .await
4910 .ready_channel(dtid, channel, wants),
4911 ),
4912 GlobalRequest::HandOffTurn => {
4913 R::HandOffTurn(self.lock_rpc_scheduler(false).await.hand_off_turn(dtid))
4914 }
4915 GlobalRequest::CommitFatalDelivery(retire_others) => R::CommitFatalDelivery(
4916 self.lock_rpc_scheduler(false)
4917 .await
4918 .commit_fatal_delivery(dtid, retire_others),
4919 ),
4920 GlobalRequest::TouchFile(ino) => R::TouchFile(self.recv_touch_file(from, ino).await),
4921 GlobalRequest::SetFileMtime(ino, mtime) => {
4922 R::SetFileMtime(self.recv_set_file_mtime(from, ino, mtime).await)
4923 }
4924 GlobalRequest::GlobalTimeLowerBound => {
4925 let ns = self.global_time.lock().unwrap().as_nanos();
4926 R::GlobalTimeLowerBound(ns)
4927 }
4928 GlobalRequest::TraceSchedEvent(ev, detpid, command_bootstrap, access) => {
4929 match self
4930 .recv_trace_schedevent(ev, detpid, request_mm, command_bootstrap, access)
4931 .await
4932 {
4933 SchedulerRpcResult::Continue(response) => R::TraceSchedEvent(response),
4934 SchedulerRpcResult::ThreadExited => R::ThreadExited,
4935 }
4936 }
4937 GlobalRequest::RegisterAlarm(dpid, dtid, duration, interval, sig) => {
4941 let now = self.global_time.lock().unwrap().as_nanos();
4942 self.sched.lock().unwrap().reconcile_alarm(dpid, now);
4946 let response = match self
4947 .recv_register_alarm(
4948 dpid,
4949 RpcIncarnation {
4950 dettid: dtid,
4951 mm: request_mm,
4952 },
4953 now,
4954 duration,
4955 interval,
4956 sig,
4957 )
4958 .await
4959 {
4960 SchedulerRpcResult::Continue(remaining) => R::RegisterAlarm(remaining),
4961 SchedulerRpcResult::ThreadExited => R::ThreadExited,
4962 };
4963 self.sched.lock().unwrap().note_arming_hint(dtid, now);
4964 response
4965 }
4966 GlobalRequest::AlarmRemaining(dpid) => {
4969 let now = self.global_time.lock().unwrap().as_nanos();
4970 let mut sched = self.lock_rpc_scheduler(false).await;
4971 if sched.reconcile_alarm(dpid, now).is_some() {
4975 sched.note_arming_hint(dtid, now);
4976 }
4977 match sched.itimer_snapshot(dpid, now) {
4978 Ok(snapshot) => R::AlarmRemaining(snapshot),
4979 Err(error) => {
4980 sched.fail_parked(dtid, ProtocolFailure::Timer(error));
4981 R::ThreadExited
4982 }
4983 }
4984 }
4985 GlobalRequest::NoteGuestSigalrm(target) => {
4989 self.sched.lock().unwrap().note_guest_sigalrm(dtid, target);
4990 R::NoteGuestSigalrm
4991 }
4992 GlobalRequest::NoteSigalrmDisposition => {
4993 self.sched.lock().unwrap().note_sigalrm_disposition(dtid);
4994 R::NoteGuestSigalrm
4995 }
4996 GlobalRequest::ReconcileAlarm(dpid) => {
4997 let now = self.global_time.lock().unwrap().as_nanos();
4998 let mut sched = self.sched.lock().unwrap();
4999 if sched.reconcile_alarm(dpid, now).is_some() {
5000 sched.note_arming_hint(dtid, now);
5001 }
5002 R::ReconcileAlarm
5003 }
5004 GlobalRequest::RegisterPosixTimer(
5007 dpid,
5008 dtid,
5009 timer_id,
5010 deadline,
5011 interval,
5012 notification,
5013 ) => {
5014 let now = self.global_time.lock().unwrap().as_nanos();
5015 let response = match self
5016 .recv_register_posix_timer(
5017 dpid,
5018 RpcIncarnation {
5019 dettid: dtid,
5020 mm: request_mm,
5021 },
5022 timer_id,
5023 deadline,
5024 interval,
5025 notification,
5026 )
5027 .await
5028 {
5029 SchedulerRpcResult::Continue(()) => R::RegisterPosixTimer(()),
5030 SchedulerRpcResult::ThreadExited => R::ThreadExited,
5031 };
5032 self.sched.lock().unwrap().note_arming_hint(dtid, now);
5033 response
5034 }
5035 GlobalRequest::ResolveKillTargets(dpid) => {
5038 let scheduler = self.lock_rpc_scheduler(false).await;
5039 R::ResolveKillTargets(
5040 scheduler.process_signal_targets(dpid),
5041 scheduler.exited_process_is_zombie(dpid),
5042 )
5043 }
5044 GlobalRequest::NamesHermitTask(caller, tgid, tid) => {
5045 R::NamesHermitTask(hermit_task_named(caller, tgid, tid))
5046 }
5047 GlobalRequest::KernelRunTarget(target) => R::KernelRunTarget(
5048 self.lock_rpc_scheduler(false)
5049 .await
5050 .kernel_run_block(target),
5051 ),
5052 GlobalRequest::HoldTurnForGroupExit(dettid) => R::HoldTurnForGroupExit(
5053 self.lock_rpc_scheduler(false)
5054 .await
5055 .hold_turn_for_group_exit(dettid),
5056 ),
5057 GlobalRequest::NotifySignalPending(
5058 dettid,
5059 SigWrapper(signal),
5060 target_process,
5061 scope,
5062 ) => {
5063 let (fence, valve) = {
5064 let mut scheduler = self.lock_rpc_scheduler(false).await;
5065 scheduler.note_committed_send_in_window(
5072 dettid,
5073 signal,
5074 scope == SignalScope::Thread,
5075 );
5076 scheduler.notify_signal_pending(dettid, SigWrapper(signal));
5077 let dbt = self.cfg.backend.requires_thread_directed_process_signals;
5078 let process_directed = scope == SignalScope::Process;
5086 let target_process = if signal == libc::SIGKILL && dbt {
5087 target_process.or_else(|| scheduler.registered_process(dettid))
5088 } else {
5089 target_process
5090 };
5091 let mut fence = None;
5092 if signal == libc::SIGKILL {
5093 scheduler.record_committed_sigkill(dettid, target_process);
5094 if let Some(detpid) = target_process {
5095 if dbt {
5096 fence =
5097 fence_or_refuse(&scheduler, dettid, detpid, process_directed);
5098 }
5099 scheduler.note_process_sigkill(dettid, detpid, process_directed);
5100 }
5101 }
5102 (fence, scheduler.sigkill_fence_valve)
5103 };
5104 if let Some((detpid, pidfd)) = fence
5112 && let Err(reason) = wait_for_process_exit(pidfd, valve).await
5113 {
5114 crate::scheduler::exit_on_scheduler_refusal(
5115 &crate::scheduler::sigkill_retirement::UnfencedSigkill {
5116 process: detpid,
5117 reason,
5118 },
5119 );
5120 }
5121 R::NotifySignalPending(())
5122 }
5123 GlobalRequest::NotifyUnresolvedSigkill(group) => {
5124 self.lock_rpc_scheduler(false)
5125 .await
5126 .record_unresolved_sigkill(group);
5127 R::NotifyUnresolvedSigkill(())
5128 }
5129 GlobalRequest::ReserveSigkill(sender, target, uid) => {
5130 let mut scheduler = self.lock_rpc_scheduler(false).await;
5131 let reserved = scheduler
5132 .registered_process(target)
5133 .ok_or(crate::scheduler::sigkill_retirement::SigkillRefusal::NoTarget)
5134 .and_then(|process| scheduler.reserve_sigkill(sender, process, uid));
5135 R::ReserveSigkill(reserved.ok())
5136 }
5137 GlobalRequest::ReserveSigkillGroup(sender, pgid, uid) => R::ReserveSigkill(
5138 self.lock_rpc_scheduler(false)
5139 .await
5140 .reserve_sigkill_group(sender, pgid, uid)
5141 .ok(),
5142 ),
5143 GlobalRequest::CommitSigkill(token, sent) => {
5144 let (victims, wait) = {
5148 let mut sched = self.lock_rpc_scheduler(false).await;
5149 let wait = sent && !sched.sigkill_reservation_includes_sender(token);
5150 (sched.commit_sigkill(token, sent), wait)
5151 };
5152 let since = std::time::Instant::now();
5159 if wait {
5160 loop {
5161 {
5162 let mut sched = self.lock_rpc_scheduler(false).await;
5163 if sched.sigkill_settled(&victims) {
5164 break;
5165 }
5166 if since.elapsed() >= sched.sigkill_fence_valve {
5167 sched.refuse_unsettled_sigkill(since.elapsed());
5168 break;
5169 }
5170 }
5171 yield_once().await;
5172 }
5173 }
5174 R::CommitSigkill(())
5175 }
5176 GlobalRequest::ThreadIsLive(dtid) => {
5177 R::ThreadIsLive(self.lock_rpc_scheduler(false).await.thread_is_live(dtid))
5178 }
5179 GlobalRequest::NamesARetiredExitedLeader(named) => R::NamesARetiredExitedLeader(
5180 self.lock_rpc_scheduler(false)
5181 .await
5182 .names_a_retired_exited_leader(named),
5183 ),
5184 GlobalRequest::ParentDeathSignal(arm) => {
5185 let mut sched = self.lock_rpc_scheduler(false).await;
5186 if let Some(signal) = arm {
5187 sched.set_parent_death_signal(dtid, signal);
5188 }
5189 R::ParentDeathSignal(sched.parent_death_signal(dtid))
5190 }
5191 GlobalRequest::ThreadIdRefused(id) => {
5192 let (registered, zombie) = {
5193 let sched = self.lock_rpc_scheduler(false).await;
5194 (
5195 sched.thread_was_registered(id),
5196 sched.id_is_a_zombie_child_of(id, dtid),
5197 )
5198 };
5199 let clocked = self.global_time.lock().unwrap().contains_thread(id);
5200 R::ThreadIdRefused((registered || clocked) && !zombie)
5201 }
5202 GlobalRequest::PendingRealTimeExpirations(detpid) => R::PendingRealTimeExpirations(
5203 self.lock_rpc_scheduler(false)
5204 .await
5205 .pending_real_time_expirations(detpid),
5206 ),
5207 GlobalRequest::RecordHandlerRestoreMask(detpid, unblocked) => {
5208 self.lock_rpc_scheduler(false)
5209 .await
5210 .record_handler_restore_unblocked(detpid, unblocked);
5211 R::RecordHandlerRestoreMask(())
5212 }
5213 GlobalRequest::RegisterSignalfd(detpid, mask) => {
5214 self.lock_rpc_scheduler(false)
5215 .await
5216 .register_signalfd(detpid, mask);
5217 R::RegisterSignalfd(())
5218 }
5219 GlobalRequest::DescriptorsReplaced(files, first, last) => {
5220 self.lock_rpc_scheduler(false)
5221 .await
5222 .note_descriptors_replaced(files, first, last);
5223 R::DescriptorsReplaced(())
5224 }
5225 GlobalRequest::DiscardPendingRealTimeExpirations(detpid, signals) => {
5226 self.lock_rpc_scheduler(false)
5227 .await
5228 .discard_pending_real_time_expirations(detpid, signals);
5229 R::DiscardPendingRealTimeExpirations(())
5230 }
5231 GlobalRequest::RecordHostTimedSignals(dtid, scope, signals) => R::HostTimedSignals(
5232 self.lock_rpc_scheduler(false)
5233 .await
5234 .record_host_timed_signals(dtid, scope, signals),
5235 ),
5236 GlobalRequest::HostTimedSignals(dtid) => R::HostTimedSignals(
5237 self.lock_rpc_scheduler(false)
5238 .await
5239 .host_timed_signals(dtid),
5240 ),
5241 GlobalRequest::HostTimedAndPrivateSigchld(dtid) => {
5242 let (host_timed, private) = self
5243 .lock_rpc_scheduler(false)
5244 .await
5245 .host_timed_and_private_sigchld(dtid);
5246 R::HostTimedAndPrivateSigchld(host_timed, private)
5247 }
5248 GlobalRequest::ExactChildWaitState(parent, child) => R::ExactChildWaitState(
5249 self.lock_rpc_scheduler(false)
5250 .await
5251 .exact_child_wait_state(parent, child),
5252 ),
5253 GlobalRequest::ReadyChildWait(parent, selector) => {
5254 let sched = self.lock_rpc_scheduler(false).await;
5255 R::ReadyChildWait((
5256 sched.ready_child_wait(parent, selector),
5257 sched.has_child_wait_target(parent, selector),
5258 ))
5259 }
5260 GlobalRequest::ConsumeChildWait(parent, child) => R::ConsumeChildWait({
5261 let mut sched = self.lock_rpc_scheduler(false).await;
5262 if let Some(cpu) = work_span_cpu
5263 && sched.thread_tree.parent_process(&child) == Some(parent)
5264 {
5265 sched.work_span_note_reap(dtid, cpu, parent, child);
5266 }
5267 sched.consume_child_wait(parent, child)
5268 }),
5269 GlobalRequest::ProcessGroup(process) => R::ProcessGroup(
5270 self.lock_rpc_scheduler(false)
5271 .await
5272 .thread_tree
5273 .process_group(process),
5274 ),
5275 GlobalRequest::SetProcessGroup(process, group) => R::SetProcessGroup(
5276 self.lock_rpc_scheduler(false)
5277 .await
5278 .thread_tree
5279 .set_process_group(process, group),
5280 ),
5281 GlobalRequest::CreateSession(process) => R::CreateSession(
5282 self.lock_rpc_scheduler(false)
5283 .await
5284 .thread_tree
5285 .create_session(process),
5286 ),
5287 GlobalRequest::RegisteredProcessOf(dettid) => R::RegisteredProcessOf(
5288 self.lock_rpc_scheduler(false)
5289 .await
5290 .thread_tree
5291 .process_of(dettid),
5292 ),
5293 GlobalRequest::ForgetThreadTimerCopies(process, signo) => {
5294 self.lock_rpc_scheduler(false)
5295 .await
5296 .forget_thread_timer_copies(process, signo);
5297 R::ForgetThreadTimerCopies(())
5298 }
5299 GlobalRequest::ExecTimerFlush(process) => R::ExecTimerFlush(
5300 self.lock_rpc_scheduler(false)
5301 .await
5302 .take_exec_timer_flush(dtid, process),
5303 ),
5304 GlobalRequest::RetireExitedLeader => R::RetireExitedLeader(
5305 self.lock_rpc_scheduler(false)
5306 .await
5307 .retire_exited_leader(dtid, request_mm),
5308 ),
5309 GlobalRequest::GuestPid(caller, pid) => {
5310 let sched = self.lock_rpc_scheduler(false).await;
5311 R::GuestPid(sched.guest_pid(caller, pid))
5312 }
5313 GlobalRequest::ProcfsListing(process) => {
5314 R::ProcfsListing(self.lock_rpc_scheduler(false).await.procfs_listing(process))
5315 }
5316 GlobalRequest::ProcfsIds(to_kernel, ids) => R::ProcfsIds(
5317 self.lock_rpc_scheduler(false)
5318 .await
5319 .procfs_ids(to_kernel, &ids),
5320 ),
5321 GlobalRequest::UserNamespaceRelation(reader, task) => {
5322 let sched = self.lock_rpc_scheduler(false).await;
5323 let reader = sched.registered_process(reader).unwrap_or(reader);
5324 let task = sched.registered_process(task).unwrap_or(task);
5325 let record = &sched.thread_tree.user_namespaces;
5326 R::UserNamespaceRelation(
5327 record.relation(reader, task),
5328 record.owner_may_differ_below(reader, task),
5329 )
5330 }
5331 GlobalRequest::UserNamespaceOwnerMayDiffer(process) => {
5332 let mut sched = self.lock_rpc_scheduler(false).await;
5333 let process = sched.registered_process(process).unwrap_or(process);
5334 sched
5335 .thread_tree
5336 .user_namespaces
5337 .note_owner_may_differ(process);
5338 R::UserNamespaceOwnerMayDiffer(())
5339 }
5340 GlobalRequest::ProcessImage(process) => {
5341 let sched = self.lock_rpc_scheduler(false).await;
5342 let process = sched
5344 .registered_process(DetTid::from_raw(process.as_raw()))
5345 .unwrap_or(process);
5346 R::ProcessImage(
5347 self.exec_images
5348 .lock()
5349 .unwrap()
5350 .get(&process)
5351 .cloned()
5352 .map(Box::new),
5353 )
5354 }
5355 GlobalRequest::RecordProcessAuxv(bytes, cmdline) => {
5356 let sched = self.lock_rpc_scheduler(false).await;
5357 let process = sched.registered_process(dtid).unwrap_or(dtid);
5358 let mut table = self.process_auxv.lock().unwrap();
5359 table.record(process, bytes);
5360 table.record_cmdline(process, cmdline);
5361 R::RecordProcessAuxv(())
5362 }
5363 GlobalRequest::ProcessAuxv(process) => {
5364 let sched = self.lock_rpc_scheduler(false).await;
5365 let process = sched
5367 .registered_process(DetTid::from_raw(process.as_raw()))
5368 .unwrap_or(process);
5369 R::ProcessAuxv(self.process_auxv.lock().unwrap().current(process))
5370 }
5371 GlobalRequest::ProcessCmdline(process) => {
5372 let sched = self.lock_rpc_scheduler(false).await;
5373 let process = sched
5375 .registered_process(DetTid::from_raw(process.as_raw()))
5376 .unwrap_or(process);
5377 let area = self.process_auxv.lock().unwrap().current_cmdline(process);
5378 R::ProcessCmdline(process, area)
5379 }
5380 GlobalRequest::MarkChangedRoot => {
5381 let _sched = self.lock_rpc_scheduler(false).await;
5384 self.process_auxv.lock().unwrap().mark_changed_root(dtid);
5385 R::MarkChangedRoot(())
5386 }
5387 GlobalRequest::RootChanged(task) => {
5388 let _sched = self.lock_rpc_scheduler(false).await;
5391 R::RootChanged(self.process_auxv.lock().unwrap().root_changed(task))
5392 }
5393 GlobalRequest::DescriptionLocks(open_file, locks, slots) => {
5394 let sched = self.lock_rpc_scheduler(false).await;
5395 let process = sched.registered_process(dtid).unwrap_or(dtid);
5396 let mut ledger = self.lock_ledger.lock().unwrap();
5397 match locks {
5398 Ok(locks) => ledger.note_locks(process, open_file, locks, slots),
5399 Err(reason) => ledger.note_unknown(process, open_file, reason, slots),
5400 }
5401 R::DescriptionLocks(())
5402 }
5403 GlobalRequest::DescriptionLocksTaken(open_file, locks, taken, slots, identity) => {
5404 let sched = self.lock_rpc_scheduler(false).await;
5405 let process = sched.registered_process(dtid).unwrap_or(dtid);
5406 self.lock_ledger
5407 .lock()
5408 .unwrap()
5409 .note_locks_taken(process, open_file, locks, taken, slots, identity);
5410 R::DescriptionLocksTaken(())
5411 }
5412 GlobalRequest::SecondIdentityLocks(open_file, locks, slots) => {
5413 let sched = self.lock_rpc_scheduler(false).await;
5414 let process = sched.registered_process(dtid).unwrap_or(dtid);
5415 self.lock_ledger
5416 .lock()
5417 .unwrap()
5418 .note_second_identity_locks(process, open_file, locks, slots);
5419 R::SecondIdentityLocks(())
5420 }
5421 GlobalRequest::LockOwners(open_file, locks) => {
5422 let sched = self.lock_rpc_scheduler(false).await;
5425 let listed = sched.procfs_listing(None).processes;
5426 let ledger = self.lock_ledger.lock().unwrap();
5427 R::LockOwners(match ledger.record_lock_held_as_ofd(&locks) {
5428 Some(reason) => Err(reason),
5429 None => Ok(ledger.owners(open_file, &locks, &|process| {
5430 listed.binary_search(&process.as_raw()).is_ok()
5431 })),
5432 })
5433 }
5434 GlobalRequest::LedgerLockRows => {
5435 let sched = self.lock_rpc_scheduler(false).await;
5436 let listed = sched.procfs_listing(None).processes;
5437 let mut ledger = self.lock_ledger.lock().unwrap();
5444 ledger.prune(|process| sched.process_is_alive(process));
5445 R::LedgerLockRows(match ledger.unknown() {
5446 Some(reason) => Err(reason.to_string()),
5447 None => {
5448 let rows = ledger.rendered_rows(&|process| {
5449 listed.binary_search(&process.as_raw()).is_ok()
5450 });
5451 match ledger.record_lock_held_as_ofd(rows.iter().map(|(lock, _)| lock)) {
5452 Some(reason) => Err(reason),
5453 None => Ok(rows),
5454 }
5455 }
5456 })
5457 }
5458 GlobalRequest::TableReleased(open_file, remaining) => {
5459 let sched = self.lock_rpc_scheduler(false).await;
5460 let process = sched.registered_process(dtid).unwrap_or(dtid);
5461 self.lock_ledger
5462 .lock()
5463 .unwrap()
5464 .release(process, open_file, remaining);
5465 R::TableReleased(())
5466 }
5467 GlobalRequest::TableExec(surviving) => {
5468 let sched = self.lock_rpc_scheduler(false).await;
5469 let process = sched.registered_process(dtid).unwrap_or(dtid);
5470 self.lock_ledger
5471 .lock()
5472 .unwrap()
5473 .exec_kept(process, &surviving);
5474 R::TableExec(())
5475 }
5476 GlobalRequest::AliasedInode(inode) => {
5477 let _sched = self.lock_rpc_scheduler(false).await;
5478 self.lock_ledger.lock().unwrap().alias_inode(inode);
5479 R::AliasedInode(())
5480 }
5481 GlobalRequest::LockLedgerRefused(reason) => {
5482 let _sched = self.lock_rpc_scheduler(false).await;
5483 self.lock_ledger.lock().unwrap().refuse(reason);
5484 R::LockLedgerRefused(())
5485 }
5486 GlobalRequest::RecordLocksHeldAsOfd(objects) => {
5487 let _sched = self.lock_rpc_scheduler(false).await;
5488 self.lock_ledger
5489 .lock()
5490 .unwrap()
5491 .note_record_locks_held_as_ofd(objects);
5492 R::RecordLocksHeldAsOfd(())
5493 }
5494 GlobalRequest::GuestLockRows(rows) => {
5495 let sched = self.lock_rpc_scheduler(false).await;
5500 let owners = rows
5501 .iter()
5502 .filter_map(|row| i32::try_from(row.owner).ok().filter(|owner| *owner > 0))
5503 .collect::<BTreeSet<_>>()
5504 .into_iter()
5505 .collect::<Vec<_>>();
5506 let listed = sched.procfs_listing(None).processes;
5507 let guest_owners = owners
5508 .iter()
5509 .zip(sched.procfs_ids(false, &owners))
5510 .filter(|(_, guest)| {
5511 guest.is_some_and(|guest| listed.binary_search(&guest).is_ok())
5512 })
5513 .map(|(owner, _)| i64::from(*owner))
5514 .collect::<BTreeSet<_>>();
5515 let mut ledger = self.lock_ledger.lock().unwrap();
5518 ledger.prune(|process| sched.process_is_alive(process));
5519 if let Some(reason) = ledger.unknown() {
5526 R::GuestLockRows(Err(reason.to_string()))
5527 } else {
5528 let held = ledger.held();
5529 R::GuestLockRows(Ok(crate::procfs::locks::guest_lock_rows(
5530 &rows,
5531 |owner| guest_owners.contains(&owner),
5532 &held,
5533 )))
5534 }
5535 }
5536 GlobalRequest::AuxvFromKernel(why, cmdline) => {
5537 let sched = self.lock_rpc_scheduler(false).await;
5538 let process = sched.registered_process(dtid).unwrap_or(dtid);
5539 let mut table = self.process_auxv.lock().unwrap();
5540 table.forget(process);
5541 table.record_cmdline(process, cmdline);
5542 R::AuxvFromKernel(table.first_kernel_file(why))
5543 }
5544 GlobalRequest::GroupOrSession(pid, session) => R::GroupOrSession(
5545 self.lock_rpc_scheduler(false)
5546 .await
5547 .group_or_session_of(pid, session),
5548 ),
5549 GlobalRequest::SetsidDecision(process) => R::SetsidDecision(
5550 self.lock_rpc_scheduler(false)
5551 .await
5552 .setsid_decision(process)
5553 .map_err(|errno| errno.into_raw()),
5554 ),
5555 GlobalRequest::SetpgidDecision(caller, pid, pgid) => R::SetpgidDecision(
5556 self.lock_rpc_scheduler(false)
5557 .await
5558 .setpgid_decision(caller, pid, pgid)
5559 .map_err(|errno| errno.into_raw()),
5560 ),
5561 GlobalRequest::ProcfsTask(named, thread) => {
5562 let scheduler = self.lock_rpc_scheduler(false).await;
5563 let existence = scheduler.procfs_task(named, thread);
5564 let mut answer = self
5565 .inodes
5566 .lock()
5567 .unwrap()
5568 .procfs_task_answer(named, thread, existence);
5569 if existence.0 && !existence.1 {
5570 answer.mm = scheduler.thread_mm(thread.unwrap_or(named));
5571 answer.runtime_private_memory = answer
5572 .mm
5573 .and_then(|mm| scheduler.runtime_private_memory(mm));
5574 }
5575 R::ProcfsTask(answer)
5576 }
5577 GlobalRequest::TaskStatFacts(named, thread) => R::TaskStatFacts(
5578 self.lock_rpc_scheduler(false)
5579 .await
5580 .task_stat_facts(named, thread),
5581 ),
5582 GlobalRequest::SetTaskComm(tid, comm) => {
5583 self.lock_rpc_scheduler(false)
5584 .await
5585 .set_task_comm(tid, comm);
5586 R::SetTaskComm(())
5587 }
5588 GlobalRequest::RecordExternalParent(process, parent) => {
5589 self.lock_rpc_scheduler(false)
5590 .await
5591 .record_external_parent(process, parent);
5592 R::RecordExternalParent(())
5593 }
5594 GlobalRequest::SetRssLimit(process, limit) => {
5595 self.lock_rpc_scheduler(false)
5596 .await
5597 .set_rss_limit(process, limit);
5598 R::SetRssLimit(())
5599 }
5600 GlobalRequest::RegisterPlaceholder(directory, open_file) => {
5601 let _sched = self.lock_rpc_scheduler(false).await;
5602 R::RegisterPlaceholder(
5603 self.inodes
5604 .lock()
5605 .unwrap()
5606 .register_placeholder(directory, open_file),
5607 )
5608 }
5609 GlobalRequest::Placeholder(tag) => {
5610 let _sched = self.lock_rpc_scheduler(false).await;
5611 R::Placeholder(self.inodes.lock().unwrap().placeholder(tag))
5612 }
5613 GlobalRequest::PlaceholderFlock(request) => {
5614 let sched = self.lock_rpc_scheduler(false).await;
5615 let process = sched.registered_process(dtid).unwrap_or(dtid);
5616 let mut ledger = self.lock_ledger.lock().unwrap();
5617 ledger.prune(|process| sched.process_is_alive(process));
5622 R::PlaceholderFlock(ledger.placeholder_flock(process, request))
5623 }
5624 GlobalRequest::PlaceholderWriteHint(key, hint) => {
5625 let _sched = self.lock_rpc_scheduler(false).await;
5626 R::PlaceholderWriteHint(
5627 self.lock_ledger
5628 .lock()
5629 .unwrap()
5630 .placeholder_write_hint(key, hint),
5631 )
5632 }
5633 GlobalRequest::PlaceholderCopies(change) => {
5634 let _sched = self.lock_rpc_scheduler(false).await;
5635 R::PlaceholderCopies(self.lock_ledger.lock().unwrap().placeholder_copies(change))
5636 }
5637 GlobalRequest::CpuAffinity(tid) => {
5638 R::CpuAffinity(self.lock_rpc_scheduler(false).await.cpu_affinity(tid))
5639 }
5640 GlobalRequest::SetCpuAffinity(tid, cpus) => R::CpuAffinity(
5641 self.lock_rpc_scheduler(false)
5642 .await
5643 .set_cpu_affinity(tid, cpus),
5644 ),
5645 GlobalRequest::RenderedFileState(inode, change) => R::RenderedFileState(
5646 self.inodes
5647 .lock()
5648 .unwrap()
5649 .rendered_file_state(inode, change),
5650 ),
5651 GlobalRequest::LocalPid(caller, task) => R::LocalPid(
5652 self.lock_rpc_scheduler(false)
5653 .await
5654 .thread_tree
5655 .pid_namespaces
5656 .localize_for(caller, task),
5657 ),
5658 GlobalRequest::SignalTargetProcess(named) => R::SignalTargetProcess(
5659 self.lock_rpc_scheduler(false)
5660 .await
5661 .signal_target_process(named),
5662 ),
5663 GlobalRequest::PosixTimerThread(process, named) => R::PosixTimerThread(
5664 self.lock_rpc_scheduler(false)
5665 .await
5666 .posix_timer_thread(process, named),
5667 ),
5668 GlobalRequest::SetSigchldDisposition(process, ignored, nocldwait) => {
5669 R::SetSigchldDisposition(
5670 self.lock_rpc_scheduler(false)
5671 .await
5672 .thread_tree
5673 .set_sigchld_disposition(process, ignored, nocldwait),
5674 )
5675 }
5676 GlobalRequest::SetChildSubreaper(process, on) => R::SetChildSubreaper(
5677 self.lock_rpc_scheduler(false)
5678 .await
5679 .thread_tree
5680 .set_child_subreaper(process, on),
5681 ),
5682 GlobalRequest::UnrecoverableShutdown => {
5683 self.force_shutdown_with_error();
5684 R::UnrecoverableShutdown(())
5685 }
5686 GlobalRequest::Network(request) => {
5687 let _sched = self.lock_rpc_scheduler(false).await;
5688 let mut engine = self
5689 .network
5690 .as_ref()
5691 .expect("network request without a network trace mode")
5692 .lock()
5693 .unwrap();
5694 let now = self.global_time.lock().unwrap().as_nanos();
5695 R::Network(engine.apply(now, request))
5696 }
5697 GlobalRequest::RequestPort(open_file_id) => {
5698 let _sched = self.lock_rpc_scheduler(false).await;
5699 let mut mut_used_ports = self.used_ports.lock().unwrap();
5700 self.update_port_range();
5701 let total_available =
5702 self.port_end_range.load(SeqCst) - self.port_start_range.load(SeqCst);
5703 let mut index = 0;
5704 while (*mut_used_ports).contains(&self.next_port.load(SeqCst))
5705 && index < total_available
5706 {
5707 self.next_port.fetch_add(1, SeqCst);
5708 if self.next_port.load(SeqCst) > self.port_end_range.load(SeqCst) {
5709 self.next_port
5710 .store(self.port_start_range.load(SeqCst), SeqCst);
5711 }
5712 index += 1;
5713 }
5714 if index == total_available {
5715 R::PortFull
5716 } else {
5717 (*mut_used_ports).insert(self.next_port.load(SeqCst));
5718 let mut open_file_to_port = self.open_file_to_port.lock().unwrap();
5719 open_file_to_port.insert(open_file_id, self.next_port.load(SeqCst));
5720 R::RequestPort(self.next_port.load(SeqCst))
5721 }
5722 }
5723 GlobalRequest::AddUsedPort(port, open_file_id) => {
5724 let _sched = self.lock_rpc_scheduler(false).await;
5725 let mut used_ports = self.used_ports.lock().unwrap();
5726 used_ports.insert(port);
5727 let mut open_file_to_port = self.open_file_to_port.lock().unwrap();
5728 open_file_to_port.insert(open_file_id, port);
5729 R::AddUsedPort
5730 }
5731 GlobalRequest::ReleasePort(open_file_id) => {
5732 let _sched = self.lock_rpc_scheduler(false).await;
5733 R::ReleasePort(self.release_port(open_file_id))
5734 }
5735 };
5736
5737 #[cfg(feature = "physical-mm-test-support")]
5738 if matches!(&resp, R::MarkPastFirstExecve(_)) {
5739 self.initial_mm_checkpoint(initial_mm_probe::Point::PostExec, dtid, request_mm)
5743 .await;
5744 }
5745
5746 let (sender_became_terminal, sender_sigkill_retired) =
5751 if is_deregister || exec_reconnect.is_some() || is_exec_caller_after_local_mm_swap {
5752 (false, false)
5753 } else {
5754 let sched = self.lock_rpc_scheduler(consuming_cleanup).await;
5755 (
5756 sched.thread_is_logically_killed(dtid)
5757 || !sched.rpc_incarnation_matches(dtid, request_mm),
5758 !answered_for_sigkill_victim && sched.is_unaccounted_sigkill_victim(dtid),
5759 )
5760 };
5761 if sender_sigkill_retired {
5762 return (None, R::Retired);
5763 }
5764 if resp == R::ThreadExited || sender_became_terminal {
5765 return (None, R::ThreadExited);
5766 }
5767
5768 let time_from_sched = self.global_time.lock().unwrap().threads_time(dtid);
5769 let time_update = match time_from_sched.cmp(&time_from_guest) {
5770 Ordering::Equal => None,
5771 Ordering::Less => {
5772 panic!(
5773 "internal error: thread time should never go down, only monotonically up: time in sched {}, thread local time was {}",
5774 time_from_sched, time_from_guest
5775 )
5776 }
5777 Ordering::Greater => Some(time_from_sched),
5778 };
5779 (time_update, resp)
5780 }
5781
5782 async fn recv_resources_with_origin(
5783 &self,
5784 from: Tid,
5785 detpid: DetPid,
5786 mut rs: Resources,
5787 request_mm: Option<MmId>,
5788 rpc: RpcOrigin,
5789 capability: ControlCapability,
5790 ) -> (
5791 SchedulerRpcResult<ResourceReply>,
5792 Option<LogicalTime>,
5793 MaybePrintStack,
5794 ) {
5795 let dettid = DetTid::from_raw(from.into()); let mut boundary_stack = None;
5797 let resp2 = {
5798 let mut sched = self.lock_rpc_scheduler(false).await;
5799 if sched.thread_is_logically_killed(dettid)
5800 || request_mm.is_some_and(|mm| !sched.rpc_incarnation_matches(dettid, mm))
5801 {
5802 return (SchedulerRpcResult::ThreadExited, None, None);
5803 }
5804 if sched.is_unaccounted_sigkill_victim(dettid) {
5808 return (SchedulerRpcResult::ThreadExited, None, None);
5809 }
5810 let Some(nextturn) = sched.next_turns.get(&dettid).cloned() else {
5811 panic!(
5812 "Detcore internal error: no entry for dettid {} in next_turns during resource request.",
5813 dettid
5814 );
5815 };
5816 trace!(
5817 "[detcore, dtid {}] ResourceRequest, filling request into {}",
5818 &dettid, &nextturn.req
5819 );
5820 if let Some(mm) = request_mm
5821 && let Err(error) = sched.install_resource_origin(
5822 dettid,
5823 ResourceOrigin {
5824 rpc,
5825 mm,
5826 control: capability,
5827 },
5828 )
5829 {
5830 sched.fail_parked(dettid, error);
5831 return (SchedulerRpcResult::ThreadExited, None, None);
5832 }
5833 if let Some(boundary) = rs.slice_boundary.take() {
5834 let mm = request_mm.unwrap_or_else(|| {
5835 crate::scheduler::exit_on_scheduler_refusal(
5836 &"slice-boundary protocol refusal: unauthenticated request",
5837 )
5838 });
5839 if rs.tid != dettid {
5840 crate::scheduler::exit_on_scheduler_refusal(
5841 &"slice-boundary protocol refusal: resource owner differs from sender",
5842 );
5843 }
5844 boundary_stack = sched
5845 .accept_slice_boundary(dettid, mm, &boundary)
5846 .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error));
5847 if boundary_stack.is_some() {
5848 self.signal_requested_stacktrace(dettid);
5849 }
5850 }
5851 sched.record_request_guest_mask(dettid, &rs);
5856 sched.record_request_kernel_run(dettid, &rs);
5859 sched.request_put(&nextturn.req, rs.clone(), &self.global_time);
5860 nextturn.resp
5861 };
5862 trace!(
5863 "[detcore, dtid {}] waiting on {} for resources: {:?}",
5864 dettid, &resp2, rs
5865 );
5866 let answer = resp2.get().await; let (result, time) = self
5868 .finish_resource_response(from, detpid, rs, request_mm, answer)
5869 .await;
5870 if matches!(result, SchedulerRpcResult::ThreadExited) {
5871 (result, time, None)
5872 } else {
5873 (result, time, boundary_stack)
5874 }
5875 }
5876
5877 async fn finish_resource_response(
5878 &self,
5879 from: Tid,
5880 detpid: DetPid,
5881 rs: Resources,
5882 request_mm: Option<MmId>,
5883 answer: SchedResponse,
5884 ) -> (SchedulerRpcResult<ResourceReply>, Option<LogicalTime>) {
5885 let dettid = DetTid::from_raw(from.as_raw());
5886 let request_became_stale = {
5887 let sched = self.lock_rpc_scheduler(false).await;
5888 sched.thread_is_logically_killed(dettid)
5889 || request_mm.is_some_and(|mm| !sched.rpc_incarnation_matches(dettid, mm))
5890 };
5891 if request_became_stale {
5892 trace!(
5898 "[detcore, dtid {}] terminating pending request after logical removal",
5899 dettid
5900 );
5901 return (SchedulerRpcResult::ThreadExited, None);
5902 }
5903 if matches!(answer, SchedResponse::InGuestFatalAlarm) {
5904 let mut sched = self.lock_rpc_scheduler(false).await;
5905 if !self.cfg.sequentialize_threads
5906 || !sched.in_guest_fatal_alarm_response_matches(dettid, detpid, request_mm, &rs)
5907 {
5908 sched.fail_parked(dettid, ProtocolFailure::UnexpectedControl);
5909 return (SchedulerRpcResult::ThreadExited, None);
5910 }
5911 return (
5912 SchedulerRpcResult::Continue(ResourceReply::Grant(ResumeStatus::InGuestFatalAlarm)),
5913 None,
5914 );
5915 }
5916 if let SchedResponse::ObserveSignal(control) = answer {
5919 return (
5920 SchedulerRpcResult::Continue(ResourceReply::ObserveSignal(control)),
5921 None,
5922 );
5923 }
5924 if let Some((true, process, mm)) = rs.exit_identity() {
5925 info!(
5926 "Scheduler authorized an exit-group scenario, from dettid {} / detpid {}",
5927 dettid, detpid
5928 );
5929 {
5936 let mut sched = self.lock_rpc_scheduler(false).await;
5937 if sched.thread_is_logically_killed(dettid)
5938 || request_mm.is_some_and(|mm| !sched.rpc_incarnation_matches(dettid, mm))
5939 {
5940 return (SchedulerRpcResult::ThreadExited, None);
5941 }
5942 sched.logically_kill_exit_group_siblings(dettid, process, mm);
5943 }
5944 }
5945
5946 match answer {
5947 SchedResponse::ObserveSignal(_) | SchedResponse::InGuestFatalAlarm => {
5948 self.sched
5949 .lock()
5950 .unwrap()
5951 .fail_parked(dettid, ProtocolFailure::UnexpectedControl);
5952 (SchedulerRpcResult::ThreadExited, None)
5953 }
5954 SchedResponse::Go(Some(schedval)) => {
5956 trace!(
5957 "[dtid {}] resources granted, resuming normally: {:?}",
5958 dettid, rs
5959 );
5960
5961 let endtime_update = match schedval {
5962 SchedValue::TimeOut
5966 | SchedValue::Signaled(_)
5967 | SchedValue::Requeued { .. }
5968 | SchedValue::PiFutex(_)
5969 | SchedValue::PiTaken(_)
5970 | SchedValue::PiWaiterReached { .. } => None,
5971 SchedValue::Value(timeslice) => Some(LogicalTime::from_nanos(timeslice)),
5972 };
5973 (
5974 SchedulerRpcResult::Continue(ResourceReply::Grant(ResumeStatus::Normal)),
5975 endtime_update,
5976 )
5977 }
5978 SchedResponse::GoAlone(schedval) => {
5979 trace!(
5980 "[dtid {}] resources granted, and nothing else can run",
5981 dettid
5982 );
5983 let endtime_update = match schedval {
5984 Some(SchedValue::Value(timeslice)) => Some(LogicalTime::from_nanos(timeslice)),
5985 _ => None,
5986 };
5987 (
5988 SchedulerRpcResult::Continue(ResourceReply::Grant(
5989 ResumeStatus::NothingElseCanRun,
5990 )),
5991 endtime_update,
5992 )
5993 }
5994 SchedResponse::Go(None) => {
5995 trace!(
5996 "[dtid {}] resources granted but no timeslice specified",
5997 dettid,
5998 );
5999 (
6000 SchedulerRpcResult::Continue(ResourceReply::Grant(ResumeStatus::Normal)),
6001 None,
6002 )
6003 }
6004 SchedResponse::Signaled(signal) => {
6005 trace!(
6006 "[dtid {}] resources granted but interrupted by signal",
6007 dettid,
6008 );
6009 (
6010 SchedulerRpcResult::Continue(ResourceReply::Grant(ResumeStatus::Signaled(
6011 signal,
6012 ))),
6013 None,
6014 )
6015 }
6016 }
6017 }
6018
6019 async fn recv_release_resources(&self, from: Tid, rs: Resources) {
6020 trace!("[detcore] Resources released to pid {}: {:?}", from, rs);
6022 }
6023
6024 async fn recv_release_all_resources(&self, from: Tid) {
6025 trace!("[detcore] All resources held by pid {} released", from);
6027 }
6028
6029 fn clock_belongs_to_earlier_thread(&self, sched: &Scheduler, dtid: DetTid) -> bool {
6034 id_awaits_new_registration(sched, dtid)
6035 && self.global_time.lock().unwrap().contains_thread(dtid)
6036 }
6037
6038 async fn recv_create_child_thread(
6042 &self,
6043 rpc_sender: Tid,
6044 request_mm: MmId,
6045 registration: ChildRegistration,
6046 ) -> SchedulerRpcResult<()> {
6047 let ChildRegistration {
6048 parent_dettid,
6049 parent_detpid,
6050 child_dettid,
6051 child_tid_addr: ctid,
6052 flags,
6053 exit_signal,
6054 physical_ids,
6055 maybe_priority,
6056 parent_is_kernel_blocked,
6057 } = registration;
6058 #[cfg(feature = "physical-mm-test-support")]
6059 if flags.is_none() && rpc_sender.as_raw() == child_dettid.as_raw() {
6060 self.initial_mm_checkpoint(
6061 initial_mm_probe::Point::BeforeRegistration,
6062 child_dettid,
6063 request_mm,
6064 )
6065 .await;
6066 }
6067 let initial_priority = if let Some(pr) = &self.preemptions_to_replay {
6068 assert!(maybe_priority.is_none());
6069 let prio = pr
6070 .thread_initial_priority(&child_dettid)
6071 .unwrap_or_else(|| {
6072 warn!(
6073 "Child thread {} not found in preemption history to replay",
6074 child_dettid
6075 );
6076 DEFAULT_PRIORITY
6077 });
6078 if !is_ordinary_priority(prio) {
6079 panic!(
6080 "Read a bad initial_prority from file: {}\nFull file: {}",
6081 prio, pr,
6082 );
6083 }
6084 prio
6085 } else {
6086 let prio = maybe_priority.expect(
6087 "create_child_thread must take an initial priority unless replaying preemptions",
6088 );
6089 if !is_ordinary_priority(prio) {
6090 panic!(
6091 "recv_create_child_thread received a bad prority argument : {}",
6092 prio,
6093 );
6094 }
6095 prio
6096 };
6097
6098 {
6099 let mut sched = self.lock_rpc_scheduler(false).await;
6100 let sender = DetTid::from_raw(rpc_sender.into());
6101 if sched.thread_is_logically_killed(sender)
6102 || !sched.rpc_incarnation_matches(sender, request_mm)
6103 || sched.thread_is_logically_killed(child_dettid)
6104 {
6105 return SchedulerRpcResult::ThreadExited;
6106 }
6107 let child_detpid =
6111 if flags.is_some_and(|flags| flags.contains(CloneFlags::CLONE_THREAD)) {
6112 parent_detpid
6113 } else {
6114 DetPid::from_raw(child_dettid.as_raw())
6115 };
6116 if self.cfg.backend.process_exits_complete_asynchronously
6117 && sched.process_physically_gone(child_detpid)
6118 {
6119 drop(sched);
6120 self.report_backend_failure(reverie::BackendFailure {
6121 pid: reverie::Pid::from_raw(child_detpid.as_raw()),
6122 tid: Tid::from_raw(child_dettid.as_raw()),
6123 phase: "a process was already physically gone when it was registered",
6124 });
6125 return SchedulerRpcResult::ThreadExited;
6126 }
6127
6128 let registered_parent_creation = sender == parent_dettid
6135 && parent_dettid != child_dettid
6136 && sched.thread_was_registered(sender);
6137 let initial_current_root = flags.is_none()
6138 && request_mm != MmId::initial(child_dettid)
6139 && !registered_parent_creation;
6140 if initial_current_root
6141 && (sender != child_dettid
6142 || parent_dettid != child_dettid
6143 || parent_detpid != child_dettid
6144 || ctid != 0
6145 || parent_is_kernel_blocked
6146 || request_mm != MmId::initial(child_dettid).for_exec(child_dettid)
6147 || self.root_dettid.get().is_some()
6148 || self.initial_descriptor_image_complete.load(SeqCst)
6149 || self.root_image_marked.load(SeqCst)
6150 || self
6151 .pending_exec_states
6152 .lock()
6153 .unwrap()
6154 .contains_key(&child_dettid))
6155 {
6156 return SchedulerRpcResult::ThreadExited;
6157 }
6158 if initial_current_root
6162 && !sched.record_initial_current_image_birth(child_dettid, request_mm)
6163 {
6164 return SchedulerRpcResult::ThreadExited;
6165 }
6166
6167 if parent_is_kernel_blocked && self.cfg.sequentialize_threads {
6168 sched.complete_vfork_registration(parent_dettid, child_dettid);
6169 }
6170
6171 let child_mm = if initial_current_root {
6172 request_mm
6173 } else {
6174 MmId::for_clone(
6175 request_mm,
6176 child_dettid,
6177 flags.is_some_and(|flags| flags.contains(CloneFlags::CLONE_VM)),
6178 )
6179 };
6180 if let Some(owners) = &mut sched.boundary_owners {
6181 owners
6182 .fresh(child_dettid, child_mm)
6183 .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error));
6184 }
6185 if !initial_current_root {
6186 sched.register_reused_transferred_exec_tid(child_dettid, child_mm);
6187 sched.record_thread_mm(child_dettid, child_mm);
6188 }
6189 if sender != child_dettid && self.clock_belongs_to_earlier_thread(&sched, child_dettid)
6194 {
6195 self.global_time.lock().unwrap().retire_thread(child_dettid);
6196 }
6197 self.inodes
6200 .lock()
6201 .unwrap()
6202 .count_incarnation(child_dettid.as_raw());
6203
6204 sched.forget_committed_exit(child_dettid);
6207 sched.published_masks.remove(&child_dettid);
6208 let _entry = sched
6210 .next_turns
6211 .entry(child_dettid)
6212 .or_insert_with(|| ThreadNextTurn {
6213 dettid: child_dettid,
6214 child_tid_addr: ctid,
6215 req: Ivar::new(),
6216 resp: Ivar::new(),
6217 protocol: Default::default(),
6218 });
6219 if flags.is_some() {
6224 sched.seed_child_guest_sigmask(parent_dettid, child_dettid);
6225 }
6226
6227 {
6228 let is_group_leader = if let Some(f) = flags {
6229 !f.contains(CloneFlags::CLONE_THREAD)
6230 } else {
6231 true };
6233 sched.thread_tree.add_child_with_wait_metadata(
6234 parent_dettid,
6235 child_dettid,
6236 is_group_leader,
6237 flags.is_some_and(|flags| flags.contains(CloneFlags::CLONE_PARENT)),
6238 exit_signal,
6239 );
6240 sched.work_span_note_spawn(parent_dettid, child_dettid);
6241 if is_group_leader {
6242 sched.forget_previous_process_exit(child_dettid);
6243 sched.child_ends_timer_observability(child_dettid);
6247 }
6248 let new_pid_namespace = is_group_leader
6249 && flags.is_some_and(|flags| flags.contains(CloneFlags::CLONE_NEWPID));
6250 if new_pid_namespace {
6251 sched.thread_tree.enter_new_pid_namespace(child_dettid);
6252 sched.thread_tree.mark_pid_namespace_init(child_dettid);
6253 }
6254 if is_group_leader
6258 && flags.is_some_and(|flags| flags.contains(CloneFlags::CLONE_NEWUSER))
6259 {
6260 sched.thread_tree.enter_new_user_namespace(child_dettid);
6261 }
6262 let pid_namespaces = &mut sched.thread_tree.pid_namespaces;
6266 let mut chain = pid_namespaces.chain_of(parent_dettid);
6267 if new_pid_namespace {
6268 chain.push(child_dettid);
6269 }
6270 if let Some(reason) = pid_namespaces.untranslatable(parent_dettid) {
6271 let reason = format!("its creator {parent_dettid}: {reason}");
6272 pid_namespaces.mark_untranslatable(child_dettid, reason);
6273 } else if chain.is_empty() {
6274 pid_namespaces.forget(child_dettid);
6275 } else {
6276 let recorded =
6277 crate::scheduler::pid_namespaces::read_nspid(child_detpid, child_dettid)
6278 .and_then(|nspid| {
6279 pid_namespaces
6280 .record(child_dettid, &chain, &nspid)
6281 .map_err(|error| error.to_string())
6282 });
6283 if let Err(reason) = recorded {
6284 error!(
6285 "[detcore, dtid {}] cannot record its pids in its PID namespaces: {}",
6286 child_dettid, reason
6287 );
6288 pid_namespaces.mark_untranslatable(child_dettid, reason);
6289 } else if is_group_leader {
6290 sched.note_member_of_ended_namespace(child_dettid);
6291 }
6292 }
6293 self.process_auxv.lock().unwrap().clone_root(
6296 parent_dettid,
6297 child_dettid,
6298 flags.is_some_and(|flags| flags.contains(CloneFlags::CLONE_FS)),
6299 );
6300 if is_group_leader && flags.is_some() {
6301 let mut images = self.exec_images.lock().unwrap();
6303 match images.get(&parent_detpid).cloned() {
6304 Some(image) => images.insert(child_dettid, image),
6305 None => images.remove(&child_dettid),
6306 };
6307 drop(images);
6308 self.process_auxv
6309 .lock()
6310 .unwrap()
6311 .inherit(parent_detpid, child_dettid);
6312 sched.inherit_real_time_records(parent_detpid, child_dettid);
6313 self.lock_ledger.lock().unwrap().fork(
6319 parent_detpid,
6320 child_dettid,
6321 flags.is_some_and(|flags| flags.contains(CloneFlags::CLONE_FILES)),
6322 );
6323 }
6324 if !is_group_leader
6329 && flags.is_some_and(|flags| !flags.contains(CloneFlags::CLONE_FILES))
6330 {
6331 self.lock_ledger
6332 .lock()
6333 .unwrap()
6334 .mark_private_tables(parent_detpid);
6335 }
6336 }
6337
6338 if let Some((physical_pid, physical_tid)) = physical_ids {
6339 let child_is_thread =
6340 flags.is_some_and(|flags| flags.contains(CloneFlags::CLONE_THREAD));
6341 let child_detpid = if child_is_thread {
6342 parent_detpid
6343 } else {
6344 child_dettid
6345 };
6346 if let Err(open_error) = sched.register_physical_thread(
6347 child_dettid,
6348 child_mm,
6349 physical_pid,
6350 physical_tid,
6351 ) {
6352 error!(
6353 "[detcore, dtid {}] cannot bind host process {} thread {} during child registration: {}",
6354 child_dettid, physical_pid, physical_tid, open_error,
6355 );
6356 sched.logically_kill_thread(&child_dettid, &child_detpid, child_mm);
6357 return SchedulerRpcResult::ThreadExited;
6358 }
6359 }
6360
6361 if flags.is_none()
6365 && parent_dettid == child_dettid
6366 && parent_detpid == child_dettid
6367 && let Some(input) = self.inherited_input.lock().unwrap().as_mut()
6368 && input.root.is_none()
6369 {
6370 input.root = Some((child_dettid, child_mm));
6371 }
6372 if flags.is_none() && parent_dettid == child_dettid && parent_detpid == child_dettid {
6375 let _ = self.root_dettid.set(child_dettid);
6376 }
6377
6378 sched.hb_note_spawn(child_dettid);
6381
6382 if self.cfg.replay_schedule_from.is_none() {
6383 let old_prio = sched.priorities.insert(child_dettid, initial_priority);
6385 assert!(old_prio.is_none());
6386 } else {
6387 if sched.replay_registration_priority(child_dettid, initial_priority) {
6394 if sched.replay_left_its_record() {
6395 warn!(
6396 "[detcore, dtid {}] registered before the replayed schedule switched to it, created by process {}; it takes its starting priority {}",
6397 child_dettid, parent_detpid, initial_priority
6398 );
6399 } else {
6400 debug!(
6401 "[detcore, dtid {}] registered before the replayed schedule switched to it, created by process {}; it takes its starting priority {}",
6402 child_dettid, parent_detpid, initial_priority
6403 );
6404 }
6405 }
6406 }
6407
6408 sched.register_preemption_thread(
6409 child_dettid,
6410 initial_priority,
6411 parent_is_kernel_blocked,
6412 );
6413
6414 let intent = if self.cfg.sequentialize_threads && !parent_is_kernel_blocked {
6432 AdmitIntent::PostFork(self.cfg.runs_post_fork)
6433 } else {
6434 AdmitIntent::Fixed(AdmitSide::Back)
6435 };
6436 sched.admit_to_run_queue(child_dettid, intent);
6437 debug!(
6438 "[detcore] CreateChildThread with dtid {}: admit child via {:?}.",
6439 child_dettid, intent,
6440 );
6441 sched.started_up.try_put(());
6442 }
6443 #[cfg(feature = "physical-mm-test-support")]
6444 if flags.is_none() && rpc_sender.as_raw() == child_dettid.as_raw() {
6445 self.initial_mm_checkpoint(
6448 initial_mm_probe::Point::RegisteredBeforeGrant,
6449 child_dettid,
6450 request_mm,
6451 )
6452 .await;
6453 }
6454 if self.cfg.sequentialize_threads && !parent_is_kernel_blocked {
6459 let mut rs = Resources::new(parent_detpid);
6460 rs.insert(
6461 ResourceID::ParentContinue {
6462 parent: parent_dettid,
6463 child: child_dettid,
6464 },
6465 Permission::W,
6466 );
6467 if matches!(
6468 self.recv_grant_resources(
6469 rpc_sender,
6470 parent_detpid,
6471 rs,
6472 Some(request_mm),
6473 RpcOrigin::ParentContinue
6474 )
6475 .await
6476 .0,
6477 SchedulerRpcResult::ThreadExited
6478 ) {
6479 return SchedulerRpcResult::ThreadExited;
6480 }
6481 }
6482 SchedulerRpcResult::Continue(())
6483 }
6484
6485 async fn recv_start_new_thread(
6489 &self,
6490 from: Tid,
6491 dettid: DetTid,
6492 detpid: DetPid,
6493 request_mm: MmId,
6494 physical_ids: Option<(i32, i32)>,
6495 signal_identity: Option<reverie::SignalTaskIdentity>,
6496 ) -> SchedulerRpcResult<Option<ThreadHistory>> {
6497 #[cfg(feature = "physical-mm-test-support")]
6498 self.initial_mm_checkpoint(initial_mm_probe::Point::ThreadStartup, dettid, request_mm)
6499 .await;
6500 let mut tries: u64 = 0;
6501 let response_ivar = loop {
6503 yield_once().await;
6504 let mut sched = self.lock_rpc_scheduler(false).await;
6505 if sched.thread_is_logically_killed(dettid)
6506 || !sched.rpc_incarnation_matches(dettid, request_mm)
6507 {
6508 return SchedulerRpcResult::ThreadExited;
6509 }
6510 if self.cfg.backend.process_exits_complete_asynchronously
6511 && sched.process_physically_gone(detpid)
6512 {
6513 drop(sched);
6514 self.report_backend_failure(reverie::BackendFailure {
6515 pid: reverie::Pid::from_raw(detpid.as_raw()),
6516 tid: Tid::from_raw(dettid.as_raw()),
6517 phase: "a process was already physically gone when it started",
6518 });
6519 return SchedulerRpcResult::ThreadExited;
6520 }
6521 if self.cfg.backend.requires_thread_directed_process_signals && physical_ids.is_none() {
6522 error!(
6523 "[detcore, dtid {}] backend requires a host thread ID at StartNewThread",
6524 dettid,
6525 );
6526 sched.logically_kill_thread(&dettid, &detpid, request_mm);
6527 return SchedulerRpcResult::ThreadExited;
6528 }
6529 let rsrcs = {
6531 let mut s = HashMap::new();
6532 s.insert(ResourceID::MemAddrSpace(detpid), Permission::RW); Resources {
6534 tid: dettid,
6535 resources: s,
6536 poll_attempt: 0,
6537 fyi: String::new(),
6538 signal_interrupt_errno: None,
6539 backend_runtime_bootstrap: false,
6540 blocked_signal_mask: None,
6541 blocked_signal_mask_unknown: false,
6542 replay_served_from_log: false,
6543 reads_signalfd: false,
6544 read_fd: None,
6545 polls_guest_state_only: false,
6546 polls_a_named_fifo: false,
6547 relies_on_guest_held_marks: false,
6548 passes_descriptors_out: false,
6549 waits_until_nothing_else_can_run: false,
6550 previous_attempt_made_progress: false,
6551 upgraded_from_attempt: 0,
6552 polls_channel: None,
6553 poll_deadline: None,
6554 in_guest_sleep_sigalrm: None,
6555 signal_state_task: None,
6556 decides_signal_interruption_itself: false,
6557 continues_sigreturn: false,
6558 private_sigchld_decides: false,
6559 slice_boundary: None,
6560 guest_signal_mask: None,
6561 callback_checkpoint: None,
6562 priority_row: None,
6563 slice_expired: false,
6564 zero_length_sleep: false,
6565 }
6566 };
6567 let nextturn = match sched.next_turns.entry(dettid) {
6568 Entry::Vacant(_entry) => {
6569 if tries == 0 {
6575 trace!(
6576 "[detcore, dtid {}] thread showed up early, no queue entry yet. Waiting...",
6577 dettid
6578 );
6579 }
6580 tries += 1;
6581 continue;
6582 }
6583 Entry::Occupied(entry) => {
6584 trace!(
6585 "[detcore, dtid {}] handling StartNewThread rpc. Found next_turns entry (after {} tries)",
6586 from, tries
6587 );
6588 entry.get().clone()
6589 }
6590 };
6591 if let Some((physical_pid, physical_tid)) = physical_ids
6592 && let Err(open_error) =
6593 sched.register_physical_thread(dettid, request_mm, physical_pid, physical_tid)
6594 {
6595 error!(
6596 "[detcore, dtid {}] cannot bind host process {} thread {} for exact signal delivery: {}",
6597 dettid, physical_pid, physical_tid, open_error,
6598 );
6599 sched.logically_kill_thread(&dettid, &detpid, request_mm);
6600 return SchedulerRpcResult::ThreadExited;
6601 }
6602 if sched.signal_control_installed() {
6603 let binding = signal_identity
6604 .ok_or(TimerFailure::Identity)
6605 .and_then(|identity| {
6606 if from.as_raw() != dettid.as_raw()
6607 || sched.registered_process(dettid) != Some(detpid)
6608 {
6609 return Err(TimerFailure::Identity);
6610 }
6611 sched.real_timers.bind(detpid, dettid, request_mm, identity)
6612 });
6613 if let Err(error) = binding {
6614 sched.fail_parked(dettid, ProtocolFailure::Timer(error));
6615 return SchedulerRpcResult::ThreadExited;
6616 }
6617 }
6618 if let Err(error) = sched.install_resource_origin(
6619 dettid,
6620 ResourceOrigin {
6621 rpc: RpcOrigin::ThreadStart,
6622 mm: request_mm,
6623 control: ControlCapability::None,
6624 },
6625 ) {
6626 sched.fail_parked(dettid, error);
6627 return SchedulerRpcResult::ThreadExited;
6628 }
6629 sched.request_put(&nextturn.req, rsrcs, &self.global_time);
6630 break nextturn.resp;
6631 };
6632 debug!(
6633 "[detcore, dtid {}] New thread will now wait for response on {}...",
6634 &dettid, &response_ivar
6635 );
6636 let answer = response_ivar.get().await;
6637 if matches!(
6638 answer,
6639 SchedResponse::ObserveSignal(_) | SchedResponse::InGuestFatalAlarm
6640 ) {
6641 self.sched
6642 .lock()
6643 .unwrap()
6644 .fail_parked(dettid, ProtocolFailure::UnexpectedControl);
6645 return SchedulerRpcResult::ThreadExited;
6646 }
6647 let request_became_stale = {
6648 let sched = self.lock_rpc_scheduler(false).await;
6649 sched.thread_is_logically_killed(dettid)
6650 || !sched.rpc_incarnation_matches(dettid, request_mm)
6651 };
6652 if request_became_stale {
6653 return SchedulerRpcResult::ThreadExited;
6654 }
6655 info!(
6656 "[detcore, dtid {}] New thread given go-ahead to proceed via {}",
6657 &dettid, &response_ivar
6658 );
6659 if let Some(pr) = &self.preemptions_to_replay {
6660 let (history, old_prio) = {
6661 let mut sched = self.lock_rpc_scheduler(false).await;
6662 if sched.thread_is_logically_killed(dettid)
6663 || !sched.rpc_incarnation_matches(dettid, request_mm)
6664 {
6665 return SchedulerRpcResult::ThreadExited;
6666 }
6667 let history = pr.extract_thread_record(&dettid).unwrap_or_else(|| {
6668 warn!(
6669 "Replaying preemptions, but no record found for thread {}",
6670 dettid
6671 );
6672 ThreadHistory::new()
6673 });
6674 let old_prio = sched.priorities.insert(dettid, history.initial_priority());
6675 (history, old_prio)
6676 };
6677 debug!(
6678 "[replay-preemption] Enqueing new thread at priority {:?} (changed from {:?})",
6679 history.initial_priority(),
6680 old_prio,
6681 );
6682 SchedulerRpcResult::Continue(Some(history))
6683 } else {
6684 SchedulerRpcResult::Continue(None)
6685 }
6686 }
6687
6688 async fn recv_deregister_thread(&self, _from: Tid, mut deregistration: ThreadDeregistration) {
6691 let mut sched = self.sched.lock().unwrap();
6693 let ThreadDeregistration {
6694 dettid, detpid, mm, ..
6695 } = &deregistration;
6696 let mut pending = self.pending_exec_states.lock().unwrap();
6701 if pending.get(detpid).is_some_and(|state| {
6702 state.caller == *dettid && (*mm == state.mm || *mm == state.mm.for_exec(state.process))
6703 }) {
6704 let prepared = pending.remove(detpid).unwrap();
6705 if !prepared.fd_blocking.robust_wakes.is_empty() {
6721 sched.hold_turns_until_the_group_ends(*detpid);
6722 wake_exec_robust_waiters(
6723 &mut sched,
6724 &prepared.fd_blocking.robust_wakes,
6725 "terminal exec",
6726 );
6727 }
6728 sched.finish_exec_teardown(prepared.caller, *detpid, prepared.mm, false);
6729 deregistration.mm = prepared.mm;
6734 }
6735 drop(pending);
6736
6737 assert!(self.cfg.sequentialize_threads);
6739 self.account_deregistered_thread(&mut sched, deregistration);
6740 }
6741
6742 fn account_deregistered_thread(
6745 &self,
6746 sched: &mut Scheduler,
6747 deregistration: ThreadDeregistration,
6748 ) {
6749 let ThreadDeregistration {
6750 dettid,
6751 detpid,
6752 mm,
6753 thread_start_entered: _,
6754 vfork_origin: _,
6755 timeslice_stats,
6756 syscall_count,
6757 syscall_histogram,
6758 chaos_epochs,
6759 killed_by_group_signal,
6760 passed_descriptors_out,
6761 } = deregistration;
6762 if !sched.rpc_incarnation_matches(dettid, mm) {
6763 debug!(
6764 "[detcore, dtid {}] ignoring deregistration from retired exec incarnation {:?}",
6765 dettid, mm,
6766 );
6767 return;
6768 }
6769 self.post_exec_fd_blocking.lock().unwrap().remove(&dettid);
6770 if !sched.note_deregistration_accounted(dettid)
6774 || sched.sigkill_receipt_already_counted(dettid)
6775 {
6776 trace!(
6777 "[detcore, dtid {}] acknowledging already-accounted deregistration",
6778 dettid
6779 );
6780 return;
6781 }
6782 if passed_descriptors_out {
6786 sched.revoke_guest_held_marks(dettid);
6787 }
6788 if let Some(writer) = &mut sched.preemption_writer {
6789 for transition in chaos_epochs {
6790 writer.insert_chaos_epoch(dettid, transition);
6791 }
6792 }
6793 sched.record_timeslice_stats(dettid, timeslice_stats);
6794 sched.record_syscall_count(dettid, syscall_count, syscall_histogram);
6795 if sched.route_sigkill_receipt(dettid)
6799 != crate::scheduler::sigkill_retirement::SigkillReceipt::Ordinary
6800 {
6801 return;
6802 }
6803 let process = sched.registered_process(dettid).unwrap_or(detpid);
6806 if sched.retire_deregistered_thread(dettid, detpid, mm) && killed_by_group_signal {
6811 sched.note_signal_killed_group(dettid, process);
6812 }
6813 trace!(
6814 "[detcore, dtid {}] thread deregistered, removed from sched structures.",
6815 dettid
6816 );
6817 }
6818
6819 async fn recv_futex_action(
6820 &self,
6821 caller: RpcIncarnation,
6822 action: FutexAction,
6823 futexid: FutexID,
6824 init_read: i32,
6825 mask: u32,
6826 passed_descriptors_out: bool,
6827 ) -> Option<SchedValue> {
6828 let RpcIncarnation { dettid, mm } = caller;
6829 trace!("[detcore, dtid {}] Futex action: {:?}", &dettid, action);
6830 let response_iv = {
6831 let mut sched = self.lock_rpc_scheduler(false).await;
6832 if sched.thread_is_logically_killed(dettid)
6833 || !sched.rpc_incarnation_matches(dettid, mm)
6834 {
6835 return Some(SchedValue::Value(nix::errno::Errno::EINTR as u64));
6836 }
6837 let Some(resp_iv) = sched
6838 .next_turns
6839 .get(&dettid)
6840 .map(|nextturn| nextturn.resp.clone())
6841 else {
6842 trace!(
6845 "[detcore, dtid {}] ignoring futex action after logical thread removal",
6846 dettid
6847 );
6848 return Some(SchedValue::Value(nix::errno::Errno::EINTR as u64));
6849 };
6850 if passed_descriptors_out {
6854 sched.revoke_guest_held_marks(dettid);
6855 }
6856 match action {
6857 FutexAction::WaitRequest(maybe_timeout, signal_watch) => {
6858 if sched.child_tid_was_cleared(futexid, init_read) {
6859 trace!(
6860 "[detcore, dtid {}] late wait on cleared child-TID futex {:?}",
6861 dettid, futexid
6862 );
6863 return Some(SchedValue::Value(0));
6864 }
6865 if let Err(error) = sched.install_resource_origin(
6866 dettid,
6867 ResourceOrigin {
6868 rpc: RpcOrigin::FutexAction,
6869 mm,
6870 control: ControlCapability::None,
6871 },
6872 ) {
6873 sched.fail_parked(dettid, error);
6874 return Some(SchedValue::Value(nix::errno::Errno::EINTR as u64));
6875 }
6876 sched.sleep_futex_waiter(&dettid, futexid, maybe_timeout, mask, signal_watch);
6877 }
6879 FutexAction::WaitFinished => {
6880 sched.leave_pi_futex_queue(dettid);
6881 return None;
6882 }
6883 FutexAction::WakeRequest(num_threads) => {
6884 let (num, met_pi) =
6885 sched.wake_futex_waiters_until_pi(dettid, futexid, num_threads, mask);
6886 if self.cfg.yields_after_futex_wake(num) {
6887 sched.requeue_futex_waker(dettid);
6888 }
6889 return Some(if met_pi {
6890 SchedValue::PiWaiterReached { woken: num }
6891 } else {
6892 SchedValue::Value(num)
6893 });
6894 }
6895 FutexAction::WakeFinished(_num_threads) => {
6896 return None;
6897 }
6898 FutexAction::RequeueRequest(to, nr_wake, nr_requeue) => {
6899 let (woken, moved, met_pi) = sched.requeue_futex_waiters(
6900 dettid,
6901 futexid,
6902 to,
6903 nr_wake as usize,
6904 nr_requeue as usize,
6905 );
6906 if self.cfg.yields_after_futex_wake(woken) {
6907 sched.requeue_futex_waker(dettid);
6908 }
6909 return Some(if met_pi {
6910 SchedValue::PiWaiterReached { woken }
6911 } else {
6912 SchedValue::Requeued { woken, moved }
6913 });
6914 }
6915 FutexAction::PiState(named) => {
6916 return Some(SchedValue::PiFutex(
6917 sched.pi_futex_state(futexid, named, dettid),
6918 ));
6919 }
6920 FutexAction::PiTake => {
6921 return Some(SchedValue::PiTaken(
6922 sched.take_handed_pi_futex(futexid, dettid),
6923 ));
6924 }
6925 FutexAction::PiHandOff => {
6926 let woken = u64::from(sched.hand_off_pi_futex(futexid).is_some());
6927 if self.cfg.yields_after_futex_wake(woken) {
6928 sched.requeue_futex_waker(dettid);
6929 }
6930 return Some(SchedValue::Value(woken));
6931 }
6932 FutexAction::PiLockWait(maybe_timeout, signal_watch, owner) => {
6933 if let Err(error) = sched.install_resource_origin(
6934 dettid,
6935 ResourceOrigin {
6936 rpc: RpcOrigin::FutexAction,
6937 mm,
6938 control: ControlCapability::None,
6939 },
6940 ) {
6941 sched.fail_parked(dettid, error);
6942 return Some(SchedValue::Value(nix::errno::Errno::EINTR as u64));
6943 }
6944 sched.sleep_pi_futex_waiter(
6945 dettid,
6946 futexid,
6947 maybe_timeout,
6948 signal_watch,
6949 owner,
6950 );
6951 }
6953 }
6954 assert!(sched.run_queue.remove_tid(dettid));
6956 resp_iv
6957 };
6958 match response_iv.get().await {
6960 SchedResponse::Go(answer) | SchedResponse::GoAlone(answer) => {
6962 trace!(
6963 "[detcore, dtid {}] Unblocked from futex_wait! ({})",
6964 &dettid, &response_iv
6965 );
6966 answer
6967 }
6968 SchedResponse::Signaled(signals) => Some(SchedValue::Signaled(
6969 signals.iter().flatten().fold(0, |set, signal| {
6970 set | crate::scheduler::kernel_signal_bit(signal.0)
6971 }),
6972 )),
6973 SchedResponse::ObserveSignal(_) | SchedResponse::InGuestFatalAlarm => {
6974 self.sched
6975 .lock()
6976 .unwrap()
6977 .fail_parked(dettid, ProtocolFailure::UnexpectedControl);
6978 self.wait_for_backend_failure().await;
6979 futures::future::pending().await
6980 }
6981 }
6982 }
6983
6984 fn recv_robust_list_wakes(&self, from: DetTid, wakes: Vec<(DetTid, FutexID)>) -> Vec<u64> {
6985 let mut sched = self.sched.lock().unwrap();
6986 if sched.retain_sigkill_victim_wakes(from, &wakes) {
6987 return Vec::new();
6990 }
6991 sched.wake_futex_waiters_after_exit(&wakes)
6992 }
6993
6994 fn epoch_logical_time(&self) -> LogicalTime {
6997 let nanos = self
6998 .cfg
6999 .epoch
7000 .timestamp_nanos_opt()
7001 .expect("epoch cannot be represented in a timestamp with nanosecond precision")
7002 as u64;
7003 LogicalTime::from_nanos(nanos)
7004 }
7005
7006 async fn recv_determinize_inode(
7007 &self,
7008 from: Tid,
7009 ino: RawInode,
7010 procfs: Option<ProcfsName>,
7011 observed: ObservedMtime,
7012 sighting: InodeSighting,
7013 ) -> (DetInode, LogicalTime) {
7014 let _sched = self.lock_rpc_scheduler(false).await;
7015 let epoch = self.epoch_logical_time();
7019 let mut pool = self.inodes.lock().unwrap();
7020 if let Some(name) = procfs {
7021 let path = pool.procfs_path_key(&name);
7022 pool.procfs_key(ino, path);
7023 }
7024 let (dino, ns) = pool.add_sighted_inode(ino, observed, epoch, sighting);
7025 drop(pool);
7026 trace!(
7027 "[detcore, dtid {}] resolved (raw) inode {:?} to {:?}, mtime {}",
7028 from, ino, dino, ns
7029 );
7030 (dino, ns)
7031 }
7032
7033 async fn recv_determinize_mapping_inodes(
7034 &self,
7035 from: Tid,
7036 inodes: Vec<RawInode>,
7037 ) -> Vec<DetInode> {
7038 let _sched = self.lock_rpc_scheduler(false).await;
7039 let epoch = self.epoch_logical_time();
7040 let mut pool = self.inodes.lock().unwrap();
7041 let dinos: Vec<DetInode> = inodes
7042 .iter()
7043 .map(|ino| pool.add_inode(*ino, ObservedMtime::Unobserved, epoch).0)
7044 .collect();
7045 trace!(
7046 "[detcore, dtid {}] resolved mapping (raw) inodes {:?} to {:?}",
7047 from, inodes, dinos
7048 );
7049 dinos
7050 }
7051
7052 async fn recv_determinize_device(&self, from: Tid, raw_device: u64) -> u64 {
7055 let _sched = self.lock_rpc_scheduler(false).await;
7056 let det_device = self.devices.lock().unwrap().determinize(raw_device);
7057 trace!(
7058 "[detcore, dtid {}] resolved (raw) device {} to {}",
7059 from, raw_device, det_device
7060 );
7061 det_device
7062 }
7063
7064 async fn recv_determinize_mount_id(
7065 &self,
7066 from: Tid,
7067 raw_mount_id: u64,
7068 mountinfo_order: Option<&[u64]>,
7069 ) -> Option<u64> {
7070 let _sched = self.lock_rpc_scheduler(false).await;
7071 let virtual_mount_id = self
7072 .mount_ids
7073 .lock()
7074 .unwrap()
7075 .determinize(raw_mount_id, mountinfo_order);
7076 trace!(
7077 "[detcore, dtid {}] resolved fdinfo mount ID {} to {:?}",
7078 from, raw_mount_id, virtual_mount_id
7079 );
7080 virtual_mount_id
7081 }
7082
7083 async fn recv_resolve_mountinfo_ids(
7084 &self,
7085 from: Tid,
7086 mountinfo_order: &[u64],
7087 observation_order: &[u64],
7088 ) -> Option<Vec<u64>> {
7089 let _sched = self.lock_rpc_scheduler(false).await;
7090 let virtual_mount_ids = self
7091 .mount_ids
7092 .lock()
7093 .unwrap()
7094 .resolve_mountinfo(mountinfo_order, observation_order);
7095 trace!(
7096 "[detcore, dtid {}] resolved mountinfo observation order {:?} (canonical {:?}) to {:?}",
7097 from, observation_order, mountinfo_order, virtual_mount_ids
7098 );
7099 virtual_mount_ids
7100 }
7101
7102 async fn recv_retire_inode(&self, from: Tid, ino: RawInode) {
7103 let _sched = self.lock_rpc_scheduler(false).await;
7104 trace!("[detcore, dtid {}] retire (raw) inode {:?}", from, ino);
7105 self.inodes.lock().unwrap().retire(ino);
7106 }
7107
7108 async fn recv_forget_retired_inode(
7109 &self,
7110 from: Tid,
7111 ino: RawInode,
7112 procfs: Option<ProcfsName>,
7113 ) {
7114 let _sched = self.lock_rpc_scheduler(false).await;
7115 trace!(
7116 "[detcore, dtid {}] forget retired (raw) inode {:?}",
7117 from, ino
7118 );
7119 let mut pool = self.inodes.lock().unwrap();
7120 if let Some(name) = procfs {
7121 let path = pool.procfs_path_key(&name);
7122 pool.procfs_key(ino, path);
7123 }
7124 pool.forget_retired(ino);
7125 }
7126
7127 async fn recv_touch_file(&self, from: Tid, ino: RawInode) {
7128 let _sched = self.lock_rpc_scheduler(false).await;
7129 let mtime = if self.cfg.virtualize_time {
7130 self.global_time.lock().unwrap().as_nanos()
7131 } else {
7132 let dt: DateTime<Utc> = Utc::now();
7135 let nanos = dt.timestamp_nanos_opt().expect(
7136 "current time cannot be represented in a timestamp with nanosecond precision",
7137 ) as u64;
7138 LogicalTime::from_nanos(nanos)
7139 };
7140 trace!(
7141 "[dtid {}] bumping mtime on file (rawinode {:?}) to {}",
7142 from, ino, mtime,
7143 );
7144 self.set_inode_mtime(ino, mtime);
7145 }
7146
7147 async fn recv_set_file_mtime(&self, from: Tid, ino: RawInode, mtime: LogicalTime) {
7153 let _sched = self.lock_rpc_scheduler(false).await;
7154 trace!(
7155 "[dtid {}] setting mtime on file (rawinode {:?}) to {}",
7156 from, ino, mtime,
7157 );
7158 self.set_inode_mtime(ino, mtime);
7159 }
7160
7161 fn set_inode_mtime(&self, ino: RawInode, mtime: LogicalTime) {
7162 self.inodes.lock().unwrap().set_mtime(ino, mtime);
7163 }
7164
7165 fn with_boundary_stack(response: GlobalResponse, stack: MaybePrintStack) -> GlobalResponse {
7166 match stack {
7167 Some(path) => GlobalResponse::WithSliceBoundaryStack(path, Box::new(response)),
7168 None => response,
7169 }
7170 }
7171
7172 fn signal_requested_stacktrace(&self, dettid: DetTid) {
7174 if let Some(sig) = &self.cfg.stacktrace_signal {
7175 trace!(
7176 "[dtid {}] signaling thread with {} at the point of stack trace printing.",
7177 dettid, sig.0
7178 );
7179 let tid = Pid::from_raw(dettid.as_raw());
7180 match sig.signal() {
7187 Some(named) => signal::kill(tid, named).unwrap(),
7188 None => {
7189 let rc = unsafe { libc::kill(tid.as_raw(), sig.raw()) };
7192 assert_eq!(rc, 0, "raw kill of signal {} failed", sig.raw());
7193 }
7194 }
7195 }
7196 }
7197
7198 async fn recv_trace_schedevent(
7199 &self,
7200 ev: SchedEvent,
7201 detpid: DetPid,
7202 request_mm: MmId,
7203 command_bootstrap: bool,
7204 access: Option<Box<crate::access_trace::AccessPayload>>,
7205 ) -> SchedulerRpcResult<TraceSchedEventResponse> {
7206 let ev = {
7207 let mut sched = self.lock_rpc_scheduler(false).await;
7208 if !sched.rpc_incarnation_matches(ev.dettid, request_mm) {
7209 return SchedulerRpcResult::ThreadExited;
7210 }
7211 if sched.callback_history_active {
7212 sched
7213 .callback_progress
7214 .entry(ev.dettid)
7215 .or_default()
7216 .observe_event(&ev);
7217 }
7218 let ev = {
7220 if self.past_first_execve.load(SeqCst) {
7221 ev
7222 } else {
7223 info!(
7224 "Warning: erasing rip of pre-execve sched event! {:?}",
7225 SchedEventForLog {
7226 event: &ev,
7227 command_bootstrap
7228 }
7229 );
7230 SchedEvent {
7231 end_rip: None,
7232 start_rip: None,
7233 ..ev
7234 }
7235 }
7236 };
7237 if ev.op == Op::Syscall(Sysno::execve, SyscallPhase::Prehook) {
7239 self.past_first_execve.store(true, SeqCst);
7240 }
7241 ev
7242 };
7243
7244 let result = if self.cfg.replay_schedule_from.is_some() {
7246 let (consumed, print_stack2) = {
7247 let mut sched = self.lock_rpc_scheduler(false).await;
7248 if sched.thread_is_logically_killed(ev.dettid)
7249 || !sched.rpc_incarnation_matches(ev.dettid, request_mm)
7250 {
7251 return SchedulerRpcResult::ThreadExited;
7252 }
7253 let consumed = sched.consume_schedevent(&ev);
7254 let print_stack2 = if self.cfg.record_preemptions {
7255 let stack = sched.record_event(&ev);
7256 sched.record_access(&ev, access);
7257 stack
7258 } else {
7259 None
7260 };
7261 (consumed, print_stack2)
7262 };
7263 let ConsumeResult {
7264 keep_running,
7265 print_stack,
7266 event_ix: _,
7267 timeslice_remaining: mut end_of_timeslice,
7268 } = consumed;
7269 trace!(
7270 "keep_running :{}, end_of_timeslice: {:?}",
7271 keep_running, end_of_timeslice
7272 );
7273
7274 if !keep_running {
7275 trace!(
7276 "[detcore, dtid {}] Thread yielding to follow replay schedule",
7277 &ev.dettid,
7278 );
7279 let tid = reverie::Tid::from(ev.dettid.as_raw()); let mut rsrcs = Resources::new(ev.dettid);
7281 rsrcs.insert(ResourceID::TraceReplay, Permission::RW);
7282 let (response, timeslice) = self
7283 .recv_grant_resources(
7284 tid,
7285 detpid,
7286 rsrcs,
7287 Some(request_mm),
7288 RpcOrigin::TraceSchedEvent,
7289 )
7290 .await;
7291 if response == SchedulerRpcResult::ThreadExited {
7292 return SchedulerRpcResult::ThreadExited;
7293 }
7294 end_of_timeslice = timeslice;
7295 trace!(
7296 "[detcore, dtid {}] Thread reactivated after yielding for replay schedule",
7297 &ev.dettid,
7298 );
7299 }
7300
7301 let (schedule_released, schedule_over) = self
7307 .lock_rpc_scheduler(false)
7308 .await
7309 .schedule_replay_reply(ev.dettid);
7310 TraceSchedEventResponse {
7311 print_stack_strace: print_stack.or(print_stack2),
7312 timeslice: end_of_timeslice,
7313 schedule_released,
7314 schedule_over,
7315 }
7316 } else {
7317 let print_stack_strace = {
7318 let mut sched = self.lock_rpc_scheduler(false).await;
7319 if sched.thread_is_logically_killed(ev.dettid)
7320 || !sched.rpc_incarnation_matches(ev.dettid, request_mm)
7321 {
7322 return SchedulerRpcResult::ThreadExited;
7323 }
7324 if self.cfg.record_preemptions {
7325 let stack = sched.record_event(&ev);
7326 sched.record_access(&ev, access);
7327 stack
7328 } else {
7329 None
7330 }
7331 };
7332 TraceSchedEventResponse {
7333 print_stack_strace,
7334 timeslice: None,
7335 schedule_released: false,
7336 schedule_over: false,
7337 }
7338 };
7339
7340 if result.print_stack_strace.is_some() && self.cfg.stacktrace_signal.is_some() {
7341 let _sched = self.lock_rpc_scheduler(false).await;
7342 self.signal_requested_stacktrace(ev.dettid);
7343 }
7344
7345 SchedulerRpcResult::Continue(result)
7346 }
7347
7348 fn read_port_range() -> Vec<u16> {
7352 let contents = fs::read_to_string("/proc/sys/net/ipv4/ip_local_port_range")
7353 .expect("File should be present");
7354 let range: Vec<u16> = contents
7355 .split_whitespace()
7356 .filter_map(|number| number.parse().ok())
7357 .collect();
7358 range
7359 }
7360
7361 fn update_port_range(&self) {
7363 let range = Self::read_port_range();
7364 self.port_start_range.store(range[0], SeqCst);
7365 self.port_end_range.store(range[1], SeqCst);
7366 }
7367
7368 async fn recv_register_alarm(
7373 &self,
7374 detpid: DetPid,
7375 caller: RpcIncarnation,
7376 now: LogicalTime,
7377 duration: LogicalTime,
7378 interval: LogicalTime,
7379 sig: SigWrapper,
7380 ) -> SchedulerRpcResult<(LogicalTime, LogicalTime)> {
7381 let RpcIncarnation { dettid, mm } = caller;
7382 let mut sched = self.lock_rpc_scheduler(false).await;
7383 if sched.thread_is_logically_killed(dettid) || !sched.rpc_incarnation_matches(dettid, mm) {
7384 return SchedulerRpcResult::ThreadExited;
7385 }
7386 match sched.replace_real_timer(detpid, dettid, now, duration, interval, alarm_signal(sig)) {
7387 Ok(old) => SchedulerRpcResult::Continue(old),
7388 Err(error) => {
7389 sched.fail_parked(dettid, ProtocolFailure::Timer(error));
7390 SchedulerRpcResult::ThreadExited
7391 }
7392 }
7393 }
7394
7395 async fn recv_register_posix_timer(
7401 &self,
7402 detpid: DetPid,
7403 caller: RpcIncarnation,
7404 timer_id: i32,
7405 deadline: Option<LogicalTime>,
7406 interval: LogicalTime,
7407 notification: PosixTimerNotification,
7408 ) -> SchedulerRpcResult<()> {
7409 let PosixTimerNotification {
7410 sig,
7411 thread,
7412 value,
7413 deleted,
7414 } = notification;
7415 let RpcIncarnation { dettid, mm } = caller;
7416 let mut sched = self.lock_rpc_scheduler(false).await;
7417 if sched.thread_is_logically_killed(dettid) || !sched.rpc_incarnation_matches(dettid, mm) {
7418 return SchedulerRpcResult::ThreadExited;
7419 }
7420 let target = match thread {
7422 Some(thread) => SignalTarget::Thread(thread),
7423 None => SignalTarget::Process(dettid),
7424 };
7425 sched.note_posix_timer_notification(detpid, timer_id, sig.raw(), value, deleted);
7426 sched.register_posix_timer(
7427 detpid,
7428 target,
7429 timer_id,
7430 deadline,
7431 interval,
7432 match sig.signal() {
7433 Some(signal) => signal.into(),
7434 None => crate::scheduler::timed_waiters::TimerSignal::RefuseRealTime(sig.raw()),
7435 },
7436 );
7437 SchedulerRpcResult::Continue(())
7438 }
7439}
7440
7441#[derive(PartialEq, Debug, Eq, Clone, Copy, Serialize, Deserialize)]
7443pub struct PosixTimerNotification {
7444 pub sig: SigWrapper,
7446 pub thread: Option<DetTid>,
7449 pub value: u64,
7451 pub deleted: bool,
7453}
7454
7455#[derive(PartialEq, Debug, Eq, Clone, Serialize, Deserialize)]
7457pub struct ThreadDeregistration {
7458 pub(crate) dettid: DetTid,
7459 pub(crate) detpid: DetPid,
7460 pub(crate) mm: MmId,
7461 pub(crate) thread_start_entered: bool,
7464 pub(crate) vfork_origin: Option<ExternalOpId>,
7467 pub(crate) timeslice_stats: TimesliceStats,
7468 pub(crate) syscall_count: u64,
7469 pub(crate) syscall_histogram: SparseSyscallHistogram,
7472 pub(crate) chaos_epochs: Vec<ChaosEpochTransition>,
7473 pub(crate) killed_by_group_signal: bool,
7476 pub(crate) passed_descriptors_out: bool,
7479}
7480
7481#[derive(PartialEq, Debug, Eq, Clone, Copy, Serialize, Deserialize)]
7487pub enum SigalrmControl {
7488 SendTo(DetTid),
7490 SendToUnnamed,
7493 ArmProducer,
7499 ArmRecurringTimer,
7502 UnadmittedStdioIo,
7507 InstallHandler,
7511 HandlerToDefault,
7515 PendingEntry,
7518 Publish {
7522 handled: bool,
7524 blocked: bool,
7526 },
7527 PublishBlocked(bool),
7530 DueEntry,
7534 TakeEntry,
7539 DeliveryLost,
7544 EntryDeliveryUnfaithful,
7551}
7552
7553#[derive(PartialEq, Debug, Eq, Clone, Copy, Serialize, Deserialize)]
7562pub enum SignalScope {
7563 Process,
7565 Thread,
7567}
7568
7569pub(crate) const GLOBAL_RPC_REVISION: u32 = 21;
7579
7580#[derive(Clone)]
7599pub struct HeldTable(Option<Arc<Mutex<crate::tool_local::FileMetadata>>>);
7600
7601impl HeldTable {
7602 pub(crate) fn new(table: Arc<Mutex<crate::tool_local::FileMetadata>>) -> Self {
7604 Self(Some(table))
7605 }
7606}
7607
7608impl std::fmt::Debug for HeldTable {
7609 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7610 f.write_str("HeldTable")
7611 }
7612}
7613
7614impl PartialEq for HeldTable {
7615 fn eq(&self, other: &Self) -> bool {
7616 match (&self.0, &other.0) {
7617 (Some(table), Some(other)) => Arc::ptr_eq(table, other),
7618 (None, None) => true,
7619 _ => false,
7620 }
7621 }
7622}
7623
7624impl Eq for HeldTable {}
7625
7626const HELD_TABLE_IS_IN_PROCESS: &str = "a guest thread's descriptor table (GlobalRequest::HoldTable) \
7628 is handed over in memory and does not cross a process boundary";
7629
7630impl Serialize for HeldTable {
7632 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
7633 serializer.serialize_unit()
7634 }
7635}
7636
7637impl<'de> Deserialize<'de> for HeldTable {
7640 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
7641 <()>::deserialize(deserializer).map(|()| Self(None))
7642 }
7643}
7644
7645#[derive(PartialEq, Debug, Eq, Clone, Serialize, Deserialize)]
7648#[allow(clippy::enum_variant_names)]
7649pub enum GlobalRequest {
7650 WithForwardedCount(u64, Box<GlobalRequest>),
7655 RequestResources(Resources, DetPid),
7658 ParkedRequest(Resources, DetPid, ControlCapability),
7660 ResumeParkedRequest {
7662 ticket: ResumeTicket,
7664 current_site: reverie::CallbackSignalSite,
7666 },
7667 FinishParkedObservation {
7669 wait: ContinuationId,
7671 lease: reverie::ParkedObservationLease,
7673 site: reverie::CallbackSignalSite,
7675 finish: ObservationFinish,
7677 },
7678 ParkedProtocolFailure(ProtocolFailure),
7680 SignalDequeued {
7682 detpid: DetPid,
7684 identity: reverie::SignalTaskIdentity,
7686 dequeue: reverie::SignalDequeue,
7688 },
7689 ReleaseResources(Resources),
7691 ReleaseAllResources,
7693
7694 ReportUnsupportedSyscall(String),
7697
7698 AnnounceSignalHandlerNotInstalled(i32),
7702
7703 PolicyRefusal(String),
7708
7709 PrepareExec(DetPid, MmId, Box<ExecFdState>),
7719
7720 CancelExec(DetPid),
7722
7723 ReconnectExec {
7725 former: DetTid,
7727 process: DetPid,
7729 },
7730 ResumeExec(DetPid),
7732 RetireExec {
7736 thread: ThreadDeregistration,
7738 signaled: bool,
7740 },
7741
7742 MarkPastFirstExecve(
7751 DetPid,
7752 Option<reverie::SignalTaskIdentity>,
7753 Option<crate::exec_record::ExecRecord>,
7754 Option<(u64, u64)>,
7755 ),
7756
7757 CreateChildThread(
7763 DetTid,
7764 DetPid,
7765 usize,
7766 Option<CloneFlags>,
7767 libc::c_int,
7768 Option<(i32, i32)>,
7769 Option<Priority>,
7770 ),
7771
7772 CreateVforkChildThread(
7777 DetTid,
7778 DetPid,
7779 DetTid,
7780 usize,
7781 CloneFlags,
7782 libc::c_int,
7783 Option<Priority>,
7784 ),
7785
7786 StartNewThread(
7789 DetTid,
7790 DetPid,
7791 Option<(i32, i32)>,
7792 Option<reverie::SignalTaskIdentity>,
7793 ),
7794
7795 DeregisterThread(ThreadDeregistration),
7799
7800 SetChildTidAddress(usize),
7803
7804 SetSavedGuestSigmask(Option<u64>),
7808
7809 PublishedGuestSigmask(DetTid),
7813
7814 SetSigtimedwaitSet(Option<u64>),
7817
7818 OwnProcessSignalInputs,
7821
7822 ProcessSignalReceiver(DetPid, DetTid, i32),
7826
7827 ProcessSignalSent(DetPid),
7831
7832 OwnProcessSignalChoice(DetTid, i32),
7836
7837 RecordGuestMarkerSignal,
7839
7840 FutexAction(DetTid, FutexAction, FutexID, i32, u32, bool),
7844
7845 DeterminizeInode(RawInode, ObservedMtime, InodeSighting, Option<ProcfsName>),
7850
7851 DeterminizeMappingInodes(Vec<RawInode>),
7854
7855 DeterminizeDevice(u64),
7860
7861 DeterminizeMountId(u64, Option<Vec<u64>>),
7865
7866 ResolveMountinfoIds(Vec<u64>, Vec<u64>),
7871
7872 RetireInode(RawInode),
7875
7876 ForgetRetiredInode(RawInode, Option<ProcfsName>),
7880
7881 DirectorySnapshotMark,
7883
7884 ReadyChannel(crate::pipe_wake::ChannelId, crate::pipe_wake::Wants),
7887 HandOffTurn,
7890 CommitFatalDelivery(bool),
7895
7896 TouchFile(RawInode),
7898
7899 SetFileMtime(RawInode, LogicalTime),
7901
7902 GlobalTimeLowerBound,
7904
7905 TraceSchedEvent(
7910 SchedEvent,
7911 DetPid,
7912 bool,
7913 Option<Box<crate::access_trace::AccessPayload>>,
7914 ),
7915
7916 RegisterAlarm(DetPid, DetTid, LogicalTime, LogicalTime, SigWrapper),
7921
7922 RegisterPosixTimer(
7926 DetPid,
7927 DetTid,
7928 i32,
7929 Option<LogicalTime>,
7930 LogicalTime,
7931 PosixTimerNotification,
7932 ),
7933
7934 AlarmRemaining(DetPid),
7938 ReconcileAlarm(DetPid),
7941 NoteGuestSigalrm(crate::scheduler::itimer_coalesce::GuestSigalrmTarget),
7943 NoteSigalrmDisposition,
7945
7946 ReadyChildWait(DetPid, ChildWaitSpec),
7950 ConsumeChildWait(DetPid, DetPid),
7952 ProcessGroup(DetPid),
7954 SetProcessGroup(DetPid, DetPid),
7956 CreateSession(DetPid),
7958 ResolveKillTargets(DetPid),
7961 NamesHermitTask(DetTid, Option<i32>, i32),
7964 NotifySignalPending(DetTid, SigWrapper, Option<DetPid>, SignalScope),
7967 HoldTurnForGroupExit(DetTid),
7972 NotifyUnresolvedSigkill(Option<DetPid>),
7976 ThreadIsLive(DetTid),
7978 PendingRealTimeExpirations(DetPid),
7981 DiscardPendingRealTimeExpirations(DetPid, u64),
7984 RegisterSignalfd(DetPid, u64),
7986 RecordHandlerRestoreMask(DetPid, u64),
7989 ExactChildWaitState(DetPid, DetPid),
7991
7992 UnrecoverableShutdown,
7994
7995 RequestPort(OpenFileId),
7997
7998 AddUsedPort(u16, OpenFileId),
8000
8001 ReleasePort(OpenFileId),
8003
8004 RobustListWakes(Vec<(DetTid, FutexID)>),
8007 Network(NetworkRequest),
8009
8010 RecordHostInput {
8017 path: String,
8019 syscall: u64,
8021 identity: HostFileIdentity,
8023 source: Option<String>,
8028 regular: bool,
8030 exec: bool,
8033 kernel_object: bool,
8036 },
8037
8038 RecordHostMutation {
8044 path: String,
8046 },
8047 RecordHostTimedSignals(DetTid, HostTimedSignalScope, u64),
8051 HostTimedSignals(DetTid),
8055 SharedOpenFile(OpenFileControl),
8060 Sigalrm(SigalrmControl),
8064 ChildExitSigchld(crate::scheduler::child_exit_sigchld::ChildExitSigchldControl),
8067 RecordDeterminismLoss(String),
8073 SetChildSubreaper(DetPid, bool),
8077 RegisteredProcessOf(DetTid),
8082 SetSigchldDisposition(DetPid, bool, bool),
8087 SignalTargetProcess(DetPid),
8091 ReserveSigkill(DetTid, DetTid, u32),
8096 CommitSigkill(u64, bool),
8099 ReserveSigkillGroup(DetTid, DetPid, u32),
8103 PosixTimerThread(DetPid, i32),
8107 ForgetThreadTimerCopies(DetPid, i32),
8111 GuestPid(DetTid, i32),
8115 LocalPid(DetTid, DetTid),
8119 ExecTimerFlush(DetPid),
8123 RetireExitedLeader,
8127 DiscardedSignal(crate::scheduler::discarded_at_send::DiscardedSignalControl),
8131 DescriptorsReplaced(detcore_model::fd::FilesId, u32, u32),
8136 ThreadIdRefused(DetTid),
8140 ProcfsListing(Option<DetPid>),
8144 ParentDeathSignal(Option<i32>),
8148
8149 PrepareNativeExec(native_exec::PrepareNativeExec),
8151 SealNativeExecStats {
8153 ticket: native_exec::ExecTicket,
8155 runtime_digest: [u8; 32],
8157 runtime: Vec<u8>,
8159 },
8160 IssueNativeExec {
8162 ticket: native_exec::ExecTicket,
8164 state_digest: [u8; 32],
8166 },
8167 CancelNativeExec {
8169 ticket: native_exec::ExecTicket,
8171 },
8172 CompleteNativeExec {
8174 ticket: native_exec::ExecTicket,
8176 },
8177 NamesARetiredExitedLeader(DetTid),
8181 ProcfsIds(bool, Vec<i32>),
8185 GroupOrSession(DetPid, bool),
8189 SetsidDecision(DetPid),
8192 SetpgidDecision(DetPid, i32, i32),
8195 ProcessImage(DetPid),
8199 ProcfsTask(DetTid, Option<DetTid>),
8203 RenderedFileState(u64, crate::procfs::listing::RenderedFileChange),
8206 PublishSignalDispositions(
8211 u64,
8212 Option<crate::syscalls::signal_shadow::CopiedDispositions>,
8213 ),
8214 GuestLog(Option<crate::guest_log::GuestLogRecord>, u64),
8220 PublishSignalMask(Option<u64>),
8224 RecordProcessAuxv(Vec<u8>, Option<crate::procfs::CmdlineArea>),
8229 ProcessAuxv(DetPid),
8232 AuxvFromKernel(AuxvFromKernel, Option<crate::procfs::CmdlineArea>),
8239 AnnounceRefusedSignalControls(u64, bool),
8244 CheckBrkMappings(
8248 crate::syscalls::memory::BrkMappingCheck,
8249 reverie::ControllerCaptureFact,
8250 ),
8251 PublishRuntimePrivateMemory(reverie::RuntimePrivateMemory),
8254 MarkChangedRoot,
8257 RootChanged(DetPid),
8260 DescriptionLocks(
8267 OpenFileId,
8268 Result<Vec<crate::procfs::locks::HeldLock>, String>,
8269 u32,
8270 ),
8271 TableReleased(OpenFileId, u32),
8275 GuestLockRows(Vec<crate::procfs::locks::LockRowFacts>),
8278 TableExec(Vec<(OpenFileId, u32)>),
8282 AliasedInode(Option<String>),
8287 HostTimedAndPrivateSigchld(DetTid),
8291 LockLedgerRefused(String),
8295 MappingCapture(mapping_capture::Request),
8297 StoppingRefusal(String),
8309 ProcessCmdline(DetPid),
8313 KernelRunTarget(DetTid),
8318 CallbackCheckpointMismatch {
8320 checkpoint: crate::preemptions::callback_history::Checkpoint,
8322 hard: bool,
8324 actual: crate::preemptions::callback_history::Origin,
8326 },
8327 TaskStatFacts(DetTid, Option<DetTid>),
8331 SetTaskComm(DetTid, crate::procfs::task_stat::TaskComm),
8335 RecordExternalParent(DetPid, i32),
8340 SetRssLimit(DetPid, u64),
8343 PublishTemporaryMask(crate::scheduler::guest_mask_publication::TemporaryMaskControl),
8350 InPidNamespaceInit(DetTid),
8356 VirtualInotifyCookies(Vec<u32>),
8360 RegisterPlaceholder(
8364 crate::procfs::fd_directory::PlaceholderDirectory,
8365 OpenFileId,
8366 ),
8367 Placeholder(u64),
8370 PlaceholderFlock(crate::procfs::locks::PlaceholderFlock),
8373 PlaceholderWriteHint(crate::procfs::fd_directory::PlaceholderLockKey, Option<u64>),
8377 PlaceholderCopies(crate::procfs::locks::PlaceholderCopies),
8381 CpuAffinity(DetTid),
8384 SetCpuAffinity(DetTid, crate::cpu_set::CpuSet),
8388 #[cfg(target_arch = "aarch64")]
8394 StoreExclusive(crate::store_exclusive::Report),
8395 SigkillDyingThreads(u64),
8399 RecordSigkillRobustLists(
8403 u64,
8404 Vec<(
8405 DetTid,
8406 MmId,
8407 crate::syscalls::robust_list::RobustListSnapshot,
8408 )>,
8409 ),
8410 TakeSigkillRobustList,
8414 NextGuestId,
8418 GaveHostId,
8421 ReplayDeparture {
8425 previous: LogicalTime,
8427 observed: LogicalTime,
8429 slice: u64,
8431 },
8432 DescriptionLocksTaken(
8441 OpenFileId,
8442 Vec<crate::procfs::locks::HeldLock>,
8443 Vec<crate::procfs::locks::HeldLock>,
8444 u32,
8445 crate::procfs::locks::LedgerIdentity,
8446 ),
8447 LockOwners(OpenFileId, Vec<crate::procfs::locks::HeldLock>),
8451 LedgerLockRows,
8455 SecondIdentityLocks(OpenFileId, Vec<crate::procfs::locks::HeldLock>, u32),
8462 HoldTable(HeldTable),
8469 RecordLocksHeldAsOfd(Option<Vec<String>>),
8476 HostDiskstats,
8482 UserNamespaceRelation(DetTid, DetTid),
8488 UserNamespaceOwnerMayDiffer(DetTid),
8494 ProcfsSequence(OpenFileId),
8500 StoreProcfsSequence(OpenFileId, crate::procfs::ProcfsSequence),
8504}
8505
8506#[allow(missing_docs, clippy::unit_arg)]
8508#[derive(PartialEq, Debug, Eq, Clone, Serialize, Deserialize)]
8509pub enum GlobalResponse {
8510 ThreadExited,
8513 WithDeadline(Duration, Box<GlobalResponse>),
8518 Granted(Box<GlobalResponse>),
8523 RequestResources(ResumeStatus),
8524 ParkedRequest(ResourceReply),
8525 ResumeParkedRequest(ResourceReply),
8526 FinishParkedObservation(Result<FinishAck, ProtocolFailure>),
8527 SignalDequeued {
8528 ack: Result<DequeueAck, TimerFailure>,
8529 terminal: bool,
8530 },
8531 ReleaseResources(()),
8532 ReleaseAllResources(()),
8533 ReportUnsupportedSyscall(()),
8535 AnnounceSignalHandlerNotInstalled(()),
8536 PolicyRefusal(()),
8537 PrepareExec(()),
8538 CancelExec(()),
8539 ReconnectExec(bool),
8540 ResumeExec(ResumeStatus, Option<LogicalTime>),
8541 RetireExec(bool),
8542 MarkPastFirstExecve(Box<ExecFdState>),
8543 CreateChildThread(Option<MmId>),
8544 StartNewThread(Option<ThreadHistory>),
8546 DeregisterThread(()),
8547 SetChildTidAddress(()),
8548 SetSavedGuestSigmask(()),
8549 PublishedGuestSigmask(Option<u64>),
8550 SetSigtimedwaitSet(()),
8551 OwnProcessSignalInputs(OwnProcessSignalInputs),
8552 ProcessSignalReceiver(ProcessSignalReceiver),
8553 ProcessSignalSent(()),
8554 OwnProcessSignalChoice(()),
8555 RecordGuestMarkerSignal(()),
8556 FutexAction(Option<SchedValue>),
8557 DeterminizeInode((DetInode, LogicalTime)),
8559 DeterminizeMappingInodes(Vec<DetInode>),
8560 DeterminizeDevice(u64),
8563 DeterminizeMountId(Option<u64>),
8564 ResolveMountinfoIds(Option<Vec<u64>>),
8565 RetireInode(()),
8566 ForgetRetiredInode(()),
8567 DirectorySnapshotMark(SnapshotMark),
8568 ReadyChannel(crate::pipe_wake::ReadyOutcome),
8569 HandOffTurn(bool),
8570 CommitFatalDelivery(bool),
8572 TouchFile(()),
8573 SetFileMtime(()),
8574 GlobalTimeLowerBound(LogicalTime),
8575 TraceSchedEvent(TraceSchedEventResponse),
8576 RegisterAlarm((LogicalTime, LogicalTime)),
8580 RegisterPosixTimer(()),
8583 ReadyChildWait((Option<DetPid>, bool)),
8586 ConsumeChildWait(bool),
8587 ProcessGroup(Option<DetPid>),
8588 SetProcessGroup(bool),
8589 CreateSession(bool),
8590 AlarmRemaining(ItimerSnapshot),
8591 ReconcileAlarm,
8592 NoteGuestSigalrm,
8593 ResolveKillTargets(Vec<DetTid>, Option<bool>),
8596 NamesHermitTask(bool),
8597 NotifySignalPending(()),
8598 HoldTurnForGroupExit(bool),
8599 NotifyUnresolvedSigkill(()),
8600 ThreadIsLive(bool),
8601 PendingRealTimeExpirations(u64),
8602 DiscardPendingRealTimeExpirations(()),
8603 RegisterSignalfd(()),
8604 RecordHandlerRestoreMask(()),
8605 ExactChildWaitState(ExactChildWaitState),
8606 UnrecoverableShutdown(()),
8608
8609 RequestPort(u16),
8610 AddUsedPort,
8611 ReleasePort(Option<u16>),
8612 PortFull,
8613 RobustListWakes(Vec<u64>),
8614 Network(Result<NetworkReply, NetworkEngineError>),
8615 RecordHostInput(()),
8618 RecordHostMutation(()),
8621 HostTimedSignals(u64),
8624 SharedOpenFile(Result<OpenFileControlReply, SharedOpenFileError>),
8626 Sigalrm(bool),
8629 ChildExitSigchld(crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer),
8630 RecordDeterminismLoss(()),
8632 SetChildSubreaper(()),
8634 RegisteredProcessOf(Option<DetPid>),
8636 SetSigchldDisposition(()),
8638 SignalTargetProcess(Option<DetPid>),
8640 ReserveSigkill(Option<crate::scheduler::sigkill_retirement::SigkillReservation>),
8643 CommitSigkill(()),
8645 Retired,
8651 PosixTimerThread(PosixTimerThread),
8653 ForgetThreadTimerCopies(()),
8655 GuestPid(crate::scheduler::pid_namespaces::GuestPidAnswer),
8657 LocalPid(Result<Option<i32>, String>),
8659 ExecTimerFlush(Result<crate::scheduler::ExecTimerFlush, String>),
8661 RetireExitedLeader(bool),
8663 DiscardedSignal(crate::scheduler::discarded_at_send::DiscardedSignalAnswer),
8665 DescriptorsReplaced(()),
8667 ThreadIdRefused(bool),
8669 ProcfsListing(crate::procfs::listing::ListingFacts),
8671 ParentDeathSignal(i32),
8673
8674 PrepareNativeExec(Result<native_exec::ExecTicket, native_exec::ExecControlError>),
8676 SealNativeExecStats(Result<[u8; 32], native_exec::ExecControlError>),
8677 IssueNativeExec(Result<(), native_exec::ExecControlError>),
8678 CancelNativeExec(Result<(), native_exec::ExecControlError>),
8679 CompleteNativeExec(Result<(), native_exec::ExecControlError>),
8680 NamesARetiredExitedLeader(bool),
8682 ProcfsIds(Vec<Option<i32>>),
8684 GroupOrSession(Option<DetPid>),
8686 SetsidDecision(Result<(), i32>),
8688 SetpgidDecision(Result<(DetPid, DetPid), i32>),
8691 ProcessImage(Option<Box<crate::exec_record::ExecRecord>>),
8693 ProcfsTask(crate::procfs::listing::ProcfsTaskAnswer),
8695 RenderedFileState(crate::procfs::listing::RenderedFileState),
8697 PublishSignalDispositions(Option<crate::syscalls::signal_shadow::PublicationAnswer>),
8702 GuestLog(()),
8704 PublishSignalMask(bool),
8707 RecordProcessAuxv(()),
8709 ProcessAuxv(Option<ProcessAuxv>),
8711 AuxvFromKernel(bool),
8713 AnnounceRefusedSignalControls(()),
8715 CheckBrkMappings(Option<bool>),
8718 PublishRuntimePrivateMemory(bool),
8720 MarkChangedRoot(()),
8722 RootChanged(bool),
8724 DescriptionLocks(()),
8726 TableReleased(()),
8728 GuestLockRows(Result<Vec<bool>, String>),
8731 TableExec(()),
8733 AliasedInode(()),
8735 HostTimedAndPrivateSigchld(u64, u64),
8738 LockLedgerRefused(()),
8740 MappingCapture(mapping_capture::Reply),
8742 WithSliceBoundaryBudget(crate::slice_boundary::grant::Budget, Box<GlobalResponse>),
8745 WithSliceBoundaryOwner(
8748 detcore_model::schedule::boundary::Owner,
8749 Box<GlobalResponse>,
8750 ),
8751 WithSliceBoundaryStack(Option<PathBuf>, Box<GlobalResponse>),
8754 ProcessCmdline(DetPid, Option<crate::procfs::CmdlineArea>),
8756 KernelRunTarget(Option<crate::scheduler::KernelRunBlock>),
8758 WithCallbackHistory(
8761 crate::preemptions::callback_history::Startup,
8762 Box<GlobalResponse>,
8763 ),
8764 CallbackCheckpointMismatch(()),
8766 TaskStatFacts(crate::procfs::task_stat::TaskStatFacts),
8768 SetTaskComm(()),
8770 RecordExternalParent(()),
8772 SetRssLimit(()),
8774 GuestSigmaskForStatus(crate::scheduler::guest_mask_publication::StatusGuestMask),
8781 PublishTemporaryMask(Option<crate::scheduler::guest_mask_publication::CommittedSendWindow>),
8786 InPidNamespaceInit(bool),
8788 VirtualInotifyCookies(Vec<u32>),
8791 RegisterPlaceholder(Option<u64>),
8794 Placeholder(
8797 Option<(
8798 crate::procfs::fd_directory::PlaceholderDirectory,
8799 OpenFileId,
8800 )>,
8801 ),
8802 PlaceholderFlock(crate::procfs::locks::PlaceholderFlockOutcome),
8804 PlaceholderWriteHint(u64),
8807 PlaceholderCopies(Result<(), OpenFileId>),
8810 CpuAffinity(crate::cpu_set::TaskAffinity),
8813 WithLoneGrant(Box<GlobalResponse>),
8819 #[cfg(target_arch = "aarch64")]
8821 StoreExclusive(()),
8822 SigkillDyingThreads(Vec<(DetTid, MmId)>),
8825 RecordSigkillRobustLists(()),
8827 TakeSigkillRobustList(Option<crate::syscalls::robust_list::RobustListSnapshot>),
8829 NextGuestId(Option<i32>),
8832 GaveHostId(()),
8834 ReplayDeparture(Result<(), replay_departure::Refusal>),
8837 DescriptionLocksTaken(()),
8839 LockOwners(Result<Vec<i64>, String>),
8843 LedgerLockRows(Result<Vec<(crate::procfs::locks::HeldLock, i64)>, String>),
8846 SecondIdentityLocks(()),
8848 HoldTable(Result<(), String>),
8852 RecordLocksHeldAsOfd(()),
8854 HostDiskstats(Result<Vec<u8>, i32>),
8857 UserNamespaceRelation(
8859 crate::scheduler::user_namespaces::UserNamespaceRelation,
8860 bool,
8861 ),
8862 UserNamespaceOwnerMayDiffer(()),
8865 ProcfsSequence(Option<crate::procfs::ProcfsSequence>),
8867 StoreProcfsSequence(()),
8869}
8870
8871pub(crate) fn check_brk_mappings<G, T>(
8875 guest: &G,
8876 check: crate::syscalls::memory::BrkMappingCheck,
8877) -> Option<bool>
8878where
8879 G: Guest<Detcore<T>>,
8880 T: RecordOrReplay,
8881{
8882 let fact = guest.controller_capture_fact()?;
8883 let state = guest.thread_state();
8884 let request = (
8885 state.thread_logical_time.clone(),
8886 state.mm_id,
8887 GlobalRequest::CheckBrkMappings(check, fact),
8888 );
8889 match guest.try_send_rpc_sync(request).ok()? {
8890 (None, GlobalResponse::CheckBrkMappings(answer)) => answer,
8891 _ => None,
8892 }
8893}
8894
8895pub(crate) fn counted_request(request: GlobalRequest) -> GlobalRequest {
8900 match crate::detlog::take_forwarded_since_request() {
8901 0 => request,
8902 produced => GlobalRequest::WithForwardedCount(produced, Box::new(request)),
8903 }
8904}
8905
8906pub fn shared_open_file_request(control: OpenFileControl) -> (DetTime, MmId, GlobalRequest) {
8913 (
8914 DetTime::default(),
8915 MmId::initial(DetTid::from_raw(0)),
8916 GlobalRequest::SharedOpenFile(control),
8917 )
8918}
8919
8920pub(crate) fn guest_log_request(
8924 record: Option<crate::guest_log::GuestLogRecord>,
8925 unsent_before: u64,
8926) -> (DetTime, MmId, GlobalRequest) {
8927 (
8928 DetTime::default(),
8929 MmId::initial(DetTid::from_raw(0)),
8930 GlobalRequest::GuestLog(record, unsent_before),
8931 )
8932}
8933
8934pub(crate) fn guest_log_reply(
8936 response: (Option<LogicalTime>, GlobalResponse),
8937) -> std::io::Result<()> {
8938 match response {
8939 (None, GlobalResponse::GuestLog(())) => Ok(()),
8940 other => Err(std::io::Error::other(format!(
8941 "unexpected answer to a guest log record: {other:?}"
8942 ))),
8943 }
8944}
8945
8946#[cfg(target_arch = "aarch64")]
8949pub(crate) fn store_exclusive_request(
8950 report: crate::store_exclusive::Report,
8951) -> (DetTime, MmId, GlobalRequest) {
8952 (
8953 DetTime::default(),
8954 MmId::initial(DetTid::from_raw(0)),
8955 GlobalRequest::StoreExclusive(report),
8956 )
8957}
8958
8959#[cfg(target_arch = "aarch64")]
8962pub(crate) fn store_exclusive_reply(response: (Option<LogicalTime>, GlobalResponse)) {
8963 if response != (None, GlobalResponse::StoreExclusive(())) {
8964 debug!("unexpected answer to a store-exclusive report: {response:?}");
8965 }
8966}
8967
8968pub fn shared_open_file_reply(
8970 response: (Option<LogicalTime>, GlobalResponse),
8971) -> Result<OpenFileControlReply, SharedOpenFileError> {
8972 match response {
8973 (None, GlobalResponse::SharedOpenFile(reply)) => reply,
8974 other => Err(SharedOpenFileError(format!(
8975 "unexpected answer to a shared open file control message: {other:?}"
8976 ))),
8977 }
8978}
8979
8980pub fn format_unsupported_syscall_warning(syscalls: &BTreeSet<String>) -> Option<String> {
8984 if syscalls.is_empty() {
8985 None
8986 } else {
8987 Some(format!(
8988 "syscalls {} used but not yet supported",
8989 syscalls.iter().cloned().collect::<Vec<_>>().join(",")
8990 ))
8991 }
8992}
8993
8994pub async fn prepare_exec<G, T>(
9013 guest: &mut G,
9014 mm: MmId,
9015 fd_blocking: ExecFdBlockingOverrides,
9016 robust_wakes: Vec<(DetTid, FutexID)>,
9017 exe: Option<crate::exec_record::ExecRecord>,
9018) where
9019 G: Guest<Detcore<T>>,
9020 T: RecordOrReplay,
9021{
9022 crate::guest_log::flush_unsent();
9026 let detpid = guest.thread_state().detpid.expect("detpid unset");
9027 let fd_state = ExecFdState {
9028 blocking: fd_blocking,
9029 inherited_reads: guest.thread_state().inherited_reads_for_exec(),
9030 initialize_inherited_stdio: false,
9031 initial_input: None,
9032 robust_wakes,
9033 open_files: guest
9034 .thread_state()
9035 .shared_open_files_for_exec()
9036 .expect("prepared exec descriptors are no longer live"),
9037 maps: if guest.config().backend.rediscovers_descriptors_after_exec {
9038 guest
9039 .thread_state()
9040 .maps_for_exec(guest.runtime_private_memory_regions().as_ref())
9041 .expect("prepared maps descriptors are no longer live")
9042 } else {
9043 None
9044 },
9045 procfs_records: if guest.config().backend.rediscovers_descriptors_after_exec {
9049 guest
9050 .thread_state()
9051 .procfs_records_for_exec()
9052 .expect("prepared per-process descriptors are no longer live")
9053 } else {
9054 None
9055 },
9056 channels: Default::default(),
9057 open_file_namespace: None,
9058 random_device_sequence: guest.thread_state().random_device_sequence_for_exec(),
9059 exe,
9060 };
9061 let fd_state = if guest.config().pipe_wake_latency_in_effect().is_some()
9065 && guest.config().backend.rediscovers_descriptors_after_exec
9066 {
9067 ExecFdState {
9068 channels: guest.thread_state().pipe_channels_for_exec(),
9069 open_file_namespace: Some(guest.thread_state().open_file_namespace_for_exec()),
9070 ..fd_state
9071 }
9072 } else {
9073 fd_state
9074 };
9075 let (_, response) = send_and_update_time(
9076 guest,
9077 GlobalRequest::PrepareExec(detpid, mm, Box::new(fd_state)),
9078 )
9079 .await;
9080 assert_eq!(response, GlobalResponse::PrepareExec(()));
9081}
9082
9083pub async fn cancel_exec<G, T>(guest: &mut G)
9087where
9088 G: Guest<Detcore<T>>,
9089 T: RecordOrReplay,
9090{
9091 let detpid = guest.thread_state().detpid.expect("detpid unset");
9092 let (_, response) = send_and_update_time(guest, GlobalRequest::CancelExec(detpid)).await;
9093 assert_eq!(response, GlobalResponse::CancelExec(()));
9094}
9095
9096pub async fn mark_past_first_execve<G, T>(
9097 guest: &mut G,
9098 unprepared_exe: Option<crate::exec_record::ExecRecord>,
9099) where
9100 G: Guest<Detcore<T>>,
9101 T: RecordOrReplay,
9102{
9103 let controlled =
9104 guest.signal_control_mode() == reverie::BackendSignalControlMode::ToolControlled;
9105 let signal_identity = controlled.then(|| guest.signal_task_identity()).flatten();
9106 let detpid = guest.thread_state().detpid.expect("detpid unset");
9107 let kernel_image = crate::syscalls::guest_task_procfs(
9110 &guest.config().backend,
9111 guest.tid().as_raw(),
9112 guest
9113 .thread_state()
9114 .changed_root
9115 .load(std::sync::atomic::Ordering::SeqCst),
9116 )
9117 .and_then(|task| std::fs::metadata(task.join("exe")).ok())
9118 .map(|meta| {
9119 use std::os::unix::fs::MetadataExt;
9120 (meta.dev(), meta.ino())
9121 });
9122 let (_, response) = send_and_update_time(
9123 guest,
9124 GlobalRequest::MarkPastFirstExecve(detpid, signal_identity, unprepared_exe, kernel_image),
9125 )
9126 .await;
9127 let mut overrides = match response {
9128 GlobalResponse::MarkPastFirstExecve(overrides) => *overrides,
9129 _ => unreachable!(),
9130 };
9131 if controlled {
9132 guest.thread_state_mut().signal_task_identity = signal_identity;
9133 }
9134 guest.thread_state_mut().exe = overrides.exe.take().map(Arc::new);
9137 if !overrides.is_empty() {
9138 if let Some(creator) = overrides.open_file_creator() {
9139 guest.thread_state_mut().set_open_file_creator(creator);
9142 }
9143 let dettid = guest.thread_state().dettid;
9144 let metadata = Arc::clone(&guest.thread_state().file_metadata);
9145 metadata
9146 .lock()
9147 .unwrap()
9148 .apply_exec_fd_state(
9149 dettid,
9150 overrides,
9151 guest.config().backend.tool_shares_guest_descriptor_table,
9152 )
9153 .expect("prepared exec descriptor provenance no longer matches the guest table");
9154 }
9155}
9156
9157pub(crate) async fn record_host_input<G, T>(
9163 guest: &mut G,
9164 path: String,
9165 identity: HostFileIdentity,
9166 source: Option<String>,
9167 regular: bool,
9168 exec: bool,
9169 kernel_object: bool,
9170) where
9171 G: Guest<Detcore<T>>,
9172 T: RecordOrReplay,
9173{
9174 let syscall = guest.thread_state().stats.syscall_count;
9175 let (time, response) = send_and_update_time(
9176 guest,
9177 GlobalRequest::RecordHostInput {
9178 path,
9179 syscall,
9180 identity,
9181 source,
9182 regular,
9183 exec,
9184 kernel_object,
9185 },
9186 )
9187 .await;
9188 assert_eq!(
9189 (time, response),
9190 (None, GlobalResponse::RecordHostInput(()))
9191 );
9192}
9193
9194fn report_host_input_change(change: &detcore_model::host_input::HostInputChange) {
9199 let line = format!(
9200 "{} {change}\n",
9201 detcore_model::host_input::HOST_INPUT_CHANGED_SINCE_RECORDING_MARKER
9202 );
9203 let _ = if crate::nonwaiting_write::diagnostics_must_not_wait() {
9204 std::io::Write::write_all(&mut crate::util::DroppingStderr, line.as_bytes())
9205 } else {
9206 std::io::Write::write_all(&mut crate::util::RetryingStderr, line.as_bytes())
9207 };
9208}
9209
9210pub(crate) async fn record_host_mutation<G, T>(guest: &mut G, path: String)
9215where
9216 G: Guest<Detcore<T>>,
9217 T: RecordOrReplay,
9218{
9219 let (time, response) =
9220 send_and_update_time(guest, GlobalRequest::RecordHostMutation { path }).await;
9221 assert_eq!(
9222 (time, response),
9223 (None, GlobalResponse::RecordHostMutation(()))
9224 );
9225}
9226
9227pub(crate) async fn report_determinism_loss<G, T>(guest: &mut G, reason: String)
9231where
9232 G: Guest<Detcore<T>>,
9233 T: RecordOrReplay,
9234{
9235 let (time, response) =
9236 send_and_update_time(guest, GlobalRequest::RecordDeterminismLoss(reason)).await;
9237 assert_eq!(
9238 (time, response),
9239 (None, GlobalResponse::RecordDeterminismLoss(()))
9240 );
9241}
9242
9243pub async fn report_unsupported_syscall<G, T>(guest: &mut G, sysno: Sysno)
9245where
9246 G: Guest<Detcore<T>>,
9247 T: RecordOrReplay,
9248{
9249 let (_, response) = send_and_update_time(
9250 guest,
9251 GlobalRequest::ReportUnsupportedSyscall(sysno.to_string()),
9252 )
9253 .await;
9254 assert_eq!(response, GlobalResponse::ReportUnsupportedSyscall(()));
9255}
9256
9257pub(crate) fn signal_handler_not_installed_warning(signum: i32) -> String {
9260 format!(
9261 "HERMIT_SIGNAL_HANDLER_NOT_INSTALLED signum={signum} (SIGSTKFLT): the guest set a handler \
9262 or SIG_IGN for it and rt_sigaction reported success, but Hermit did not install it, \
9263 because the backend reserves SIGSTKFLT (Reverie's PMU preemption timer, SaBRe's \
9264 controlled exit). A SIGSTKFLT the guest receives takes the default action. Hermit \
9265 answers success on purpose so that programs that register it routinely, such as the \
9266 Go runtime, still start."
9267 )
9268}
9269
9270pub(crate) async fn announce_refused_signal_controls<G, T>(
9275 guest: &mut G,
9276 actions: u64,
9277 alternate_stack: bool,
9278) where
9279 G: Guest<Detcore<T>>,
9280 T: RecordOrReplay,
9281{
9282 let (_, response) = guest
9285 .send_rpc((
9286 DetTime::default(),
9287 MmId::initial(DetTid::from_raw(0)),
9288 GlobalRequest::AnnounceRefusedSignalControls(actions, alternate_stack),
9289 ))
9290 .await;
9291 debug_assert_eq!(response, GlobalResponse::AnnounceRefusedSignalControls(()));
9292}
9293
9294pub(crate) async fn host_diskstats<G, T>(guest: &mut G) -> Result<Vec<u8>, crate::Errno>
9301where
9302 G: Guest<Detcore<T>>,
9303 T: RecordOrReplay,
9304{
9305 let (_, response) = guest
9306 .send_rpc((
9307 DetTime::default(),
9308 MmId::initial(DetTid::from_raw(0)),
9309 GlobalRequest::HostDiskstats,
9310 ))
9311 .await;
9312 match response {
9313 GlobalResponse::HostDiskstats(answer) => answer.map_err(crate::Errno::new),
9314 other => panic!("unexpected response to HostDiskstats: {other:?}"),
9315 }
9316}
9317
9318pub(crate) async fn procfs_sequence<G, T>(
9322 guest: &mut G,
9323 id: OpenFileId,
9324) -> Option<crate::procfs::ProcfsSequence>
9325where
9326 G: Guest<Detcore<T>>,
9327 T: RecordOrReplay,
9328{
9329 let (_, response) = guest
9330 .send_rpc((
9331 DetTime::default(),
9332 MmId::initial(DetTid::from_raw(0)),
9333 GlobalRequest::ProcfsSequence(id),
9334 ))
9335 .await;
9336 match response {
9337 GlobalResponse::ProcfsSequence(sequence) => sequence,
9338 other => panic!("unexpected response to ProcfsSequence: {other:?}"),
9339 }
9340}
9341
9342pub(crate) async fn store_procfs_sequence<G, T>(
9345 guest: &mut G,
9346 id: OpenFileId,
9347 sequence: crate::procfs::ProcfsSequence,
9348) where
9349 G: Guest<Detcore<T>>,
9350 T: RecordOrReplay,
9351{
9352 let (_, response) = guest
9353 .send_rpc((
9354 DetTime::default(),
9355 MmId::initial(DetTid::from_raw(0)),
9356 GlobalRequest::StoreProcfsSequence(id, sequence),
9357 ))
9358 .await;
9359 match response {
9360 GlobalResponse::StoreProcfsSequence(()) => {}
9361 other => panic!("unexpected response to StoreProcfsSequence: {other:?}"),
9362 }
9363}
9364
9365pub(crate) async fn announce_signal_handler_not_installed<G, T>(guest: &mut G, signum: i32)
9368where
9369 G: Guest<Detcore<T>>,
9370 T: RecordOrReplay,
9371{
9372 let (_, response) = send_and_update_time(
9373 guest,
9374 GlobalRequest::AnnounceSignalHandlerNotInstalled(signum),
9375 )
9376 .await;
9377 assert_eq!(
9378 response,
9379 GlobalResponse::AnnounceSignalHandlerNotInstalled(())
9380 );
9381}
9382
9383pub(crate) async fn report_policy_refusal<G, T>(guest: &mut G, reason: String)
9386where
9387 G: Guest<Detcore<T>>,
9388 T: RecordOrReplay,
9389{
9390 let (_, response) = send_and_update_time(guest, GlobalRequest::PolicyRefusal(reason)).await;
9391 assert!(
9392 matches!(
9393 response,
9394 GlobalResponse::PolicyRefusal(()) | GlobalResponse::ThreadExited
9395 ),
9396 "unexpected response to PolicyRefusal: {response:?}"
9397 );
9398}
9399
9400pub(crate) async fn kernel_run_target<G, T>(
9404 guest: &mut G,
9405 target: DetTid,
9406) -> Option<crate::scheduler::KernelRunBlock>
9407where
9408 G: Guest<Detcore<T>>,
9409 T: RecordOrReplay,
9410{
9411 let (_, response) = send_and_update_time(guest, GlobalRequest::KernelRunTarget(target)).await;
9412 match response {
9413 GlobalResponse::KernelRunTarget(block) => block,
9414 GlobalResponse::ThreadExited => None,
9415 response => panic!("unexpected response to KernelRunTarget: {response:?}"),
9416 }
9417}
9418
9419pub(crate) async fn stopping_refusal<G, T>(guest: &mut G, reason: String)
9425where
9426 G: Guest<Detcore<T>>,
9427 T: RecordOrReplay,
9428{
9429 let (_, response) = send_and_update_time(guest, GlobalRequest::StoppingRefusal(reason)).await;
9430 assert!(
9431 matches!(
9432 response,
9433 GlobalResponse::PolicyRefusal(()) | GlobalResponse::ThreadExited
9434 ),
9435 "unexpected response to StoppingRefusal: {response:?}"
9436 );
9437}
9438
9439pub(crate) async fn set_saved_guest_sigmask<G, T>(guest: &mut G, mask: Option<u64>)
9444where
9445 G: Guest<Detcore<T>>,
9446 T: RecordOrReplay,
9447{
9448 let (_, response) =
9449 send_and_update_time(guest, GlobalRequest::SetSavedGuestSigmask(mask)).await;
9450 assert!(
9451 matches!(
9452 response,
9453 GlobalResponse::SetSavedGuestSigmask(()) | GlobalResponse::ThreadExited
9454 ),
9455 "unexpected response to SetSavedGuestSigmask: {response:?}"
9456 );
9457}
9458
9459pub(crate) async fn published_guest_sigmask<G, T>(guest: &mut G, thread: DetTid) -> Option<u64>
9464where
9465 G: Guest<Detcore<T>>,
9466 T: RecordOrReplay,
9467{
9468 let (_, response) =
9469 send_and_update_time(guest, GlobalRequest::PublishedGuestSigmask(thread)).await;
9470 match response {
9471 GlobalResponse::PublishedGuestSigmask(mask) => mask,
9472 GlobalResponse::ThreadExited => None,
9473 other => panic!("unexpected response to PublishedGuestSigmask: {other:?}"),
9474 }
9475}
9476
9477pub(crate) async fn status_guest_mask<G, T>(
9484 guest: &mut G,
9485 thread: DetTid,
9486) -> crate::scheduler::guest_mask_publication::StatusGuestMask
9487where
9488 G: Guest<Detcore<T>>,
9489 T: RecordOrReplay,
9490{
9491 use crate::scheduler::guest_mask_publication::PublishedGuestMask;
9492 use crate::scheduler::guest_mask_publication::StatusGuestMask;
9493 let (_, response) =
9494 send_and_update_time(guest, GlobalRequest::PublishedGuestSigmask(thread)).await;
9495 match response {
9496 GlobalResponse::GuestSigmaskForStatus(status) => status,
9497 GlobalResponse::PublishedGuestSigmask(mask) => StatusGuestMask {
9498 mask: mask.map(PublishedGuestMask::Known),
9499 ..Default::default()
9500 },
9501 GlobalResponse::ThreadExited => StatusGuestMask::default(),
9502 other => panic!("unexpected response to PublishedGuestSigmask: {other:?}"),
9503 }
9504}
9505
9506pub(crate) async fn publish_temporary_mask<G, T>(
9511 guest: &mut G,
9512 control: crate::scheduler::guest_mask_publication::TemporaryMaskControl,
9513) -> Option<crate::scheduler::guest_mask_publication::CommittedSendWindow>
9514where
9515 G: Guest<Detcore<T>>,
9516 T: RecordOrReplay,
9517{
9518 let (_, response) =
9519 send_and_update_time(guest, GlobalRequest::PublishTemporaryMask(control)).await;
9520 match response {
9521 GlobalResponse::PublishTemporaryMask(window) => window,
9522 GlobalResponse::ThreadExited => None,
9523 other => panic!("unexpected response to PublishTemporaryMask: {other:?}"),
9524 }
9525}
9526
9527pub(crate) async fn in_pid_namespace_init<G, T>(guest: &mut G, thread: DetTid) -> bool
9530where
9531 G: Guest<Detcore<T>>,
9532 T: RecordOrReplay,
9533{
9534 let (_, response) =
9535 send_and_update_time(guest, GlobalRequest::InPidNamespaceInit(thread)).await;
9536 match response {
9537 GlobalResponse::InPidNamespaceInit(init) => init,
9538 GlobalResponse::ThreadExited => false,
9539 other => panic!("unexpected response to InPidNamespaceInit: {other:?}"),
9540 }
9541}
9542
9543pub(crate) async fn virtual_inotify_cookies<G, T>(guest: &mut G, host: Vec<u32>) -> Vec<u32>
9548where
9549 G: Guest<Detcore<T>>,
9550 T: RecordOrReplay,
9551{
9552 let (_, response) =
9553 send_and_update_time(guest, GlobalRequest::VirtualInotifyCookies(host.clone())).await;
9554 match response {
9555 GlobalResponse::VirtualInotifyCookies(cookies) => cookies,
9556 GlobalResponse::ThreadExited => host,
9557 other => panic!("unexpected response to VirtualInotifyCookies: {other:?}"),
9558 }
9559}
9560
9561pub(crate) async fn set_sigtimedwait_set<G, T>(guest: &mut G, set: Option<u64>)
9564where
9565 G: Guest<Detcore<T>>,
9566 T: RecordOrReplay,
9567{
9568 let (_, response) = send_and_update_time(guest, GlobalRequest::SetSigtimedwaitSet(set)).await;
9569 assert!(
9570 matches!(
9571 response,
9572 GlobalResponse::SetSigtimedwaitSet(()) | GlobalResponse::ThreadExited
9573 ),
9574 "unexpected response to SetSigtimedwaitSet: {response:?}"
9575 );
9576}
9577
9578pub(crate) async fn record_guest_marker_signal<G, T>(guest: &mut G)
9581where
9582 G: Guest<Detcore<T>>,
9583 T: RecordOrReplay,
9584{
9585 let (_, response) = send_and_update_time(guest, GlobalRequest::RecordGuestMarkerSignal).await;
9586 assert!(
9587 matches!(response, GlobalResponse::RecordGuestMarkerSignal(())),
9588 "unexpected response to RecordGuestMarkerSignal: {response:?}"
9589 );
9590}
9591
9592pub(crate) async fn own_process_signal_inputs<G, T>(guest: &mut G) -> OwnProcessSignalInputs
9595where
9596 G: Guest<Detcore<T>>,
9597 T: RecordOrReplay,
9598{
9599 match send_and_update_time(guest, GlobalRequest::OwnProcessSignalInputs)
9600 .await
9601 .1
9602 {
9603 GlobalResponse::OwnProcessSignalInputs(inputs) => inputs,
9604 response => unreachable!("unexpected response to OwnProcessSignalInputs: {response:?}"),
9605 }
9606}
9607
9608pub(crate) async fn process_signal_receiver<G, T>(
9612 guest: &mut G,
9613 process: DetPid,
9614 named: DetTid,
9615 signo: i32,
9616) -> ProcessSignalReceiver
9617where
9618 G: Guest<Detcore<T>>,
9619 T: RecordOrReplay,
9620{
9621 match send_and_update_time(
9622 guest,
9623 GlobalRequest::ProcessSignalReceiver(process, named, signo),
9624 )
9625 .await
9626 .1
9627 {
9628 GlobalResponse::ProcessSignalReceiver(receiver) => receiver,
9629 response => unreachable!("unexpected response to ProcessSignalReceiver: {response:?}"),
9630 }
9631}
9632
9633pub(crate) async fn process_signal_sent<G, T>(guest: &mut G, process: DetPid)
9636where
9637 G: Guest<Detcore<T>>,
9638 T: RecordOrReplay,
9639{
9640 match send_and_update_time(guest, GlobalRequest::ProcessSignalSent(process))
9641 .await
9642 .1
9643 {
9644 GlobalResponse::ProcessSignalSent(()) => {}
9645 response => unreachable!("unexpected response to ProcessSignalSent: {response:?}"),
9646 }
9647}
9648
9649pub(crate) async fn own_process_signal_choice<G, T>(guest: &mut G, named: DetTid, signo: i32)
9652where
9653 G: Guest<Detcore<T>>,
9654 T: RecordOrReplay,
9655{
9656 match send_and_update_time(guest, GlobalRequest::OwnProcessSignalChoice(named, signo))
9657 .await
9658 .1
9659 {
9660 GlobalResponse::OwnProcessSignalChoice(()) => {}
9661 response => unreachable!("unexpected response to OwnProcessSignalChoice: {response:?}"),
9662 }
9663}
9664
9665pub(crate) async fn set_child_tid_address<G, T>(guest: &mut G, address: usize)
9666where
9667 G: Guest<Detcore<T>>,
9668 T: RecordOrReplay,
9669{
9670 let (_, response) =
9671 send_and_update_time(guest, GlobalRequest::SetChildTidAddress(address)).await;
9672 assert_eq!(response, GlobalResponse::SetChildTidAddress(()));
9673}
9674
9675pub(crate) async fn publish_signal_dispositions<G, T>(
9684 guest: &mut G,
9685 generation: u64,
9686 dispositions: Option<crate::syscalls::signal_shadow::CopiedDispositions>,
9687) -> Option<crate::syscalls::signal_shadow::PublicationAnswer>
9688where
9689 G: Guest<Detcore<T>>,
9690 T: RecordOrReplay,
9691{
9692 let state = guest.thread_state();
9693 let message = (
9694 state.thread_logical_time.clone(),
9695 state.mm_id,
9696 GlobalRequest::PublishSignalDispositions(generation, dispositions),
9697 );
9698 match guest.send_rpc(message).await.1 {
9699 GlobalResponse::PublishSignalDispositions(answer) => answer,
9700 _ => None,
9701 }
9702}
9703
9704pub(crate) async fn publish_signal_mask<G, T>(guest: &mut G, mask: Option<u64>) -> bool
9708where
9709 G: Guest<Detcore<T>>,
9710 T: RecordOrReplay,
9711{
9712 let state = guest.thread_state();
9713 let message = (
9714 state.thread_logical_time.clone(),
9715 state.mm_id,
9716 GlobalRequest::PublishSignalMask(mask),
9717 );
9718 matches!(
9719 guest.send_rpc(message).await.1,
9720 GlobalResponse::PublishSignalMask(true)
9721 )
9722}
9723
9724async fn publish_runtime_private_memory<G, T>(guest: &mut G)
9725where
9726 G: Guest<Detcore<T>>,
9727 T: RecordOrReplay,
9728{
9729 let Some(facts) = guest.runtime_private_memory_regions() else {
9730 return;
9731 };
9732 let state = guest.thread_state();
9733 let mm = state.mm_id;
9734 if state.published_runtime_private_memory.as_ref() == Some(&(mm, facts.clone())) {
9735 return;
9736 }
9737 let message = (
9738 state.thread_logical_time.clone(),
9739 mm,
9740 GlobalRequest::PublishRuntimePrivateMemory(facts.clone()),
9741 );
9742 if guest.send_rpc(message).await.1 == GlobalResponse::PublishRuntimePrivateMemory(true) {
9743 guest.thread_state_mut().published_runtime_private_memory = Some((mm, facts));
9744 }
9745}
9746
9747pub async fn send_and_update_time<G, T>(
9748 guest: &mut G,
9749 request: GlobalRequest,
9750) -> (Option<LogicalTime>, GlobalResponse)
9751where
9752 G: Guest<Detcore<T>>,
9753 T: RecordOrReplay,
9754{
9755 assert_eq!(
9761 DetTid::from_raw(guest.tid().as_raw()),
9762 guest.thread_state().dettid,
9763 "replacement image must reconnect before ordinary RPCs"
9764 );
9765 crate::syscalls::signal_shadow::publish_dispositions(guest).await;
9768 crate::syscalls::signal_shadow::publish_mask(guest).await;
9769 if !matches!(request, GlobalRequest::StartNewThread(..)) {
9770 publish_runtime_private_memory(guest).await;
9771 }
9772 let mytime = guest.thread_state().thread_logical_time.clone();
9773 let mm = guest.thread_state().mm_id;
9774 let continues_sigreturn = request_continues_sigreturn(&request);
9775 let starts_thread = matches!(request, GlobalRequest::StartNewThread(..));
9776 let mut resp = guest.send_rpc((mytime, mm, counted_request(request))).await;
9777 let lone_grant = if let GlobalResponse::WithLoneGrant(response) = resp.1 {
9778 resp.1 = *response;
9779 true
9780 } else {
9781 false
9782 };
9783 guest.thread_state_mut().lone_grant = lone_grant;
9784 if guest.thread_state().replaying_lone_slice_end.is_some() {
9785 if !lone_grant && matches!(resp.1, GlobalResponse::Granted(_)) {
9789 guest.thread_state_mut().lone_replay_unsound = true;
9790 }
9791 } else if !lone_grant {
9792 crate::stop_skipping_lone_slice_end(guest);
9796 }
9797 if let GlobalResponse::Granted(response) = resp.1 {
9800 resp.1 = *response;
9801 guest
9802 .thread_state_mut()
9803 .note_scheduler_handoff(continues_sigreturn);
9804 }
9805 if let GlobalResponse::WithSliceBoundaryOwner(owner, response) = resp.1 {
9806 resp.1 = *response;
9807 crate::slice_boundary::adopt_owner(guest.thread_state_mut(), owner, starts_thread)
9808 .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error));
9809 }
9810 let boundary_budget = match resp.1 {
9811 GlobalResponse::WithSliceBoundaryBudget(budget, response) => {
9812 resp.1 = *response;
9813 Some(budget)
9814 }
9815 _ => None,
9816 };
9817 let deadline = match resp.1 {
9818 GlobalResponse::WithDeadline(remaining, response) => {
9819 resp.1 = *response;
9820 Some(remaining)
9821 }
9822 _ => None,
9823 };
9824 if let GlobalResponse::WithSliceBoundaryStack(path, response) = resp.1 {
9825 resp.1 = *response;
9826 write_backtrace(guest, path.as_ref());
9827 }
9828 if let GlobalResponse::WithCallbackHistory(checkpoints, response) = resp.1 {
9829 resp.1 = *response;
9830 guest
9831 .thread_state_mut()
9832 .callback_history
9833 .install(checkpoints);
9834 } else if matches!(resp.1, GlobalResponse::StartNewThread(_)) {
9835 assert!(
9836 !guest.thread_state().callback_history.is_active(),
9837 "preserved callback activation matches coordinator startup"
9838 );
9839 }
9840 guest.thread_state_mut().last_grant_deadline = deadline;
9841 cap_timeslice_at_deadline(guest, deadline);
9842 if resp.1 == GlobalResponse::Retired {
9843 trace!(
9847 "[detcore, dtid {}] waiting for the kernel to end a SIGKILLed thread",
9848 guest.thread_state().dettid
9849 );
9850 return std::future::pending().await;
9851 }
9852 if resp.1 == GlobalResponse::ThreadExited {
9853 let dettid = guest.thread_state().dettid;
9854 guest.thread_state().cancel_exit_group_filing();
9860 if !guest
9861 .thread_state()
9862 .wait_for_exit_group_robust_walk(crate::tool_local::EXIT_GROUP_ROBUST_WALK_WAIT)
9863 {
9864 panic!(
9867 "[detcore, dtid {}] EXIT_GROUP_ROBUST_WALK_TIMEOUT: the exit_group's robust-list walk neither finished nor was cancelled within {:?}",
9868 dettid,
9869 crate::tool_local::EXIT_GROUP_ROBUST_WALK_WAIT,
9870 );
9871 }
9872 trace!(
9873 "[detcore, dtid {}] exiting after terminal scheduler cancellation",
9874 dettid
9875 );
9876 guest.retire_current_thread().await
9880 }
9881 if let Some(time) = resp.0 {
9884 guest
9885 .thread_state_mut()
9886 .thread_logical_time
9887 .advance_to(time);
9888 }
9889 if let Some(budget) = boundary_budget {
9890 let installed = if starts_thread {
9891 crate::slice_boundary::grant::stage_start(guest.thread_state_mut(), budget)
9892 } else {
9893 crate::slice_boundary::grant::install(guest.thread_state_mut(), budget)
9894 };
9895 installed.unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error));
9896 }
9897 resp
9898}
9899
9900#[derive(PartialEq, Debug, Eq, Clone, Serialize, Deserialize)]
9904pub enum ResumeStatus {
9905 Normal,
9906 Signaled(Option<Vec<SigWrapper>>),
9907 InGuestFatalAlarm,
9909 NothingElseCanRun,
9911}
9912
9913#[derive(PartialEq, Debug, Eq, Clone)]
9916enum SchedulerRpcResult<T> {
9917 Continue(T),
9918 ThreadExited,
9919}
9920
9921fn wake_exec_robust_waiters(sched: &mut Scheduler, wakes: &[(DetTid, FutexID)], at: &str) {
9927 if wakes.is_empty() {
9928 return;
9929 }
9930 let counts = sched.wake_futex_waiters_after_exit(wakes);
9931 for ((owner, futex), count) in wakes.iter().zip(counts) {
9932 info!(
9933 "[detcore, dtid {}] robust-list owner death woke {} waiter(s) on futex {:?} at {}",
9934 owner, count, futex, at,
9935 );
9936 }
9937}
9938
9939pub(crate) fn open_process_pidfd(pid: i32) -> std::io::Result<std::os::fd::OwnedFd> {
9942 let raw = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, 0) };
9944 if raw < 0 {
9945 return Err(std::io::Error::last_os_error());
9946 }
9947 Ok(unsafe { std::os::fd::OwnedFd::from_raw_fd(raw as libc::c_int) })
9949}
9950
9951fn sigkill_fence_for(
9976 scheduler: &Scheduler,
9977 named: DetTid,
9978 detpid: DetPid,
9979 process_directed: bool,
9980 open: impl Fn(i32) -> std::io::Result<std::os::fd::OwnedFd>,
9981) -> Result<Option<std::os::fd::OwnedFd>, crate::scheduler::sigkill_retirement::UnfencedSigkill> {
9982 let unfenced = |reason: String| crate::scheduler::sigkill_retirement::UnfencedSigkill {
9983 process: detpid,
9984 reason,
9985 };
9986 if process_directed && scheduler.process_exit_granted(detpid) {
9987 return Ok(None);
9988 }
9989 let Some(via) = scheduler.sigkill_victim_registered_thread(named, detpid, process_directed)
9990 else {
9991 return Err(unfenced(if process_directed {
9992 format!(
9993 "neither the thread it named (dettid {named}) nor any other thread of it has a registered host process"
9994 )
9995 } else {
9996 format!(
9997 "the thread it named (dettid {named}) has no registered host process, and a SIGKILL to that thread alone does not end its group"
9998 )
9999 }));
10000 };
10001 let host = scheduler
10002 .physical_process_of(via)
10003 .expect("a registered thread has a host process");
10004 match open(host) {
10005 Ok(pidfd) => Ok(Some(pidfd)),
10006 Err(error)
10007 if matches!(error.raw_os_error(), Some(libc::ESRCH | libc::EINVAL))
10008 && scheduler.registered_thread_has_exited(via) == Some(true) =>
10009 {
10010 Ok(None)
10011 }
10012 Err(error) => Err(unfenced(format!(
10013 "pidfd_open of its host process failed: {error}"
10014 ))),
10015 }
10016}
10017
10018fn fence_or_refuse(
10021 scheduler: &Scheduler,
10022 named: DetTid,
10023 detpid: DetPid,
10024 process_directed: bool,
10025) -> Option<(DetPid, std::os::fd::OwnedFd)> {
10026 match sigkill_fence_for(
10027 scheduler,
10028 named,
10029 detpid,
10030 process_directed,
10031 open_process_pidfd,
10032 ) {
10033 Ok(fence) => fence.map(|pidfd| (detpid, pidfd)),
10034 Err(refusal) => crate::scheduler::exit_on_scheduler_refusal(&refusal),
10035 }
10036}
10037
10038async fn wait_for_process_exit(pidfd: std::os::fd::OwnedFd, valve: Duration) -> Result<(), String> {
10041 let fd = tokio::io::unix::AsyncFd::with_interest(pidfd, tokio::io::Interest::READABLE)
10042 .map_err(|error| format!("its pidfd cannot be awaited: {error}"))?;
10043 match tokio::time::timeout(valve, fd.readable()).await {
10044 Ok(Ok(_)) => Ok(()),
10045 Ok(Err(error)) => Err(format!("waiting on its pidfd failed: {error}")),
10046 Err(_) => Err(format!("it was not gone within {valve:?}")),
10047 }
10048}
10049
10050fn request_continues_sigreturn(request: &GlobalRequest) -> bool {
10058 match request {
10059 GlobalRequest::WithForwardedCount(_, request) => request_continues_sigreturn(request),
10060 GlobalRequest::RequestResources(resources, _)
10061 | GlobalRequest::ParkedRequest(resources, ..) => resources.continues_sigreturn,
10062 _ => false,
10063 }
10064}
10065
10066fn registers_own_image(sender: DetTid, request: &GlobalRequest) -> bool {
10069 match request {
10070 GlobalRequest::WithForwardedCount(_, request) => registers_own_image(sender, request),
10071 GlobalRequest::CreateChildThread(child, ..) => *child == sender,
10072 GlobalRequest::CreateVforkChildThread(_, _, child, ..) => *child == sender,
10073 _ => false,
10074 }
10075}
10076
10077fn resumes_from_grant(request: &GlobalRequest) -> bool {
10078 match request {
10079 GlobalRequest::WithForwardedCount(_, request) => resumes_from_grant(request),
10080 GlobalRequest::RequestResources(..)
10081 | GlobalRequest::ParkedRequest(..)
10082 | GlobalRequest::ResumeParkedRequest { .. }
10083 | GlobalRequest::ResumeExec(..)
10084 | GlobalRequest::CreateChildThread(..)
10085 | GlobalRequest::CreateVforkChildThread(..)
10086 | GlobalRequest::StartNewThread(..)
10087 | GlobalRequest::FutexAction(..)
10088 | GlobalRequest::TraceSchedEvent(..)
10089 | GlobalRequest::RegisterAlarm(..)
10090 | GlobalRequest::AlarmRemaining(..)
10091 | GlobalRequest::ReconcileAlarm(..)
10092 | GlobalRequest::RegisterPosixTimer(..) => true,
10093 _ => false,
10094 }
10095}
10096
10097pub(crate) fn cap_timeslice_at_deadline<G, T>(guest: &mut G, deadline: Option<Duration>)
10105where
10106 G: Guest<Detcore<T>>,
10107 T: RecordOrReplay,
10108{
10109 let Some(remaining) = deadline else {
10110 return;
10111 };
10112 let clock_multiplier = guest.config().clock_multiplier.unwrap_or(1.0);
10113 let schedule_replay = guest
10114 .thread_state()
10115 .follows_replayed_schedule(guest.config());
10116 let min_slice = Duration::from_nanos(guest.config().scheduler_turn_charge().unwrap_or(0));
10119 guest.thread_state_mut().cap_timeslice_at_deadline(
10120 remaining,
10121 clock_multiplier,
10122 min_slice,
10123 schedule_replay,
10124 );
10125}
10126
10127pub(crate) fn publish_request_guest_mask<G, T>(guest: &G, r: &Resources) -> Option<u64>
10150where
10151 G: Guest<Detcore<T>>,
10152 T: RecordOrReplay,
10153{
10154 if !guest.config().in_guest_signal_delivery || r.blocked_signal_mask_unknown {
10155 return None;
10156 }
10157 if let Some(temporary) = r.blocked_signal_mask {
10158 return Some(temporary);
10159 }
10160 let own = guest.resume_signal_mask()?;
10161 let thread = guest.thread_state();
10162 if guest.config().backend.virtualizes_guest_sigalrm && thread.sigalrm_handled {
10163 let sigalrm = 1_u64 << (libc::SIGALRM - 1);
10164 return Some(if thread.sigalrm_blocked_published {
10165 own | sigalrm
10166 } else {
10167 own & !sigalrm
10168 });
10169 }
10170 Some(own)
10171}
10172
10173pub async fn resource_request<G, T>(guest: &mut G, mut r: Resources) -> ResumeStatus
10177where
10178 G: Guest<Detcore<T>>,
10179 T: RecordOrReplay,
10180{
10181 r.backend_runtime_bootstrap |=
10186 guest.thread_state().in_uncharged_bootstrap_syscall && guest.is_backend_runtime_bootstrap();
10187 r.passes_descriptors_out |= guest.thread_state().passed_descriptors_out_unreported;
10193 let carried_export = r.passes_descriptors_out;
10194 guest.thread_state_mut().pipe_handoff_due = None;
10197 if guest.config().sequentialize_threads {
10198 crate::reserved_input::invalidate(guest);
10202 if let Some(lease) = guest.signal_observation_lease() {
10203 if let Some(site) = guest.parked_signal_site() {
10204 return parked::capable_resource_request(
10205 guest,
10206 r,
10207 ControlCapability::PublishOnly { lease, site },
10208 )
10209 .await;
10210 }
10211 parked::terminate_protocol(guest, ProtocolFailure::Identity).await;
10212 }
10213 let dettid = guest.thread_state().dettid;
10214 let detpid = guest.thread_state().detpid.expect("detpid unset");
10215 trace!(
10216 "[detcore, dtid {}] BLOCKING on resource_request rpc... {:?}",
10217 &dettid, r
10218 );
10219 r.guest_signal_mask = publish_request_guest_mask(guest, &r);
10220 let resp =
10221 send_and_update_time(guest, GlobalRequest::RequestResources(r.clone(), detpid)).await;
10222 if carried_export {
10223 guest.thread_state_mut().passed_descriptors_out_unreported = false;
10227 guest.thread_state_mut().passed_descriptors_out_reported = true;
10228 }
10229 match resp.1 {
10230 GlobalResponse::RequestResources(x) => {
10231 use reverie::reserved_input::InputGrantKind;
10232 let pending = r.resources.keys().find_map(|resource| match resource {
10233 ResourceID::BlockingVfork(op) => Some(op.sequence),
10234 _ => None,
10235 });
10236 let background_or_terminal = r.resources.keys().any(|resource| {
10237 matches!(
10238 resource,
10239 ResourceID::BlockingExternalIO(_)
10240 | ResourceID::BlockingRtSigsuspend(_)
10241 | ResourceID::Exit { .. }
10242 | ResourceID::InGuestFatalAlarm
10243 )
10244 });
10245 if !background_or_terminal && !matches!(x, ResumeStatus::InGuestFatalAlarm) {
10246 crate::reserved_input::granted(
10247 guest,
10248 pending.map_or(InputGrantKind::Ordinary, |logical_operation| {
10249 InputGrantKind::PendingVfork { logical_operation }
10250 }),
10251 );
10252 }
10253 trace!(
10254 "[detcore, dtid {}] UNBLOCKED, acquired resources: {:?}",
10255 &dettid, r
10256 );
10257 x
10258 }
10259 _ => unreachable!(),
10260 }
10261 } else {
10262 ResumeStatus::Normal
10263 }
10264}
10265
10266pub async fn resource_release_all<G, T>(guest: &mut G)
10271where
10272 G: Guest<Detcore<T>>,
10273 T: RecordOrReplay,
10274{
10275 if guest.config().sequentialize_threads {
10276 let resp = send_and_update_time(guest, GlobalRequest::ReleaseAllResources).await;
10281 match resp.1 {
10282 GlobalResponse::ReleaseAllResources(x) => x,
10283 _ => unreachable!(),
10284 }
10285 }
10286}
10287
10288pub async fn thread_start_request<G, T>(
10295 cfg: &Config,
10296 guest: &mut G,
10297 detpid: DetPid,
10298) -> Option<ThreadHistory>
10299where
10300 G: Guest<Detcore<T>>,
10301 T: RecordOrReplay,
10302{
10303 let dettid = guest.thread_state().dettid;
10304 if cfg.sequentialize_threads {
10305 trace!("[detcore, dtid {}] new thread BLOCKING on rpc...", &dettid);
10306 let physical_ids = guest
10307 .thread_state()
10308 .physical_tid
10309 .map(|tid| (guest.pid().as_raw(), tid));
10310 let signal_identity = guest.signal_task_identity();
10311 guest.thread_state_mut().signal_task_identity = signal_identity;
10312 let resp = send_and_update_time(
10313 guest,
10314 GlobalRequest::StartNewThread(dettid, detpid, physical_ids, signal_identity),
10315 )
10316 .await;
10317 match resp.1 {
10318 GlobalResponse::StartNewThread(preempts) => {
10319 publish_runtime_private_memory(guest).await;
10322 crate::reserved_input::granted(
10323 guest,
10324 reverie::reserved_input::InputGrantKind::Ordinary,
10325 );
10326 trace!("[detcore, dtid {}] new thread UNBLOCKED (post-rpc)", dettid);
10327 preempts
10328 }
10329 _ => unreachable!(),
10330 }
10331 } else {
10332 None
10333 }
10334}
10335
10336pub(crate) fn finish_thread_start<G, T>(cfg: &Config, guest: &mut G, history: Option<ThreadHistory>)
10340where
10341 G: Guest<Detcore<T>>,
10342 T: RecordOrReplay,
10343{
10344 let reset = {
10345 let thread = guest.thread_state_mut();
10346 thread.preemption_points = history.map(|history| history.into_iter());
10347 crate::slice_boundary::grant::reset_start(thread, cfg)
10348 .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error))
10349 };
10350 let deadline = guest.thread_state_mut().last_grant_deadline.take();
10351 cap_timeslice_at_deadline(guest, deadline);
10352 crate::slice_boundary::grant::finish_start(guest.thread_state_mut(), reset)
10353 .unwrap_or_else(|error| crate::scheduler::exit_on_scheduler_refusal(&error));
10354}
10355
10356pub(crate) fn child_tid_clear_address(flags: CloneFlags, address: usize) -> usize {
10359 if flags.contains(CloneFlags::CLONE_CHILD_CLEARTID) {
10360 address
10361 } else {
10362 0
10363 }
10364}
10365
10366pub async fn create_child_thread<G, T>(
10372 guest: &mut G,
10373 child_dettid: DetTid,
10374 ctid: usize,
10375 flags: Option<CloneFlags>,
10376 exit_signal: libc::c_int,
10377 physical_ids: Option<(i32, i32)>,
10378) -> Option<MmId>
10379where
10380 G: Guest<Detcore<T>>,
10381 T: RecordOrReplay,
10382{
10383 let starting_priority = if guest.config().replay_preemptions_from.is_some() {
10385 None
10388 } else if guest.config().replay_schedule_from.is_some() {
10389 if child_dettid <= DetTid::from_raw(3) {
10391 Some(REPLAY_FOREGROUND_PRIORITY)
10392 } else {
10393 Some(REPLAY_DEFERRED_PRIORITY)
10394 }
10395 } else if guest.config().chaos {
10396 let entropy = guest
10397 .thread_state_mut()
10398 .chaos_prng_next_u64("child_priority");
10399 if guest.config().chaos_target_races {
10400 if entropy.is_multiple_of(2) {
10406 Some(FIRST_PRIORITY)
10407 } else {
10408 Some(LAST_PRIORITY)
10409 }
10410 } else {
10411 Some(entropy_to_priority(entropy))
10412 }
10413 } else {
10414 Some(DEFAULT_PRIORITY)
10415 };
10416
10417 let detpid = guest.thread_state().detpid.expect("detpid unset");
10418
10419 if flags.is_some() && guest.config().in_guest_signal_delivery {
10434 let current =
10435 publish_request_guest_mask(guest, &Resources::new(guest.thread_state().dettid));
10436 set_saved_guest_sigmask(guest, current).await;
10437 }
10438
10439 let resp = send_and_update_time(
10440 guest,
10441 GlobalRequest::CreateChildThread(
10442 child_dettid,
10443 detpid,
10444 ctid,
10445 flags,
10446 exit_signal,
10447 physical_ids,
10448 starting_priority,
10449 ),
10450 )
10451 .await;
10452 match resp.1 {
10453 GlobalResponse::CreateChildThread(x) => x,
10454 _ => unreachable!(),
10455 }
10456}
10457
10458pub async fn create_vfork_child_thread<G, T>(
10466 guest: &mut G,
10467 child_dettid: DetTid,
10468 vfork: crate::tool_local::PendingVfork,
10469) where
10470 G: Guest<Detcore<T>>,
10471 T: RecordOrReplay,
10472{
10473 let starting_priority = if guest.config().replay_preemptions_from.is_some() {
10474 None
10475 } else if guest.config().replay_schedule_from.is_some() {
10476 Some(if child_dettid <= DetTid::from_raw(3) {
10477 REPLAY_FOREGROUND_PRIORITY
10478 } else {
10479 REPLAY_DEFERRED_PRIORITY
10480 })
10481 } else if guest.config().chaos {
10482 Some(entropy_to_priority(vfork.child_priority_entropy.expect(
10483 "vfork child priority entropy missing in chaos mode",
10484 )))
10485 } else {
10486 Some(DEFAULT_PRIORITY - 1)
10492 };
10493
10494 let resp = send_and_update_time(
10495 guest,
10496 GlobalRequest::CreateVforkChildThread(
10497 vfork.parent_dettid,
10498 vfork.parent_detpid,
10499 child_dettid,
10500 vfork.child_tid_addr,
10501 vfork.flags,
10502 vfork.exit_signal,
10503 starting_priority,
10504 ),
10505 )
10506 .await;
10507 match resp.1 {
10508 GlobalResponse::CreateChildThread(_) => (),
10509 _ => unreachable!(),
10510 }
10511}
10512
10513fn id_awaits_new_registration(sched: &Scheduler, dtid: DetTid) -> bool {
10523 sched.thread_was_registered(dtid) && !sched.next_turns.contains_key(&dtid)
10524}
10525
10526#[cfg(test)]
10527mod reserved_input_registration_tests {
10528 use reverie::GlobalTool;
10529 use reverie::reserved_input::ReservedInputControl;
10530
10531 use super::GlobalState;
10532 use crate::Config;
10533 use crate::types::DetTid;
10534 use crate::types::MmId;
10535
10536 #[test]
10537 fn input_registration_binds_explicit_tool_identity_without_a_physical_ids_rpc() {
10538 let mut global = GlobalState::for_tests(&Config::default());
10539 let control = ReservedInputControl::new_backend().unwrap();
10540 global
10541 .install_reserved_input_control(control.clone())
10542 .unwrap();
10543 let native = control.backend_register_root(123, 123).unwrap();
10544 assert!(global.on_reserved_input_task(native).is_err());
10545 let bound = control
10546 .backend_bind_tool_identity(native.task, 5000, 5000)
10547 .unwrap();
10548 global.on_reserved_input_task(bound).unwrap();
10549 crate::reserved_input::register_initial_history(&control, native.task).unwrap();
10552 let logical = DetTid::from_raw(5000);
10553 {
10554 let mut scheduler = global.sched.lock().unwrap();
10555 assert!(!scheduler.reserved_input_tasks.contains_key(&logical));
10556 scheduler.record_thread_mm(logical, MmId::initial(logical));
10557 assert_eq!(scheduler.reserved_input_tasks[&logical], native.task);
10558 assert_eq!(scheduler.physical_process_of(logical), None);
10559 assert!(
10560 !scheduler
10561 .reserved_input_tasks
10562 .contains_key(&DetTid::from_raw(123))
10563 );
10564 }
10565 let child = control
10566 .backend_register_child(native.task, 124, 123, Some(true), Some(true))
10567 .unwrap();
10568 let child = control
10569 .backend_bind_tool_identity(child.task, 5001, 5000)
10570 .unwrap();
10571 global.on_reserved_input_task(child).unwrap();
10572 {
10573 let mut scheduler = global.sched.lock().unwrap();
10574 let child_tid = DetTid::from_raw(5001);
10575 scheduler.record_thread_mm(child_tid, MmId::initial(logical));
10576 assert_eq!(scheduler.reserved_input_tasks[&child_tid], child.task);
10577 }
10578 let replacement = control
10579 .backend_exec_replacement(native.task, child.task)
10580 .unwrap();
10581 let replacement = control
10582 .backend_bind_tool_identity(replacement.task, 5000, 5000)
10583 .unwrap();
10584 global.on_reserved_input_task(replacement).unwrap();
10585 let scheduler = global.sched.lock().unwrap();
10586 assert_eq!(
10587 scheduler.reserved_input_tool_tasks[&logical],
10588 replacement.task
10589 );
10590 assert_eq!(scheduler.reserved_input_tasks[&logical], native.task);
10592 assert_eq!(
10593 scheduler.reserved_input_tasks[&DetTid::from_raw(5001)],
10594 child.task
10595 );
10596 }
10597
10598 #[test]
10599 fn input_registration_rejects_a_tool_identity_not_bound_by_the_native_owner() {
10600 let mut global = GlobalState::for_tests(&Config::default());
10601 let control = ReservedInputControl::new_backend().unwrap();
10602 global
10603 .install_reserved_input_control(control.clone())
10604 .unwrap();
10605 let native = control.backend_register_root(123, 123).unwrap();
10606 let mut forged = native;
10607 forged.tool_tid = Some(5000);
10608 forged.tool_pid = Some(5000);
10609 assert!(global.on_reserved_input_task(forged).is_err());
10610 assert!(
10611 global
10612 .sched
10613 .lock()
10614 .unwrap()
10615 .reserved_input_tool_tasks
10616 .is_empty()
10617 );
10618 }
10619}
10620
10621pub(crate) async fn deregister_thread<R>(
10626 threads_time: DetTime,
10627 cfg: &Config,
10628 reverie: &R,
10629 thread: ThreadDeregistration,
10630) where
10631 R: GlobalRPC<GlobalState>,
10633{
10634 if cfg.sequentialize_threads {
10635 let mm = thread.mm;
10636 let resp = reverie
10638 .send_rpc((threads_time, mm, {
10639 let request = counted_request(GlobalRequest::DeregisterThread(thread));
10641 crate::detlog::release_forwarded_count();
10642 request
10643 }))
10644 .await;
10645 match resp.1 {
10647 GlobalResponse::DeregisterThread(x) => x,
10648 _ => unreachable!(),
10649 }
10650 }
10651}
10652
10653pub(crate) async fn acknowledge_robust_list_exit_time<R>(
10658 threads_time: DetTime,
10659 reverie: &R,
10660 mm: MmId,
10661) -> bool
10662where
10663 R: GlobalRPC<GlobalState>,
10664{
10665 let response = reverie
10666 .send_rpc((threads_time, mm, GlobalRequest::RobustListWakes(Vec::new())))
10667 .await;
10668 match response.1 {
10669 GlobalResponse::RobustListWakes(counts) => {
10670 assert!(
10671 counts.is_empty(),
10672 "an empty exit-clock acknowledgement woke a waiter"
10673 );
10674 true
10675 }
10676 GlobalResponse::ThreadExited => false,
10677 _ => unreachable!(),
10678 }
10679}
10680
10681pub(crate) async fn robust_list_wakes_after_exit<R>(
10685 threads_time: DetTime,
10686 reverie: &R,
10687 mm: MmId,
10688 wakes: Vec<(DetTid, RobustListWake)>,
10689) -> Vec<u64>
10690where
10691 R: GlobalRPC<GlobalState>,
10692{
10693 if wakes.is_empty() {
10694 return Vec::new();
10695 }
10696 let response = reverie
10697 .send_rpc((
10698 threads_time,
10699 mm,
10700 GlobalRequest::RobustListWakes(
10701 wakes
10702 .into_iter()
10703 .map(|(owner, wake)| (owner, wake.futex))
10704 .collect(),
10705 ),
10706 ))
10707 .await;
10708 match response.1 {
10709 GlobalResponse::RobustListWakes(counts) => counts,
10710 _ => unreachable!(),
10711 }
10712}
10713
10714pub(crate) async fn robust_list_wakes_after_exec<G, T>(
10718 guest: &mut G,
10719 wakes: Vec<(DetTid, RobustListWake)>,
10720) -> Vec<u64>
10721where
10722 G: Guest<Detcore<T>>,
10723 T: RecordOrReplay,
10724{
10725 if wakes.is_empty() {
10726 return Vec::new();
10727 }
10728 let (_, response) = send_and_update_time(
10729 guest,
10730 GlobalRequest::RobustListWakes(
10731 wakes
10732 .into_iter()
10733 .map(|(owner, wake)| (owner, wake.futex))
10734 .collect(),
10735 ),
10736 )
10737 .await;
10738 match response {
10739 GlobalResponse::RobustListWakes(counts) => counts,
10740 _ => unreachable!(),
10741 }
10742}
10743
10744#[derive(PartialEq, Debug, Eq, Clone, Copy, Serialize, Deserialize)]
10746pub enum FutexAction {
10747 WaitRequest(Option<LogicalTime>, Option<FutexSignalWatch>),
10753 WaitFinished,
10755 WakeRequest(i32),
10757 WakeFinished(i32),
10759 RequeueRequest(FutexID, u32, u32),
10766 PiState(u32),
10771 PiLockWait(Option<LogicalTime>, Option<FutexSignalWatch>, DetTid),
10775 PiTake,
10778 PiHandOff,
10781}
10782
10783pub async fn futex_action<G, T>(
10786 guest: &mut G,
10787 futex_action: FutexAction,
10788 futexid: &FutexID,
10789 init_read: i32,
10790 mask: u32,
10791) -> Option<SchedValue>
10792where
10793 G: Guest<Detcore<T>>,
10794 T: RecordOrReplay,
10795{
10796 assert!(guest.config().sequentialize_threads);
10797 let dettid = guest.thread_state().dettid;
10798 let req = GlobalRequest::FutexAction(
10799 dettid,
10800 futex_action,
10801 *futexid,
10802 init_read,
10803 mask,
10804 guest.thread_state().passed_descriptors_out_unreported,
10805 );
10806 trace!(
10807 "BLOCKING on futex_action: sending request to scheduler: {:?}",
10808 req
10809 );
10810 let resp = send_and_update_time(guest, req.clone()).await;
10812 match resp.1 {
10813 GlobalResponse::FutexAction(answer) => {
10814 trace!("UNBLOCKING after futex_action. Request was: {:?}", req);
10815 answer
10816 }
10817 _ => unreachable!(),
10818 }
10819}
10820
10821pub async fn determinize_inode<G, T>(guest: &mut G, inode: RawInode) -> (DetInode, LogicalTime)
10829where
10830 G: Guest<Detcore<T>>,
10831 T: RecordOrReplay,
10832{
10833 determinize_inode_observing_mtime(
10834 guest,
10835 inode,
10836 None,
10837 ObservedMtime::Unobserved,
10838 InodeSighting::Descriptor,
10839 )
10840 .await
10841}
10842
10843pub async fn determinize_named_inode<G, T>(
10847 guest: &mut G,
10848 inode: RawInode,
10849 procfs: Option<ProcfsName>,
10850) -> (DetInode, LogicalTime)
10851where
10852 G: Guest<Detcore<T>>,
10853 T: RecordOrReplay,
10854{
10855 determinize_inode_observing_mtime(
10856 guest,
10857 inode,
10858 procfs,
10859 ObservedMtime::Unobserved,
10860 InodeSighting::Name,
10861 )
10862 .await
10863}
10864
10865pub async fn determinize_inode_observing_mtime<G, T>(
10871 guest: &mut G,
10872 inode: RawInode,
10873 procfs: Option<ProcfsName>,
10874 observed: ObservedMtime,
10875 sighting: InodeSighting,
10876) -> (DetInode, LogicalTime)
10877where
10878 G: Guest<Detcore<T>>,
10879 T: RecordOrReplay,
10880{
10881 let resp = send_and_update_time(
10882 guest,
10883 GlobalRequest::DeterminizeInode(inode, observed, sighting, procfs),
10884 )
10885 .await;
10886 match resp.1 {
10887 GlobalResponse::DeterminizeInode(x) => x,
10888 _ => unreachable!(),
10889 }
10890}
10891
10892pub async fn determinize_mapping_inodes<G, T>(guest: &mut G, inodes: Vec<RawInode>) -> Vec<DetInode>
10899where
10900 G: Guest<Detcore<T>>,
10901 T: RecordOrReplay,
10902{
10903 let resp = send_and_update_time(guest, GlobalRequest::DeterminizeMappingInodes(inodes)).await;
10904 match resp.1 {
10905 GlobalResponse::DeterminizeMappingInodes(x) => x,
10906 _ => unreachable!(),
10907 }
10908}
10909
10910pub async fn determinize_device<G, T>(guest: &mut G, raw_device: u64) -> u64
10914where
10915 G: Guest<Detcore<T>>,
10916 T: RecordOrReplay,
10917{
10918 let resp = send_and_update_time(guest, GlobalRequest::DeterminizeDevice(raw_device)).await;
10919 match resp.1 {
10920 GlobalResponse::DeterminizeDevice(x) => x,
10921 _ => unreachable!(),
10922 }
10923}
10924
10925pub async fn determinize_mount_id<G, T>(
10927 guest: &mut G,
10928 raw_mount_id: u64,
10929 mountinfo_order: Option<Vec<u64>>,
10930) -> Option<u64>
10931where
10932 G: Guest<Detcore<T>>,
10933 T: RecordOrReplay,
10934{
10935 let resp = send_and_update_time(
10936 guest,
10937 GlobalRequest::DeterminizeMountId(raw_mount_id, mountinfo_order),
10938 )
10939 .await;
10940 match resp.1 {
10941 GlobalResponse::DeterminizeMountId(value) => value,
10942 _ => unreachable!(),
10943 }
10944}
10945
10946pub async fn resolve_mountinfo_identities<G, T>(
10951 guest: &mut G,
10952 mountinfo_order: Vec<u64>,
10953 observation_order: Vec<u64>,
10954) -> Option<Vec<u64>>
10955where
10956 G: Guest<Detcore<T>>,
10957 T: RecordOrReplay,
10958{
10959 let resp = send_and_update_time(
10960 guest,
10961 GlobalRequest::ResolveMountinfoIds(mountinfo_order, observation_order),
10962 )
10963 .await;
10964 match resp.1 {
10965 GlobalResponse::ResolveMountinfoIds(value) => value,
10966 _ => unreachable!(),
10967 }
10968}
10969
10970pub async fn retire_inode<G, T>(guest: &mut G, inode: RawInode)
10972where
10973 G: Guest<Detcore<T>>,
10974 T: RecordOrReplay,
10975{
10976 let resp = send_and_update_time(guest, GlobalRequest::RetireInode(inode)).await;
10977 match resp.1 {
10978 GlobalResponse::RetireInode(x) => x,
10979 _ => unreachable!(),
10980 }
10981}
10982
10983pub async fn directory_snapshot_mark<G, T>(guest: &mut G) -> SnapshotMark
10986where
10987 G: Guest<Detcore<T>>,
10988 T: RecordOrReplay,
10989{
10990 let resp = send_and_update_time(guest, GlobalRequest::DirectorySnapshotMark).await;
10991 match resp.1 {
10992 GlobalResponse::DirectorySnapshotMark(mark) => mark,
10993 _ => unreachable!(),
10994 }
10995}
10996
10997pub async fn ready_channel<G, T>(
11000 guest: &mut G,
11001 channel: crate::pipe_wake::ChannelId,
11002 wants: crate::pipe_wake::Wants,
11003) -> crate::pipe_wake::ReadyOutcome
11004where
11005 G: Guest<Detcore<T>>,
11006 T: RecordOrReplay,
11007{
11008 let resp = send_and_update_time(guest, GlobalRequest::ReadyChannel(channel, wants)).await;
11009 match resp.1 {
11010 GlobalResponse::ReadyChannel(outcome) => outcome,
11011 _ => unreachable!(),
11012 }
11013}
11014
11015pub async fn hand_off_turn<G, T>(guest: &mut G) -> bool
11019where
11020 G: Guest<Detcore<T>>,
11021 T: RecordOrReplay,
11022{
11023 let resp = send_and_update_time(guest, GlobalRequest::HandOffTurn).await;
11024 match resp.1 {
11025 GlobalResponse::HandOffTurn(yields) => yields,
11026 _ => false,
11027 }
11028}
11029
11030pub async fn commit_fatal_delivery<G, T>(guest: &mut G, retire_others: bool) -> bool
11035where
11036 G: Guest<Detcore<T>>,
11037 T: RecordOrReplay,
11038{
11039 matches!(
11040 send_and_update_time(guest, GlobalRequest::CommitFatalDelivery(retire_others))
11041 .await
11042 .1,
11043 GlobalResponse::CommitFatalDelivery(true)
11044 )
11045}
11046
11047pub async fn determinize_listed_inode<G, T>(
11051 guest: &mut G,
11052 inode: RawInode,
11053 procfs: Option<ProcfsName>,
11054 mark: SnapshotMark,
11055) -> (DetInode, LogicalTime)
11056where
11057 G: Guest<Detcore<T>>,
11058 T: RecordOrReplay,
11059{
11060 determinize_inode_observing_mtime(
11061 guest,
11062 inode,
11063 procfs.map(|name| name.in_snapshot(mark.threads_counted)),
11064 ObservedMtime::Unobserved,
11065 InodeSighting::Listed(mark.retirements),
11066 )
11067 .await
11068}
11069
11070pub async fn forget_retired_inode<G, T>(guest: &mut G, inode: RawInode, procfs: Option<ProcfsName>)
11075where
11076 G: Guest<Detcore<T>>,
11077 T: RecordOrReplay,
11078{
11079 let resp = send_and_update_time(guest, GlobalRequest::ForgetRetiredInode(inode, procfs)).await;
11080 match resp.1 {
11081 GlobalResponse::ForgetRetiredInode(x) => x,
11082 _ => unreachable!(),
11083 }
11084}
11085
11086pub async fn touch_file<G, T>(guest: &mut G, inode: RawInode)
11089where
11090 G: Guest<Detcore<T>>,
11091 T: RecordOrReplay,
11092{
11093 let resp = send_and_update_time(guest, GlobalRequest::TouchFile(inode)).await;
11094 match resp.1 {
11095 GlobalResponse::TouchFile(x) => x,
11096 _ => unreachable!(),
11097 }
11098}
11099
11100pub async fn set_file_mtime<G, T>(guest: &mut G, inode: RawInode, mtime: LogicalTime)
11102where
11103 G: Guest<Detcore<T>>,
11104 T: RecordOrReplay,
11105{
11106 let resp = send_and_update_time(guest, GlobalRequest::SetFileMtime(inode, mtime)).await;
11107 match resp.1 {
11108 GlobalResponse::SetFileMtime(x) => x,
11109 _ => unreachable!(),
11110 }
11111}
11112
11113pub async fn global_time_lower_bound<G, T>(guest: &mut G) -> LogicalTime
11115where
11116 G: Guest<Detcore<T>>,
11117 T: RecordOrReplay,
11118{
11119 let resp = send_and_update_time(guest, GlobalRequest::GlobalTimeLowerBound).await;
11120 match resp.1 {
11121 GlobalResponse::GlobalTimeLowerBound(x) => x,
11122 _ => unreachable!(),
11123 }
11124}
11125
11126pub async fn thread_observe_time<G, T>(guest: &mut G) -> LogicalTime
11130where
11131 G: Guest<Detcore<T>>,
11132 T: RecordOrReplay,
11133{
11134 global_time_lower_bound(guest).await
11135}
11136
11137fn write_backtrace<G, T>(guest: &mut G, m_path: Option<&PathBuf>)
11139where
11140 G: Guest<Detcore<T>>,
11141 T: RecordOrReplay,
11142{
11143 if let Some(backtrace) = guest.backtrace() {
11144 if let Some(path) = m_path {
11145 let file = File::create(path).expect("Failed to open preemption stacktrace log file");
11146 serde_json::to_writer(file, &backtrace.force_pretty()).unwrap();
11147 } else {
11148 crate::util::capped_eprintln!("{}", backtrace.force_pretty());
11149 }
11150 } else {
11151 warn!("Could not read backtrace!");
11152 }
11153}
11154
11155#[derive(PartialEq, Debug, Eq, Clone, Serialize, Deserialize)]
11157pub struct TraceSchedEventResponse {
11158 print_stack_strace: MaybePrintStack,
11159 timeslice: Option<LogicalTime>,
11160 schedule_released: bool,
11163 schedule_over: bool,
11166}
11167
11168struct SchedEventForLog<'a> {
11169 event: &'a SchedEvent,
11170 command_bootstrap: bool,
11171}
11172
11173struct CommandBootstrapInstructionPointer(NonZeroUsize);
11174
11175impl std::fmt::Debug for CommandBootstrapInstructionPointer {
11176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11177 write!(f, "{}", crate::logdiff::host_addr(self.0.get()))
11178 }
11179}
11180
11181impl std::fmt::Debug for SchedEventForLog<'_> {
11182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11183 if !self.command_bootstrap {
11184 return std::fmt::Debug::fmt(self.event, f);
11185 }
11186 f.debug_struct("SchedEvent")
11187 .field("dettid", &self.event.dettid)
11188 .field("op", &self.event.op)
11189 .field("count", &self.event.count)
11190 .field(
11191 "start_rip",
11192 &self.event.start_rip.map(CommandBootstrapInstructionPointer),
11193 )
11194 .field(
11195 "end_rip",
11196 &self.event.end_rip.map(CommandBootstrapInstructionPointer),
11197 )
11198 .field("end_time", &self.event.end_time)
11199 .finish()
11200 }
11201}
11202
11203pub async fn trace_schedevent<G, T>(guest: &mut G, ev: SchedEvent, tag_end_rip: bool)
11210where
11211 G: Guest<Detcore<T>>,
11212 T: RecordOrReplay,
11213{
11214 trace_schedevent_with_access(guest, ev, tag_end_rip, None).await
11215}
11216
11217pub async fn trace_schedevent_with_access<G, T>(
11221 guest: &mut G,
11222 ev: SchedEvent,
11223 tag_end_rip: bool,
11224 access: Option<crate::access_trace::AccessPayload>,
11225) where
11226 G: Guest<Detcore<T>>,
11227 T: RecordOrReplay,
11228{
11229 assert!(guest.config().sequentialize_threads);
11230
11231 let ev = if tag_end_rip {
11233 let end_rip = if let Some(r) = ev.end_rip {
11234 r
11235 } else {
11236 let regs = match guest.thread_state().signal_register_projection {
11239 Some(projection) => projection,
11240 None => guest.regs().await,
11241 };
11242 NonZeroUsize::new(crate::arch::instruction_pointer(®s).try_into().unwrap()).unwrap()
11243 };
11244 SchedEvent {
11245 end_rip: Some(end_rip),
11246 ..ev
11247 }
11248 } else {
11249 ev
11250 };
11251
11252 if tracing::enabled!(tracing::Level::TRACE)
11253 && let Some(rip) = ev.end_rip
11254 {
11255 let rip_addr = AddrMut::<u16>::from_raw(rip.into()).unwrap();
11256 let rip_contents = guest.memory().read_value(rip_addr);
11260 trace!(
11261 "Tracing sched event, after which rip is {}, next two instruction bytes {:?}",
11262 rip, rip_contents
11263 );
11264 }
11265
11266 let detpid = guest.thread_state().detpid.expect("detpid unset");
11267 let command_bootstrap = guest.is_command_bootstrap();
11268 if crate::preemptions::callback_history::recording_enabled(guest.config())
11269 || guest.thread_state().callback_history.is_active()
11270 {
11271 guest.thread_state_mut().callback_history.observe_event(&ev);
11272 }
11273 let resp = send_and_update_time(
11274 guest,
11275 GlobalRequest::TraceSchedEvent(ev, detpid, command_bootstrap, access.map(Box::new)),
11276 )
11277 .await;
11278
11279 trace!("trace_schedevent result: {:?}", resp);
11280 match resp {
11281 (
11282 _,
11283 GlobalResponse::TraceSchedEvent(TraceSchedEventResponse {
11284 print_stack_strace,
11285 timeslice,
11286 schedule_released,
11287 schedule_over,
11288 }),
11289 ) => {
11290 if schedule_released {
11291 guest.thread_state_mut().callback_history.release();
11292 }
11293 if schedule_over {
11294 guest.thread_state_mut().schedule_replay_over = true;
11295 }
11296 if let Some(m_path) = print_stack_strace {
11297 trace!("[trace_schedevent] writing stacktrace via Reverie...");
11298 write_backtrace(guest, m_path.as_ref());
11299 }
11300
11301 if let Some(timeslice) = timeslice
11302 && guest.thread_state().past_global_first_execve
11303 {
11304 let end_of_timeslice =
11305 guest.thread_state().thread_logical_time.as_nanos() + timeslice;
11306 trace!(
11307 "[detcore][dettid {}] setting end_of_timeslice to {:?} as instructed by replayer",
11308 guest.thread_state().dettid,
11309 end_of_timeslice
11310 );
11311 guest.thread_state_mut().end_of_timeslice = Some(end_of_timeslice);
11312 if guest.config().max_timeslice.is_some() {
11313 guest.thread_state_mut().max_timeslice_end = Some(end_of_timeslice);
11314 }
11315 }
11316 }
11317 _ => {
11318 unreachable!()
11319 }
11320 }
11321}
11322
11323pub async fn register_alarm<G, T>(
11329 guest: &mut G,
11330 duration: LogicalTime,
11331 interval: LogicalTime,
11332 sig: Signal,
11333) -> (LogicalTime, LogicalTime)
11334where
11335 G: Guest<Detcore<T>>,
11336 T: RecordOrReplay,
11337{
11338 let dettid = guest.thread_state().dettid;
11339 let detpid = guest.thread_state().detpid.expect("detpid unset");
11340 let resp = send_and_update_time(
11341 guest,
11342 GlobalRequest::RegisterAlarm(detpid, dettid, duration, interval, SigWrapper::from(sig)),
11343 )
11344 .await;
11345 match resp.1 {
11346 GlobalResponse::RegisterAlarm(x) => x,
11347 _ => unreachable!(),
11348 }
11349}
11350
11351pub async fn alarm_remaining<G, T>(guest: &mut G) -> ItimerSnapshot
11355where
11356 G: Guest<Detcore<T>>,
11357 T: RecordOrReplay,
11358{
11359 let detpid = guest.thread_state().detpid.expect("detpid unset");
11360 let resp = send_and_update_time(guest, GlobalRequest::AlarmRemaining(detpid)).await;
11361 match resp.1 {
11362 GlobalResponse::AlarmRemaining(remaining) => remaining,
11363 _ => unreachable!(),
11364 }
11365}
11366
11367pub async fn note_sigalrm_event<G, T>(guest: &mut G, request: GlobalRequest)
11371where
11372 G: Guest<Detcore<T>>,
11373 T: RecordOrReplay,
11374{
11375 let resp = send_and_update_time(guest, request).await;
11376 match resp.1 {
11377 GlobalResponse::NoteGuestSigalrm => {}
11378 _ => unreachable!(),
11379 }
11380}
11381
11382pub async fn reconcile_alarm<G, T>(guest: &mut G)
11387where
11388 G: Guest<Detcore<T>>,
11389 T: RecordOrReplay,
11390{
11391 let detpid = guest.thread_state().detpid.expect("detpid unset");
11392 let resp = send_and_update_time(guest, GlobalRequest::ReconcileAlarm(detpid)).await;
11393 match resp.1 {
11394 GlobalResponse::ReconcileAlarm => {}
11395 _ => unreachable!(),
11396 }
11397}
11398
11399pub async fn register_posix_timer<G, T>(
11404 guest: &mut G,
11405 timer_id: i32,
11406 deadline: Option<LogicalTime>,
11407 interval: LogicalTime,
11408 notification: PosixTimerNotification,
11409) where
11410 G: Guest<Detcore<T>>,
11411 T: RecordOrReplay,
11412{
11413 let dettid = guest.thread_state().dettid;
11414 let detpid = guest.thread_state().detpid.expect("detpid unset");
11415 let resp = send_and_update_time(
11416 guest,
11417 GlobalRequest::RegisterPosixTimer(
11418 detpid,
11419 dettid,
11420 timer_id,
11421 deadline,
11422 interval,
11423 notification,
11424 ),
11425 )
11426 .await;
11427 match resp.1 {
11428 GlobalResponse::RegisterPosixTimer(()) => {}
11429 _ => unreachable!(),
11430 }
11431}
11432
11433pub async fn thread_is_live<G, T>(guest: &mut G, dettid: DetTid) -> bool
11443where
11444 G: Guest<Detcore<T>>,
11445 T: RecordOrReplay,
11446{
11447 let response = send_and_update_time(guest, GlobalRequest::ThreadIsLive(dettid)).await;
11448 match response.1 {
11449 GlobalResponse::ThreadIsLive(live) => live,
11450 _ => unreachable!(),
11451 }
11452}
11453
11454pub(crate) async fn next_guest_id<G, T>(guest: &mut G) -> Option<i32>
11458where
11459 G: Guest<Detcore<T>>,
11460 T: RecordOrReplay,
11461{
11462 let response = send_and_update_time(guest, GlobalRequest::NextGuestId).await;
11463 match response.1 {
11464 GlobalResponse::NextGuestId(id) => id,
11465 _ => unreachable!(),
11466 }
11467}
11468
11469pub(crate) async fn note_host_id_task<G, T>(guest: &mut G)
11472where
11473 G: Guest<Detcore<T>>,
11474 T: RecordOrReplay,
11475{
11476 let response = send_and_update_time(guest, GlobalRequest::GaveHostId).await;
11477 assert_eq!(response.1, GlobalResponse::GaveHostId(()));
11478}
11479
11480pub(crate) async fn thread_id_refused<G, T>(guest: &mut G, dettid: DetTid) -> bool
11494where
11495 G: Guest<Detcore<T>>,
11496 T: RecordOrReplay,
11497{
11498 let response = send_and_update_time(guest, GlobalRequest::ThreadIdRefused(dettid)).await;
11499 match response.1 {
11500 GlobalResponse::ThreadIdRefused(refused) => refused,
11501 _ => unreachable!(),
11502 }
11503}
11504
11505pub(crate) async fn names_a_retired_exited_leader<G, T>(guest: &mut G, named: DetTid) -> bool
11510where
11511 G: Guest<Detcore<T>>,
11512 T: RecordOrReplay,
11513{
11514 let response =
11515 send_and_update_time(guest, GlobalRequest::NamesARetiredExitedLeader(named)).await;
11516 match response.1 {
11517 GlobalResponse::NamesARetiredExitedLeader(retired) => retired,
11518 _ => unreachable!(),
11519 }
11520}
11521
11522pub(crate) async fn parent_death_signal<G, T>(guest: &mut G, arm: Option<i32>) -> i32
11528where
11529 G: Guest<Detcore<T>>,
11530 T: RecordOrReplay,
11531{
11532 let response = send_and_update_time(guest, GlobalRequest::ParentDeathSignal(arm)).await;
11533 match response.1 {
11534 GlobalResponse::ParentDeathSignal(signal) => signal,
11535 _ => unreachable!(),
11536 }
11537}
11538
11539pub(crate) async fn pending_real_time_expirations<G, T>(guest: &mut G, detpid: DetPid) -> u64
11545where
11546 G: Guest<Detcore<T>>,
11547 T: RecordOrReplay,
11548{
11549 let response =
11550 send_and_update_time(guest, GlobalRequest::PendingRealTimeExpirations(detpid)).await;
11551 match response.1 {
11552 GlobalResponse::PendingRealTimeExpirations(pending) => pending,
11553 GlobalResponse::ThreadExited => 0,
11554 other => unreachable!("unexpected response to PendingRealTimeExpirations: {other:?}"),
11555 }
11556}
11557
11558pub(crate) async fn record_handler_restore_mask<G, T>(guest: &mut G, detpid: DetPid, unblocked: u64)
11562where
11563 G: Guest<Detcore<T>>,
11564 T: RecordOrReplay,
11565{
11566 let response = send_and_update_time(
11567 guest,
11568 GlobalRequest::RecordHandlerRestoreMask(detpid, unblocked),
11569 )
11570 .await;
11571 assert!(
11572 matches!(
11573 response.1,
11574 GlobalResponse::RecordHandlerRestoreMask(()) | GlobalResponse::ThreadExited
11575 ),
11576 "unexpected response to RecordHandlerRestoreMask: {:?}",
11577 response.1
11578 );
11579}
11580
11581pub(crate) async fn register_signalfd<G, T>(guest: &mut G, detpid: DetPid, mask: u64)
11584where
11585 G: Guest<Detcore<T>>,
11586 T: RecordOrReplay,
11587{
11588 let response = send_and_update_time(guest, GlobalRequest::RegisterSignalfd(detpid, mask)).await;
11589 assert!(
11590 matches!(
11591 response.1,
11592 GlobalResponse::RegisterSignalfd(()) | GlobalResponse::ThreadExited
11593 ),
11594 "unexpected response to RegisterSignalfd: {:?}",
11595 response.1
11596 );
11597}
11598
11599pub(crate) async fn descriptors_replaced<G, T>(guest: &mut G, first: u32, last: u32)
11603where
11604 G: Guest<Detcore<T>>,
11605 T: RecordOrReplay,
11606{
11607 if !guest.config().recordreplay_modes {
11608 return;
11609 }
11610 let files = guest.thread_state().files_id();
11611 let response = send_and_update_time(
11612 guest,
11613 GlobalRequest::DescriptorsReplaced(files, first, last),
11614 )
11615 .await;
11616 assert!(
11617 matches!(
11618 response.1,
11619 GlobalResponse::DescriptorsReplaced(()) | GlobalResponse::ThreadExited
11620 ),
11621 "unexpected response to DescriptorsReplaced: {:?}",
11622 response.1
11623 );
11624}
11625
11626pub(crate) async fn discard_pending_real_time_expirations<G, T>(
11629 guest: &mut G,
11630 detpid: DetPid,
11631 signals: u64,
11632) where
11633 G: Guest<Detcore<T>>,
11634 T: RecordOrReplay,
11635{
11636 let response = send_and_update_time(
11637 guest,
11638 GlobalRequest::DiscardPendingRealTimeExpirations(detpid, signals),
11639 )
11640 .await;
11641 assert!(
11642 matches!(
11643 response.1,
11644 GlobalResponse::DiscardPendingRealTimeExpirations(()) | GlobalResponse::ThreadExited
11645 ),
11646 "unexpected response to DiscardPendingRealTimeExpirations: {:?}",
11647 response.1
11648 );
11649}
11650
11651pub async fn record_host_timed_signals<G, T>(
11656 guest: &mut G,
11657 scope: HostTimedSignalScope,
11658 signals: u64,
11659) -> u64
11660where
11661 G: Guest<Detcore<T>>,
11662 T: RecordOrReplay,
11663{
11664 let dettid = guest.thread_state().dettid;
11665 let response = send_and_update_time(
11666 guest,
11667 GlobalRequest::RecordHostTimedSignals(dettid, scope, signals),
11668 )
11669 .await;
11670 match response.1 {
11671 GlobalResponse::HostTimedSignals(signals) => signals,
11672 _ => unreachable!(),
11673 }
11674}
11675
11676pub async fn host_timed_signals<G, T>(guest: &mut G) -> u64
11680where
11681 G: Guest<Detcore<T>>,
11682 T: RecordOrReplay,
11683{
11684 let dettid = guest.thread_state().dettid;
11685 let response = send_and_update_time(guest, GlobalRequest::HostTimedSignals(dettid)).await;
11686 match response.1 {
11687 GlobalResponse::HostTimedSignals(signals) => signals,
11688 _ => unreachable!(),
11689 }
11690}
11691
11692pub(crate) async fn host_timed_and_private_sigchld<G, T>(guest: &mut G) -> (u64, u64)
11697where
11698 G: Guest<Detcore<T>>,
11699 T: RecordOrReplay,
11700{
11701 let dettid = guest.thread_state().dettid;
11702 let response =
11703 send_and_update_time(guest, GlobalRequest::HostTimedAndPrivateSigchld(dettid)).await;
11704 match response.1 {
11705 GlobalResponse::HostTimedAndPrivateSigchld(host_timed, private) => (host_timed, private),
11706 _ => unreachable!(),
11707 }
11708}
11709
11710pub(crate) async fn child_exit_sigchld<G, T>(
11714 guest: &mut G,
11715 control: crate::scheduler::child_exit_sigchld::ChildExitSigchldControl,
11716) -> crate::scheduler::child_exit_sigchld::ChildExitSigchldAnswer
11717where
11718 G: Guest<Detcore<T>>,
11719 T: RecordOrReplay,
11720{
11721 let response = send_and_update_time(guest, GlobalRequest::ChildExitSigchld(control)).await;
11722 match response.1 {
11723 GlobalResponse::ChildExitSigchld(answer) => answer,
11724 _ => unreachable!(),
11725 }
11726}
11727
11728pub(crate) async fn discarded_signal<G, T>(
11732 guest: &mut G,
11733 control: crate::scheduler::discarded_at_send::DiscardedSignalControl,
11734) -> crate::scheduler::discarded_at_send::DiscardedSignalAnswer
11735where
11736 G: Guest<Detcore<T>>,
11737 T: RecordOrReplay,
11738{
11739 let response = send_and_update_time(guest, GlobalRequest::DiscardedSignal(control)).await;
11740 match response.1 {
11741 GlobalResponse::DiscardedSignal(answer) => answer,
11742 _ => unreachable!(),
11743 }
11744}
11745
11746pub async fn sigalrm_refuses<G, T>(guest: &mut G, control: SigalrmControl) -> bool
11750where
11751 G: Guest<Detcore<T>>,
11752 T: RecordOrReplay,
11753{
11754 let response = send_and_update_time(guest, GlobalRequest::Sigalrm(control)).await;
11755 match response.1 {
11756 GlobalResponse::Sigalrm(refused) => refused,
11757 _ => unreachable!(),
11758 }
11759}
11760
11761pub async fn refuse_sigalrm<G, T>(
11764 guest: &mut G,
11765 control: SigalrmControl,
11766) -> Result<(), nix::errno::Errno>
11767where
11768 G: Guest<Detcore<T>>,
11769 T: RecordOrReplay,
11770{
11771 if sigalrm_refuses(guest, control).await {
11772 Err(nix::errno::Errno::EPERM)
11773 } else {
11774 Ok(())
11775 }
11776}
11777
11778pub async fn exact_child_wait_state<G, T>(guest: &mut G, child: DetPid) -> ExactChildWaitState
11780where
11781 G: Guest<Detcore<T>>,
11782 T: RecordOrReplay,
11783{
11784 let parent = guest.thread_state().detpid.expect("detpid unset");
11785 let response =
11786 send_and_update_time(guest, GlobalRequest::ExactChildWaitState(parent, child)).await;
11787 match response.1 {
11788 GlobalResponse::ExactChildWaitState(state) => state,
11789 _ => unreachable!(),
11790 }
11791}
11792
11793pub async fn await_exact_child_physical_exit<G, T>(
11795 guest: &mut G,
11796 child: DetPid,
11797) -> ExactChildWaitState
11798where
11799 G: Guest<Detcore<T>>,
11800 T: RecordOrReplay,
11801{
11802 let mut state = exact_child_wait_state(guest, child).await;
11803 if matches!(
11804 state,
11805 ExactChildWaitState::PhysicalExitPending | ExactChildWaitState::PhysicallyExited
11806 ) {
11807 let dettid = guest.thread_state().dettid;
11808 let mut resources = Resources::new(dettid);
11809 resources.insert(ResourceID::WaitPhysicalChild(child), Permission::R);
11810 resources.fyi("wait-child-physical-exit");
11811 let _ = resource_request(guest, resources).await;
11812 state = exact_child_wait_state(guest, child).await;
11813 }
11814 state
11815}
11816
11817pub async fn wait_for_child_lifecycle<G, T>(guest: &mut G, spec: ChildWaitSpec) -> ResumeStatus
11819where
11820 G: Guest<Detcore<T>>,
11821 T: RecordOrReplay,
11822{
11823 let dettid = guest.thread_state().dettid;
11824 let parent = guest.thread_state().detpid.expect("detpid unset");
11825 let mut resources = Resources::new(dettid);
11826 resources.insert(ResourceID::WaitChild { parent, spec }, Permission::R);
11827 resources.fyi("wait-child-lifecycle");
11828 resource_request(guest, resources).await
11829}
11830
11831pub async fn ready_child_wait<G, T>(guest: &mut G, spec: ChildWaitSpec) -> (Option<DetPid>, bool)
11832where
11833 G: Guest<Detcore<T>>,
11834 T: RecordOrReplay,
11835{
11836 let parent = guest.thread_state().detpid.expect("detpid unset");
11837 let response = send_and_update_time(guest, GlobalRequest::ReadyChildWait(parent, spec)).await;
11838 match response.1 {
11839 GlobalResponse::ReadyChildWait(snapshot) => snapshot,
11840 _ => unreachable!(),
11841 }
11842}
11843
11844pub async fn process_group<G, T>(guest: &mut G, process: DetPid) -> Option<DetPid>
11845where
11846 G: Guest<Detcore<T>>,
11847 T: RecordOrReplay,
11848{
11849 let response = send_and_update_time(guest, GlobalRequest::ProcessGroup(process)).await;
11850 match response.1 {
11851 GlobalResponse::ProcessGroup(group) => group,
11852 _ => unreachable!(),
11853 }
11854}
11855
11856pub async fn setsid_decision<G, T>(guest: &mut G, process: DetPid) -> Result<(), reverie::Errno>
11859where
11860 G: Guest<Detcore<T>>,
11861 T: RecordOrReplay,
11862{
11863 let response = send_and_update_time(guest, GlobalRequest::SetsidDecision(process)).await;
11864 match response.1 {
11865 GlobalResponse::SetsidDecision(decision) => decision.map_err(reverie::Errno::new),
11866 _ => unreachable!(),
11867 }
11868}
11869
11870pub async fn setpgid_decision<G, T>(
11873 guest: &mut G,
11874 caller: DetPid,
11875 pid: i32,
11876 pgid: i32,
11877) -> Result<(DetPid, DetPid), reverie::Errno>
11878where
11879 G: Guest<Detcore<T>>,
11880 T: RecordOrReplay,
11881{
11882 let response =
11883 send_and_update_time(guest, GlobalRequest::SetpgidDecision(caller, pid, pgid)).await;
11884 match response.1 {
11885 GlobalResponse::SetpgidDecision(decision) => decision.map_err(reverie::Errno::new),
11886 _ => unreachable!(),
11887 }
11888}
11889
11890pub async fn group_or_session_of<G, T>(guest: &mut G, pid: DetPid, session: bool) -> Option<DetPid>
11894where
11895 G: Guest<Detcore<T>>,
11896 T: RecordOrReplay,
11897{
11898 let response = send_and_update_time(guest, GlobalRequest::GroupOrSession(pid, session)).await;
11899 match response.1 {
11900 GlobalResponse::GroupOrSession(answer) => answer,
11901 _ => unreachable!(),
11902 }
11903}
11904
11905pub(crate) async fn process_image<G, T>(
11909 guest: &mut G,
11910 process: DetPid,
11911) -> Option<crate::exec_record::ExecRecord>
11912where
11913 G: Guest<Detcore<T>>,
11914 T: RecordOrReplay,
11915{
11916 match send_and_update_time(guest, GlobalRequest::ProcessImage(process))
11917 .await
11918 .1
11919 {
11920 GlobalResponse::ProcessImage(image) => image.map(|image| *image),
11921 _ => unreachable!(),
11922 }
11923}
11924
11925pub async fn set_process_group<G, T>(guest: &mut G, process: DetPid, group: DetPid) -> bool
11926where
11927 G: Guest<Detcore<T>>,
11928 T: RecordOrReplay,
11929{
11930 let response =
11931 send_and_update_time(guest, GlobalRequest::SetProcessGroup(process, group)).await;
11932 match response.1 {
11933 GlobalResponse::SetProcessGroup(updated) => updated,
11934 _ => unreachable!(),
11935 }
11936}
11937
11938pub async fn registered_process_of<G, T>(guest: &mut G, dettid: DetTid) -> Option<DetPid>
11941where
11942 G: Guest<Detcore<T>>,
11943 T: RecordOrReplay,
11944{
11945 let response = send_and_update_time(guest, GlobalRequest::RegisteredProcessOf(dettid)).await;
11946 match response.1 {
11947 GlobalResponse::RegisteredProcessOf(process) => process,
11948 _ => unreachable!(),
11949 }
11950}
11951
11952pub async fn reserve_sigkill<G, T>(
11956 guest: &mut G,
11957 target: DetTid,
11958) -> Option<crate::scheduler::sigkill_retirement::SigkillReservation>
11959where
11960 G: Guest<Detcore<T>>,
11961 T: RecordOrReplay,
11962{
11963 if !crate::scheduler::sigkill_retirement::retires_sigkill_at_the_kill_turn(
11965 &guest.config().backend,
11966 ) {
11967 return None;
11968 }
11969 let sender = guest.thread_state().dettid;
11970 let uid =
11972 crate::syscalls::real_uid(target.as_raw()).unwrap_or_else(|| unsafe { libc::getuid() });
11973 let response =
11974 send_and_update_time(guest, GlobalRequest::ReserveSigkill(sender, target, uid)).await;
11975 match response.1 {
11976 GlobalResponse::ReserveSigkill(token) => token,
11977 _ => unreachable!(),
11978 }
11979}
11980
11981pub async fn set_child_subreaper<G, T>(guest: &mut G, process: DetPid, on: bool)
11984where
11985 G: Guest<Detcore<T>>,
11986 T: RecordOrReplay,
11987{
11988 let response = send_and_update_time(guest, GlobalRequest::SetChildSubreaper(process, on)).await;
11989 match response.1 {
11990 GlobalResponse::SetChildSubreaper(()) => {}
11991 _ => unreachable!(),
11992 }
11993}
11994
11995pub async fn posix_timer_thread<G, T>(guest: &mut G, named: i32) -> PosixTimerThread
11998where
11999 G: Guest<Detcore<T>>,
12000 T: RecordOrReplay,
12001{
12002 let process = guest.thread_state().detpid.expect("detpid unset");
12003 let response =
12004 send_and_update_time(guest, GlobalRequest::PosixTimerThread(process, named)).await;
12005 match response.1 {
12006 GlobalResponse::PosixTimerThread(answer) => answer,
12007 _ => unreachable!(),
12008 }
12009}
12010
12011pub async fn forget_thread_timer_copies<G, T>(guest: &mut G, signo: i32)
12014where
12015 G: Guest<Detcore<T>>,
12016 T: RecordOrReplay,
12017{
12018 let process = guest.thread_state().detpid.expect("detpid unset");
12019 let response = send_and_update_time(
12020 guest,
12021 GlobalRequest::ForgetThreadTimerCopies(process, signo),
12022 )
12023 .await;
12024 match response.1 {
12025 GlobalResponse::ForgetThreadTimerCopies(()) => {}
12026 _ => unreachable!(),
12027 }
12028}
12029
12030pub async fn exec_timer_flush<G, T>(
12034 guest: &mut G,
12035) -> Result<crate::scheduler::ExecTimerFlush, String>
12036where
12037 G: Guest<Detcore<T>>,
12038 T: RecordOrReplay,
12039{
12040 let process = guest.thread_state().detpid.expect("detpid unset");
12041 let response = send_and_update_time(guest, GlobalRequest::ExecTimerFlush(process)).await;
12042 match response.1 {
12043 GlobalResponse::ExecTimerFlush(answer) => answer,
12044 _ => unreachable!(),
12045 }
12046}
12047
12048pub async fn retire_exited_leader<G, T>(guest: &mut G) -> bool
12052where
12053 G: Guest<Detcore<T>>,
12054 T: RecordOrReplay,
12055{
12056 let response = send_and_update_time(guest, GlobalRequest::RetireExitedLeader).await;
12057 match response.1 {
12058 GlobalResponse::RetireExitedLeader(retired) => retired,
12059 _ => unreachable!(),
12060 }
12061}
12062
12063pub async fn guest_pid<G, T>(
12067 guest: &mut G,
12068 pid: i32,
12069) -> crate::scheduler::pid_namespaces::GuestPidAnswer
12070where
12071 G: Guest<Detcore<T>>,
12072 T: RecordOrReplay,
12073{
12074 let caller = guest.thread_state().dettid;
12075 let response = send_and_update_time(guest, GlobalRequest::GuestPid(caller, pid)).await;
12076 match response.1 {
12077 GlobalResponse::GuestPid(answer) => answer,
12078 _ => unreachable!(),
12079 }
12080}
12081
12082pub async fn procfs_listing<G, T>(
12086 guest: &mut G,
12087 process: Option<DetPid>,
12088) -> crate::procfs::listing::ListingFacts
12089where
12090 G: Guest<Detcore<T>>,
12091 T: RecordOrReplay,
12092{
12093 let response = send_and_update_time(guest, GlobalRequest::ProcfsListing(process)).await;
12094 match response.1 {
12095 GlobalResponse::ProcfsListing(facts) => facts,
12096 _ => unreachable!(),
12097 }
12098}
12099
12100pub async fn procfs_task<G, T>(
12103 guest: &mut G,
12104 task: crate::procfs::BoundTask,
12105) -> crate::procfs::listing::ProcfsTaskAnswer
12106where
12107 G: Guest<Detcore<T>>,
12108 T: RecordOrReplay,
12109{
12110 let request = GlobalRequest::ProcfsTask(
12111 DetTid::from_raw(task.process),
12112 task.thread.map(DetTid::from_raw),
12113 );
12114 let response = send_and_update_time(guest, request).await;
12115 match response.1 {
12116 GlobalResponse::ProcfsTask(answer) => answer,
12117 _ => unreachable!(),
12118 }
12119}
12120
12121pub(crate) async fn task_stat_facts<G, T>(
12124 guest: &mut G,
12125 task: crate::procfs::BoundTask,
12126) -> crate::procfs::task_stat::TaskStatFacts
12127where
12128 G: Guest<Detcore<T>>,
12129 T: RecordOrReplay,
12130{
12131 let request = GlobalRequest::TaskStatFacts(
12132 DetTid::from_raw(task.process),
12133 task.thread.map(DetTid::from_raw),
12134 );
12135 match send_and_update_time(guest, request).await.1 {
12136 GlobalResponse::TaskStatFacts(facts) => facts,
12137 _ => unreachable!(),
12138 }
12139}
12140
12141pub(crate) async fn set_task_comm<G, T>(
12143 guest: &mut G,
12144 tid: DetTid,
12145 comm: crate::procfs::task_stat::TaskComm,
12146) where
12147 G: Guest<Detcore<T>>,
12148 T: RecordOrReplay,
12149{
12150 let response = send_and_update_time(guest, GlobalRequest::SetTaskComm(tid, comm))
12151 .await
12152 .1;
12153 assert_eq!(response, GlobalResponse::SetTaskComm(()));
12154}
12155
12156pub(crate) async fn record_external_parent<G, T>(guest: &mut G, process: DetPid, parent: i32)
12159where
12160 G: Guest<Detcore<T>>,
12161 T: RecordOrReplay,
12162{
12163 let response =
12164 send_and_update_time(guest, GlobalRequest::RecordExternalParent(process, parent))
12165 .await
12166 .1;
12167 assert_eq!(response, GlobalResponse::RecordExternalParent(()));
12168}
12169
12170pub(crate) async fn register_placeholder<G, T>(
12174 guest: &mut G,
12175 directory: crate::procfs::fd_directory::PlaceholderDirectory,
12176 open_file: OpenFileId,
12177) -> Option<u64>
12178where
12179 G: Guest<Detcore<T>>,
12180 T: RecordOrReplay,
12181{
12182 match send_and_update_time(
12183 guest,
12184 GlobalRequest::RegisterPlaceholder(directory, open_file),
12185 )
12186 .await
12187 .1
12188 {
12189 GlobalResponse::RegisterPlaceholder(tag) => tag,
12190 _ => unreachable!(),
12191 }
12192}
12193
12194pub(crate) async fn placeholder<G, T>(
12197 guest: &mut G,
12198 tag: u64,
12199) -> Option<(
12200 crate::procfs::fd_directory::PlaceholderDirectory,
12201 OpenFileId,
12202)>
12203where
12204 G: Guest<Detcore<T>>,
12205 T: RecordOrReplay,
12206{
12207 match send_and_update_time(guest, GlobalRequest::Placeholder(tag))
12208 .await
12209 .1
12210 {
12211 GlobalResponse::Placeholder(directory) => directory,
12212 _ => unreachable!(),
12213 }
12214}
12215
12216pub(crate) async fn placeholder_flock<G, T>(
12219 guest: &mut G,
12220 request: crate::procfs::locks::PlaceholderFlock,
12221) -> crate::procfs::locks::PlaceholderFlockOutcome
12222where
12223 G: Guest<Detcore<T>>,
12224 T: RecordOrReplay,
12225{
12226 match send_and_update_time(guest, GlobalRequest::PlaceholderFlock(request))
12227 .await
12228 .1
12229 {
12230 GlobalResponse::PlaceholderFlock(outcome) => outcome,
12231 _ => unreachable!(),
12232 }
12233}
12234
12235pub(crate) async fn placeholder_copies<G, T>(
12240 guest: &mut G,
12241 change: crate::procfs::locks::PlaceholderCopies,
12242) -> Result<(), OpenFileId>
12243where
12244 G: Guest<Detcore<T>>,
12245 T: RecordOrReplay,
12246{
12247 match send_and_update_time(guest, GlobalRequest::PlaceholderCopies(change))
12248 .await
12249 .1
12250 {
12251 GlobalResponse::PlaceholderCopies(result) => result,
12252 _ => unreachable!(),
12253 }
12254}
12255
12256pub(crate) async fn hold_table<G, T>(
12263 guest: &mut G,
12264 table: Arc<Mutex<crate::tool_local::FileMetadata>>,
12265) where
12266 G: Guest<Detcore<T>>,
12267 T: RecordOrReplay,
12268{
12269 let state = guest.thread_state();
12270 let request = (
12271 state.thread_logical_time.clone(),
12272 state.mm_id,
12273 GlobalRequest::HoldTable(HeldTable::new(table)),
12274 );
12275 match guest.send_rpc(request).await {
12276 (None, GlobalResponse::HoldTable(Ok(()))) => {}
12277 (None, GlobalResponse::HoldTable(Err(reason))) => {
12278 error!("[detcore] {reason}");
12279 report_policy_refusal(guest, reason).await;
12280 unrecoverable_shutdown(guest, detcore_model::HERMIT_POLICY_REFUSAL_EXIT).await
12281 }
12282 other => panic!("unexpected answer to a held table: {other:?}"),
12283 }
12284}
12285
12286pub(crate) async fn placeholder_write_hint<G, T>(
12291 guest: &mut G,
12292 key: crate::procfs::fd_directory::PlaceholderLockKey,
12293 hint: Option<u64>,
12294) -> u64
12295where
12296 G: Guest<Detcore<T>>,
12297 T: RecordOrReplay,
12298{
12299 match send_and_update_time(guest, GlobalRequest::PlaceholderWriteHint(key, hint))
12300 .await
12301 .1
12302 {
12303 GlobalResponse::PlaceholderWriteHint(hint) => hint,
12304 _ => unreachable!(),
12305 }
12306}
12307
12308pub(crate) async fn cpu_affinity<G, T>(guest: &mut G, tid: DetTid) -> crate::cpu_set::TaskAffinity
12311where
12312 G: Guest<Detcore<T>>,
12313 T: RecordOrReplay,
12314{
12315 match send_and_update_time(guest, GlobalRequest::CpuAffinity(tid))
12316 .await
12317 .1
12318 {
12319 GlobalResponse::CpuAffinity(affinity) => affinity,
12320 _ => unreachable!(),
12321 }
12322}
12323
12324pub(crate) async fn set_cpu_affinity<G, T>(
12327 guest: &mut G,
12328 tid: DetTid,
12329 cpus: crate::cpu_set::CpuSet,
12330) -> crate::cpu_set::TaskAffinity
12331where
12332 G: Guest<Detcore<T>>,
12333 T: RecordOrReplay,
12334{
12335 match send_and_update_time(guest, GlobalRequest::SetCpuAffinity(tid, cpus))
12336 .await
12337 .1
12338 {
12339 GlobalResponse::CpuAffinity(affinity) => affinity,
12340 _ => unreachable!(),
12341 }
12342}
12343
12344pub(crate) async fn set_rss_limit<G, T>(guest: &mut G, process: DetPid, limit: u64)
12347where
12348 G: Guest<Detcore<T>>,
12349 T: RecordOrReplay,
12350{
12351 let response = send_and_update_time(guest, GlobalRequest::SetRssLimit(process, limit))
12352 .await
12353 .1;
12354 assert_eq!(response, GlobalResponse::SetRssLimit(()));
12355}
12356
12357pub(crate) async fn record_process_auxv<G, T>(
12362 guest: &mut G,
12363 bytes: Vec<u8>,
12364 cmdline: Option<crate::procfs::CmdlineArea>,
12365) where
12366 G: Guest<Detcore<T>>,
12367 T: RecordOrReplay,
12368{
12369 let response = send_and_update_time(guest, GlobalRequest::RecordProcessAuxv(bytes, cmdline))
12370 .await
12371 .1;
12372 assert_eq!(response, GlobalResponse::RecordProcessAuxv(()));
12373}
12374
12375pub(crate) async fn process_cmdline<G, T>(
12379 guest: &mut G,
12380 process: DetPid,
12381) -> (DetPid, Option<crate::procfs::CmdlineArea>)
12382where
12383 G: Guest<Detcore<T>>,
12384 T: RecordOrReplay,
12385{
12386 match send_and_update_time(guest, GlobalRequest::ProcessCmdline(process))
12387 .await
12388 .1
12389 {
12390 GlobalResponse::ProcessCmdline(process, area) => (process, area),
12391 _ => unreachable!(),
12392 }
12393}
12394
12395pub(crate) async fn process_auxv<G, T>(guest: &mut G, process: DetPid) -> Option<ProcessAuxv>
12399where
12400 G: Guest<Detcore<T>>,
12401 T: RecordOrReplay,
12402{
12403 match send_and_update_time(guest, GlobalRequest::ProcessAuxv(process))
12404 .await
12405 .1
12406 {
12407 GlobalResponse::ProcessAuxv(answer) => answer,
12408 _ => unreachable!(),
12409 }
12410}
12411
12412pub(crate) async fn mark_changed_root<G, T>(guest: &mut G)
12415where
12416 G: Guest<Detcore<T>>,
12417 T: RecordOrReplay,
12418{
12419 let response = send_and_update_time(guest, GlobalRequest::MarkChangedRoot)
12420 .await
12421 .1;
12422 assert_eq!(response, GlobalResponse::MarkChangedRoot(()));
12423}
12424
12425pub(crate) async fn root_changed<G, T>(guest: &mut G, task: DetPid) -> bool
12428where
12429 G: Guest<Detcore<T>>,
12430 T: RecordOrReplay,
12431{
12432 match send_and_update_time(guest, GlobalRequest::RootChanged(task))
12433 .await
12434 .1
12435 {
12436 GlobalResponse::RootChanged(changed) => changed,
12437 _ => unreachable!(),
12438 }
12439}
12440
12441pub(crate) async fn note_description_locks<G, T>(
12447 guest: &mut G,
12448 open_file: OpenFileId,
12449 locks: Result<Vec<crate::procfs::locks::HeldLock>, String>,
12450 slots: u32,
12451) where
12452 G: Guest<Detcore<T>>,
12453 T: RecordOrReplay,
12454{
12455 let response = send_and_update_time(
12456 guest,
12457 GlobalRequest::DescriptionLocks(open_file, locks, slots),
12458 )
12459 .await
12460 .1;
12461 assert_eq!(response, GlobalResponse::DescriptionLocks(()));
12462}
12463
12464pub(crate) async fn note_description_locks_taken<G, T>(
12470 guest: &mut G,
12471 open_file: OpenFileId,
12472 locks: Vec<crate::procfs::locks::HeldLock>,
12473 taken: Vec<crate::procfs::locks::HeldLock>,
12474 slots: u32,
12475 identity: crate::procfs::locks::LedgerIdentity,
12476) where
12477 G: Guest<Detcore<T>>,
12478 T: RecordOrReplay,
12479{
12480 let response = send_and_update_time(
12481 guest,
12482 GlobalRequest::DescriptionLocksTaken(open_file, locks, taken, slots, identity),
12483 )
12484 .await
12485 .1;
12486 assert_eq!(response, GlobalResponse::DescriptionLocksTaken(()));
12487}
12488
12489pub(crate) async fn note_second_identity_locks<G, T>(
12494 guest: &mut G,
12495 open_file: OpenFileId,
12496 locks: Vec<crate::procfs::locks::HeldLock>,
12497 slots: u32,
12498) where
12499 G: Guest<Detcore<T>>,
12500 T: RecordOrReplay,
12501{
12502 let response = send_and_update_time(
12503 guest,
12504 GlobalRequest::SecondIdentityLocks(open_file, locks, slots),
12505 )
12506 .await
12507 .1;
12508 assert_eq!(response, GlobalResponse::SecondIdentityLocks(()));
12509}
12510
12511pub(crate) async fn lock_owners<G, T>(
12517 guest: &mut G,
12518 open_file: OpenFileId,
12519 locks: Vec<crate::procfs::locks::HeldLock>,
12520) -> Result<Vec<i64>, String>
12521where
12522 G: Guest<Detcore<T>>,
12523 T: RecordOrReplay,
12524{
12525 match send_and_update_time(guest, GlobalRequest::LockOwners(open_file, locks))
12526 .await
12527 .1
12528 {
12529 GlobalResponse::LockOwners(owners) => owners,
12530 _ => unreachable!(),
12531 }
12532}
12533
12534pub(crate) async fn ledger_lock_rows<G, T>(
12538 guest: &mut G,
12539) -> Result<Vec<(crate::procfs::locks::HeldLock, i64)>, String>
12540where
12541 G: Guest<Detcore<T>>,
12542 T: RecordOrReplay,
12543{
12544 match send_and_update_time(guest, GlobalRequest::LedgerLockRows)
12545 .await
12546 .1
12547 {
12548 GlobalResponse::LedgerLockRows(rows) => rows,
12549 _ => unreachable!(),
12550 }
12551}
12552
12553pub(crate) async fn note_table_released<G, T>(guest: &mut G, open_file: OpenFileId, remaining: u32)
12556where
12557 G: Guest<Detcore<T>>,
12558 T: RecordOrReplay,
12559{
12560 let response = send_and_update_time(guest, GlobalRequest::TableReleased(open_file, remaining))
12561 .await
12562 .1;
12563 assert_eq!(response, GlobalResponse::TableReleased(()));
12564}
12565
12566pub(crate) async fn note_table_exec<G, T>(guest: &mut G, surviving: Vec<(OpenFileId, u32)>)
12570where
12571 G: Guest<Detcore<T>>,
12572 T: RecordOrReplay,
12573{
12574 let response = send_and_update_time(guest, GlobalRequest::TableExec(surviving))
12575 .await
12576 .1;
12577 assert_eq!(response, GlobalResponse::TableExec(()));
12578}
12579
12580pub(crate) async fn note_aliased_inode<G, T>(guest: &mut G, inode: Option<String>)
12584where
12585 G: Guest<Detcore<T>>,
12586 T: RecordOrReplay,
12587{
12588 let response = send_and_update_time(guest, GlobalRequest::AliasedInode(inode))
12589 .await
12590 .1;
12591 assert_eq!(response, GlobalResponse::AliasedInode(()));
12592}
12593
12594pub(crate) async fn note_lock_ledger_refused<G, T>(guest: &mut G, reason: String)
12597where
12598 G: Guest<Detcore<T>>,
12599 T: RecordOrReplay,
12600{
12601 let response = send_and_update_time(guest, GlobalRequest::LockLedgerRefused(reason))
12602 .await
12603 .1;
12604 assert_eq!(response, GlobalResponse::LockLedgerRefused(()));
12605}
12606
12607pub(crate) async fn note_record_locks_held_as_ofd<G, T>(guest: &mut G, objects: Option<Vec<String>>)
12611where
12612 G: Guest<Detcore<T>>,
12613 T: RecordOrReplay,
12614{
12615 let response = send_and_update_time(guest, GlobalRequest::RecordLocksHeldAsOfd(objects))
12616 .await
12617 .1;
12618 assert_eq!(response, GlobalResponse::RecordLocksHeldAsOfd(()));
12619}
12620
12621pub(crate) async fn guest_lock_rows<G, T>(
12625 guest: &mut G,
12626 rows: Vec<crate::procfs::locks::LockRowFacts>,
12627) -> Result<Vec<bool>, String>
12628where
12629 G: Guest<Detcore<T>>,
12630 T: RecordOrReplay,
12631{
12632 match send_and_update_time(guest, GlobalRequest::GuestLockRows(rows))
12633 .await
12634 .1
12635 {
12636 GlobalResponse::GuestLockRows(kept) => kept,
12637 _ => unreachable!(),
12638 }
12639}
12640
12641pub(crate) async fn auxv_from_kernel<G, T>(
12648 guest: &mut G,
12649 why: AuxvFromKernel,
12650 cmdline: Option<crate::procfs::CmdlineArea>,
12651) -> bool
12652where
12653 G: Guest<Detcore<T>>,
12654 T: RecordOrReplay,
12655{
12656 match send_and_update_time(guest, GlobalRequest::AuxvFromKernel(why, cmdline))
12657 .await
12658 .1
12659 {
12660 GlobalResponse::AuxvFromKernel(first) => first,
12661 _ => unreachable!(),
12662 }
12663}
12664
12665pub async fn rendered_file_state<G, T>(
12668 guest: &mut G,
12669 inode: u64,
12670 change: crate::procfs::listing::RenderedFileChange,
12671) -> crate::procfs::listing::RenderedFileState
12672where
12673 G: Guest<Detcore<T>>,
12674 T: RecordOrReplay,
12675{
12676 let response =
12677 send_and_update_time(guest, GlobalRequest::RenderedFileState(inode, change)).await;
12678 match response.1 {
12679 GlobalResponse::RenderedFileState(state) => state,
12680 _ => unreachable!(),
12681 }
12682}
12683
12684pub(crate) async fn user_namespace_relation<G, T>(
12689 guest: &mut G,
12690 task: DetTid,
12691) -> (
12692 crate::scheduler::user_namespaces::UserNamespaceRelation,
12693 bool,
12694)
12695where
12696 G: Guest<Detcore<T>>,
12697 T: RecordOrReplay,
12698{
12699 let reader = guest.thread_state().dettid;
12700 let response =
12701 send_and_update_time(guest, GlobalRequest::UserNamespaceRelation(reader, task)).await;
12702 match response.1 {
12703 GlobalResponse::UserNamespaceRelation(relation, owner_may_differ) => {
12704 (relation, owner_may_differ)
12705 }
12706 _ => unreachable!(),
12707 }
12708}
12709
12710pub(crate) async fn note_user_namespace_owner_may_differ<G, T>(guest: &mut G, process: DetTid)
12715where
12716 G: Guest<Detcore<T>>,
12717 T: RecordOrReplay,
12718{
12719 let response =
12720 send_and_update_time(guest, GlobalRequest::UserNamespaceOwnerMayDiffer(process)).await;
12721 match response.1 {
12722 GlobalResponse::UserNamespaceOwnerMayDiffer(()) => {}
12723 _ => unreachable!(),
12724 }
12725}
12726
12727pub async fn procfs_ids<G, T>(guest: &mut G, to_kernel: bool, ids: Vec<i32>) -> Vec<Option<i32>>
12731where
12732 G: Guest<Detcore<T>>,
12733 T: RecordOrReplay,
12734{
12735 let response = send_and_update_time(guest, GlobalRequest::ProcfsIds(to_kernel, ids)).await;
12736 match response.1 {
12737 GlobalResponse::ProcfsIds(ids) => ids,
12738 _ => unreachable!(),
12739 }
12740}
12741
12742pub async fn local_pid<G, T>(guest: &mut G, task: DetTid) -> Result<Option<i32>, String>
12746where
12747 G: Guest<Detcore<T>>,
12748 T: RecordOrReplay,
12749{
12750 let caller = guest.thread_state().dettid;
12751 let response = send_and_update_time(guest, GlobalRequest::LocalPid(caller, task)).await;
12752 match response.1 {
12753 GlobalResponse::LocalPid(answer) => answer,
12754 _ => unreachable!(),
12755 }
12756}
12757
12758pub async fn signal_target_process<G, T>(guest: &mut G, named: DetPid) -> Option<DetPid>
12762where
12763 G: Guest<Detcore<T>>,
12764 T: RecordOrReplay,
12765{
12766 let response = send_and_update_time(guest, GlobalRequest::SignalTargetProcess(named)).await;
12767 match response.1 {
12768 GlobalResponse::SignalTargetProcess(process) => process,
12769 _ => unreachable!(),
12770 }
12771}
12772
12773pub async fn set_sigchld_disposition<G, T>(
12776 guest: &mut G,
12777 process: DetPid,
12778 ignored: bool,
12779 nocldwait: bool,
12780) where
12781 G: Guest<Detcore<T>>,
12782 T: RecordOrReplay,
12783{
12784 let response = send_and_update_time(
12785 guest,
12786 GlobalRequest::SetSigchldDisposition(process, ignored, nocldwait),
12787 )
12788 .await;
12789 match response.1 {
12790 GlobalResponse::SetSigchldDisposition(()) => {}
12791 _ => unreachable!(),
12792 }
12793}
12794
12795pub async fn reserve_sigkill_group<G, T>(
12799 guest: &mut G,
12800 pgid: DetPid,
12801) -> Option<crate::scheduler::sigkill_retirement::SigkillReservation>
12802where
12803 G: Guest<Detcore<T>>,
12804 T: RecordOrReplay,
12805{
12806 if !crate::scheduler::sigkill_retirement::retires_sigkill_at_the_kill_turn(
12808 &guest.config().backend,
12809 ) {
12810 return None;
12811 }
12812 let sender = guest.thread_state().dettid;
12813 let uid = crate::syscalls::real_uid(guest.tid().as_raw())
12814 .unwrap_or_else(|| unsafe { libc::getuid() });
12815 let response =
12816 send_and_update_time(guest, GlobalRequest::ReserveSigkillGroup(sender, pgid, uid)).await;
12817 match response.1 {
12818 GlobalResponse::ReserveSigkill(token) => token,
12819 _ => unreachable!(),
12820 }
12821}
12822
12823pub(crate) async fn sigkill_dying_threads<G, T>(guest: &mut G, token: u64) -> Vec<(DetTid, MmId)>
12826where
12827 G: Guest<Detcore<T>>,
12828 T: RecordOrReplay,
12829{
12830 let response = send_and_update_time(guest, GlobalRequest::SigkillDyingThreads(token)).await;
12831 match response.1 {
12832 GlobalResponse::SigkillDyingThreads(dying) => dying,
12833 _ => unreachable!(),
12834 }
12835}
12836
12837pub(crate) async fn record_sigkill_robust_lists<G, T>(
12840 guest: &mut G,
12841 token: u64,
12842 lists: Vec<(
12843 DetTid,
12844 MmId,
12845 crate::syscalls::robust_list::RobustListSnapshot,
12846 )>,
12847) where
12848 G: Guest<Detcore<T>>,
12849 T: RecordOrReplay,
12850{
12851 let response =
12852 send_and_update_time(guest, GlobalRequest::RecordSigkillRobustLists(token, lists)).await;
12853 match response.1 {
12854 GlobalResponse::RecordSigkillRobustLists(()) => {}
12855 _ => unreachable!(),
12856 }
12857}
12858
12859pub(crate) async fn take_sigkill_robust_list<R>(
12862 threads_time: DetTime,
12863 reverie: &R,
12864 mm: MmId,
12865) -> Option<crate::syscalls::robust_list::RobustListSnapshot>
12866where
12867 R: GlobalRPC<GlobalState>,
12868{
12869 let response = reverie
12870 .send_rpc((threads_time, mm, GlobalRequest::TakeSigkillRobustList))
12871 .await;
12872 match response.1 {
12873 GlobalResponse::TakeSigkillRobustList(list) => list,
12874 _ => unreachable!(),
12875 }
12876}
12877
12878pub async fn commit_sigkill<G, T>(guest: &mut G, token: u64, sent: bool)
12880where
12881 G: Guest<Detcore<T>>,
12882 T: RecordOrReplay,
12883{
12884 let response = send_and_update_time(guest, GlobalRequest::CommitSigkill(token, sent)).await;
12885 match response.1 {
12886 GlobalResponse::CommitSigkill(()) => {}
12887 _ => unreachable!(),
12888 }
12889}
12890
12891pub async fn create_session<G, T>(guest: &mut G, process: DetPid) -> bool
12892where
12893 G: Guest<Detcore<T>>,
12894 T: RecordOrReplay,
12895{
12896 let response = send_and_update_time(guest, GlobalRequest::CreateSession(process)).await;
12897 match response.1 {
12898 GlobalResponse::CreateSession(updated) => updated,
12899 _ => unreachable!(),
12900 }
12901}
12902
12903pub async fn consume_child_wait<G, T>(guest: &mut G, child: DetPid) -> bool
12904where
12905 G: Guest<Detcore<T>>,
12906 T: RecordOrReplay,
12907{
12908 let parent = guest.thread_state().detpid.expect("detpid unset");
12909 let response =
12910 send_and_update_time(guest, GlobalRequest::ConsumeChildWait(parent, child)).await;
12911 match response.1 {
12912 GlobalResponse::ConsumeChildWait(consumed) => consumed,
12913 _ => unreachable!(),
12914 }
12915}
12916
12917pub async fn resolve_kill_targets_and_exit<G, T>(
12920 guest: &mut G,
12921 detpid: DetPid,
12922) -> (Vec<DetTid>, Option<bool>)
12923where
12924 G: Guest<Detcore<T>>,
12925 T: RecordOrReplay,
12926{
12927 let response = send_and_update_time(guest, GlobalRequest::ResolveKillTargets(detpid)).await;
12928 match response.1 {
12929 GlobalResponse::ResolveKillTargets(targets, zombie) => (targets, zombie),
12930 _ => unreachable!(),
12931 }
12932}
12933
12934pub async fn names_hermit_task<G, T>(guest: &mut G, tgid: Option<i32>, tid: i32) -> bool
12938where
12939 G: Guest<Detcore<T>>,
12940 T: RecordOrReplay,
12941{
12942 let caller = guest.thread_state().dettid;
12943 let response =
12944 send_and_update_time(guest, GlobalRequest::NamesHermitTask(caller, tgid, tid)).await;
12945 match response.1 {
12946 GlobalResponse::NamesHermitTask(named) => named,
12947 _ => unreachable!(),
12948 }
12949}
12950
12951pub fn hermit_task_named(caller: DetTid, tgid: Option<i32>, tid: i32) -> bool {
12973 use std::os::unix::fs::MetadataExt;
12974 let hermit = std::process::id() as i32;
12975 if tid <= 0 || tgid.is_some_and(|tgid| tgid != hermit) {
12976 return false;
12977 }
12978 let in_hermits_group =
12980 tid == hermit || unsafe { libc::syscall(libc::SYS_tgkill, hermit, tid, 0) } == 0;
12981 if !in_hermits_group || !descends_from(caller.as_raw(), hermit) {
12982 return false;
12983 }
12984 let namespace = |path: String| fs::metadata(path).map(|meta| (meta.dev(), meta.ino()));
12985 match (
12986 namespace(format!("/proc/{}/ns/pid", caller.as_raw())),
12987 namespace("/proc/self/ns/pid".to_owned()),
12988 ) {
12989 (Ok(callers), Ok(hermits)) => callers == hermits,
12990 _ => false,
12991 }
12992}
12993
12994fn descends_from(task: i32, ancestor: i32) -> bool {
12998 let parent = |pid: i32| -> Option<i32> {
12999 let stat = fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
13000 stat.rsplit_once(')')?
13003 .1
13004 .split_whitespace()
13005 .nth(1)?
13006 .parse()
13007 .ok()
13008 };
13009 let mut pid = task;
13010 for _ in 0..4096 {
13013 match parent(pid) {
13014 Some(next) if next == ancestor => return true,
13015 Some(next) if next > 0 => pid = next,
13016 _ => return false,
13017 }
13018 }
13019 false
13020}
13021
13022pub async fn notify_unresolved_sigkill<G, T>(guest: &mut G, group: Option<DetPid>)
13026where
13027 G: Guest<Detcore<T>>,
13028 T: RecordOrReplay,
13029{
13030 let response = send_and_update_time(guest, GlobalRequest::NotifyUnresolvedSigkill(group)).await;
13031 match response.1 {
13032 GlobalResponse::NotifyUnresolvedSigkill(()) => {}
13033 _ => unreachable!(),
13034 }
13035}
13036
13037fn alarm_signal(sig: SigWrapper) -> Signal {
13047 sig.signal().unwrap_or_else(|| {
13048 panic!(
13049 "timer registration received unnameable signal {}",
13050 sig.raw()
13051 )
13052 })
13053}
13054
13055pub async fn hold_turn_for_group_exit<G, T>(guest: &mut G, dettid: DetTid) -> bool
13060where
13061 G: Guest<Detcore<T>>,
13062 T: RecordOrReplay,
13063{
13064 let response = send_and_update_time(guest, GlobalRequest::HoldTurnForGroupExit(dettid)).await;
13065 match response.1 {
13066 GlobalResponse::HoldTurnForGroupExit(held) => held,
13067 _ => unreachable!(),
13068 }
13069}
13070
13071pub async fn notify_signal_pending<G, T>(
13072 guest: &mut G,
13073 dettid: DetTid,
13074 signal: SigWrapper,
13075 target_process: Option<DetPid>,
13076 scope: SignalScope,
13077) where
13078 G: Guest<Detcore<T>>,
13079 T: RecordOrReplay,
13080{
13081 let response = send_and_update_time(
13082 guest,
13083 GlobalRequest::NotifySignalPending(dettid, signal, target_process, scope),
13084 )
13085 .await;
13086 match response.1 {
13087 GlobalResponse::NotifySignalPending(()) => {}
13088 _ => unreachable!(),
13089 }
13090}
13091
13092pub async fn unrecoverable_shutdown<G, T>(guest: &G, status: i32) -> !
13108where
13109 G: Guest<Detcore<T>>,
13110 T: RecordOrReplay,
13111{
13112 crate::guest_log::flush_unsent();
13114 if cfg!(debug_assertions) {
13115 let mytime = guest.thread_state().thread_logical_time.clone();
13116 let mm = guest.thread_state().mm_id;
13117 let _ = guest
13119 .send_rpc((mytime, mm, GlobalRequest::UnrecoverableShutdown))
13120 .await;
13121 }
13122
13123 std::process::exit(status);
13142}
13143
13144#[cfg(test)]
13145mod tests {
13146 #[tokio::test]
13147 async fn automatic_guest_ids_skip_registered_guest_choices() {
13148 let config = Config {
13149 guest_ids: detcore_model::pid::GuestIds::Chosen {
13150 base: 5000,
13151 refuse_host_ids: true,
13152 },
13153 ..Config::default()
13154 };
13155 let state = GlobalState::initialize(&config, false);
13156 let root = DetTid::from_raw(5000);
13157 let explicit = DetTid::from_raw(5001);
13158 {
13159 let mut sched = state.sched.lock().unwrap();
13160 sched.thread_tree.add_child(root, root, true);
13161 sched.thread_tree.add_child(root, explicit, true);
13162 assert!(sched.thread_was_registered(explicit));
13163 }
13164 assert_eq!(state.take_guest_id().await, Some(5002));
13165 assert_eq!(state.take_guest_id().await, Some(5003));
13166 }
13167
13168 #[tokio::test]
13171 async fn automatic_guest_ids_stop_instead_of_wrapping() {
13172 use std::sync::atomic::Ordering::SeqCst;
13173
13174 let config = |base| Config {
13175 guest_ids: detcore_model::pid::GuestIds::Chosen {
13176 base,
13177 refuse_host_ids: true,
13178 },
13179 ..Config::default()
13180 };
13181 let state = GlobalState::initialize(&config(i32::MAX - 1), false);
13182 assert_eq!(state.take_guest_id().await, Some(i32::MAX));
13183 assert_eq!(state.next_guest_id.load(SeqCst), 0);
13184 for _ in 0..3 {
13185 assert_eq!(state.take_guest_id().await, None);
13186 assert_eq!(state.next_guest_id.load(SeqCst), 0);
13187 }
13188 for base in [i32::MAX, 0, -1, i32::MIN] {
13189 let state = GlobalState::initialize(&config(base), false);
13190 assert_eq!(state.take_guest_id().await, None, "base {base}");
13191 assert_eq!(state.take_guest_id().await, None, "base {base}");
13192 assert_eq!(state.next_guest_id.load(SeqCst), 0);
13193 }
13194
13195 let state = GlobalState::initialize(&config(i32::MAX - 2), false);
13197 let root = DetTid::from_raw(i32::MAX - 2);
13198 let registered = DetTid::from_raw(i32::MAX - 1);
13199 let clocked = DetTid::from_raw(i32::MAX);
13200 {
13201 let mut scheduler = state.sched.lock().unwrap();
13202 scheduler.thread_tree.add_child(root, root, true);
13203 scheduler.thread_tree.add_child(root, registered, true);
13204 assert!(!scheduler.thread_was_registered(clocked));
13205 }
13206 {
13207 let mut clock = state.global_time.lock().unwrap();
13208 let now = clock.as_nanos();
13209 clock.update_global_time(clocked, now, Default::default());
13210 assert!(clock.contains_thread(clocked));
13211 }
13212 assert_eq!(state.take_guest_id().await, None);
13213 assert_eq!(state.next_guest_id.load(SeqCst), 0);
13214 assert_eq!(state.take_guest_id().await, None);
13215 }
13216
13217 #[test]
13231 fn a_death_by_a_fatal_signal_lets_go_of_the_tables_its_threads_named() {
13232 use nix::fcntl::OFlag;
13233
13234 use crate::fd::FdType;
13235 use crate::fd::SentDescriptor;
13236 use crate::procfs::locks::PlaceholderCopies;
13237 use crate::scheduler::signal_control::SignalDeath;
13238 use crate::types::DetPid;
13239 use crate::types::DetTid;
13240
13241 let config = Config {
13242 virtualize_time: true,
13243 sequentialize_threads: true,
13244 recordreplay_modes: false,
13245 ..Config::default()
13246 };
13247 let state = GlobalState::initialize(&config, false);
13248 let hold = |tid: DetTid, thread: &crate::ThreadState<()>| {
13249 assert_eq!(
13250 state.hold_table(
13251 Tid::from_raw(tid.as_raw()),
13252 &super::HeldTable::new(std::sync::Arc::clone(&thread.file_metadata)),
13253 ),
13254 (None, GlobalResponse::HoldTable(Ok(())))
13255 );
13256 };
13257
13258 let parent_id = DetTid::from_raw(9060);
13261 let child_id = DetTid::from_raw(9061);
13262 let parent = crate::ThreadState::new(parent_id, &config, ());
13263 parent
13264 .add_fd(3, OFlag::empty(), FdType::Socket, None)
13265 .unwrap();
13266 parent
13267 .add_fd(4, OFlag::empty(), FdType::Socket, None)
13268 .unwrap();
13269 let left = parent.with_detfd(3, |detfd| detfd.clone()).unwrap();
13270 let right = parent.with_detfd(4, |detfd| detfd.clone()).unwrap();
13271 left.pair_sockets(&right, false);
13272 drop(right);
13273 let child = crate::ThreadState::new(child_id, &config, ());
13274 let mut copy = parent.file_metadata.lock().unwrap().fork_for(child_id);
13275 copy.hold_alone(child_id);
13276 *child.file_metadata.lock().unwrap() = copy;
13277 assert_eq!(parent.remove_fd(4), None, "the child holds the receiver");
13278 assert_eq!(child.remove_fd(3), None, "the parent holds the sender");
13279 parent
13280 .add_fd(5, OFlag::empty(), FdType::Regular, None)
13281 .unwrap();
13282 let carried = parent
13283 .with_detfd(5, |detfd| detfd.in_flight_open_file(true))
13284 .unwrap();
13285 let sent_id = parent.with_detfd(5, |detfd| detfd.open_file_id()).unwrap();
13286 assert!(
13287 left.queue_rights_to_peer(
13288 vec![SentDescriptor {
13289 file: (1, 5),
13290 open_file: Some(carried),
13291 lock_copy: Some(sent_id),
13292 }],
13293 1,
13294 )
13295 .is_ok()
13296 );
13297 assert_eq!(parent.remove_fd(5), None, "a copy is in flight");
13298 let ledger = || state.lock_ledger.lock().unwrap();
13299 assert_eq!(
13300 ledger().placeholder_copies(PlaceholderCopies::Sending(vec![sent_id])),
13301 Ok(())
13302 );
13303 hold(child_id, &child);
13304 state.release_tables_at_death(SignalDeath {
13305 process: DetPid::from_raw(child_id.as_raw()),
13306 threads: vec![child_id],
13307 });
13308 assert!(
13309 !ledger().counts_copies_in_flight(),
13310 "the copy in flight went with the receiving socket"
13311 );
13312 assert_eq!(ledger().unknown(), None);
13313 assert!(state.held_tables.lock().unwrap().is_empty());
13314 assert!(
13315 matches!(
13316 child
13317 .file_metadata
13318 .lock()
13319 .unwrap()
13320 .release_at_death(child_id),
13321 crate::tool_local::TableAtDeath::Undecided
13322 ),
13323 "the table went, with its holders"
13324 );
13325
13326 let shared_id = DetTid::from_raw(9062);
13328 let unnamed_id = DetTid::from_raw(9063);
13329 let shared = crate::ThreadState::new(shared_id, &config, ());
13330 shared.file_metadata.lock().unwrap().join_holder(shared_id);
13331 hold(shared_id, &shared);
13332 assert_eq!(
13333 ledger().placeholder_copies(PlaceholderCopies::Sending(vec![sent_id])),
13334 Ok(())
13335 );
13336 state.release_tables_at_death(SignalDeath {
13337 process: DetPid::from_raw(shared_id.as_raw()),
13338 threads: vec![shared_id, unnamed_id],
13339 });
13340 assert_eq!(ledger().unknown(), None, "the unnamed thread shares it");
13341 assert!(ledger().counts_copies_in_flight(), "its table held no copy");
13342
13343 let private_id = DetTid::from_raw(9064);
13346 let private = DetPid::from_raw(private_id.as_raw());
13347 ledger().mark_private_tables(private);
13348 state.release_tables_at_death(SignalDeath {
13349 process: private,
13350 threads: vec![private_id],
13351 });
13352 let refusal = ledger().unknown().map(str::to_owned);
13353 assert!(
13354 refusal
13355 .as_deref()
13356 .is_some_and(|reason| reason.contains("died by a fatal signal")),
13357 "{refusal:?}"
13358 );
13359 drop(left);
13360
13361 let request = GlobalRequest::HoldTable(super::HeldTable::new(std::sync::Arc::clone(
13364 &child.file_metadata,
13365 )));
13366 let encoded = bincode::serde::encode_to_vec(&request, bincode::config::legacy()).unwrap();
13367 let (decoded, _): (GlobalRequest, usize) =
13368 bincode::serde::decode_from_slice(&encoded, bincode::config::legacy()).unwrap();
13369 assert_ne!(decoded, request, "no table crosses the encoding");
13370 let GlobalRequest::HoldTable(decoded) = decoded else {
13371 panic!("a held table decodes as a held table: {decoded:?}");
13372 };
13373 let refused = state.hold_table(Tid::from_raw(child_id.as_raw()), &decoded);
13374 assert!(
13375 matches!(
13376 &refused,
13377 (None, GlobalResponse::HoldTable(Err(reason)))
13378 if reason.contains("a transport that encodes its requests")
13379 ),
13380 "{refused:?}"
13381 );
13382 assert!(state.held_tables.lock().unwrap().is_empty());
13383 }
13384
13385 #[tokio::test]
13392 async fn the_host_diskstats_are_read_once_and_answered_alike() {
13393 use reverie::Tid;
13394 let config = Config::default();
13395 let state = GlobalState::initialize(&config, false);
13396 let ask = |from: i32| {
13397 state.receive_rpc(
13398 Tid::from_raw(from),
13399 (
13400 DetTime::default(),
13401 MmId::initial(DetTid::from_raw(0)),
13402 super::GlobalRequest::HostDiskstats,
13403 ),
13404 )
13405 };
13406 let (time, first) = ask(3).await;
13407 assert_eq!(time, None, "answered with no time");
13408 let host = std::fs::read("/proc/diskstats").map_err(|error| error.raw_os_error());
13409 match (&first, &host) {
13410 (super::GlobalResponse::HostDiskstats(Ok(bytes)), Ok(_)) => {
13411 assert!(!bytes.is_empty() || host.as_ref().is_ok_and(Vec::is_empty));
13412 }
13413 (super::GlobalResponse::HostDiskstats(Err(errno)), Err(Some(expected))) => {
13414 assert_eq!(errno, expected);
13415 }
13416 other => panic!("the coordinator's read disagrees with this process's: {other:?}"),
13417 }
13418 for from in [4, 3, 9] {
13419 assert_eq!(ask(from).await.1, first, "the same table for every request");
13420 }
13421 }
13422
13423 #[tokio::test]
13430 async fn a_shared_record_sequence_is_the_last_one_stored() {
13431 use reverie::Tid;
13432 let config = Config::default();
13433 let state = GlobalState::initialize(&config, false);
13434 let send = |from: i32, request: super::GlobalRequest| {
13435 state.receive_rpc(
13436 Tid::from_raw(from),
13437 (
13438 DetTime::default(),
13439 MmId::initial(DetTid::from_raw(0)),
13440 request,
13441 ),
13442 )
13443 };
13444 let id = crate::OpenFileId::new(DetTid::from_raw(3), 7);
13445 let other = crate::OpenFileId::new(DetTid::from_raw(4), 7);
13446 let (time, none) = send(3, super::GlobalRequest::ProcfsSequence(id)).await;
13447 assert_eq!(time, None, "answered with no time");
13448 assert_eq!(none, super::GlobalResponse::ProcfsSequence(None));
13449 let first = crate::procfs::ProcfsSequence::for_test(4, Some(4), b"rchar: 0\n");
13450 let second = crate::procfs::ProcfsSequence::for_test(6, Some(6), b"rchar: 0\n");
13451 for (from, sequence) in [(3, &first), (5, &second)] {
13452 let (time, stored) = send(
13453 from,
13454 super::GlobalRequest::StoreProcfsSequence(id, sequence.clone()),
13455 )
13456 .await;
13457 assert_eq!(time, None, "answered with no time");
13458 assert_eq!(stored, super::GlobalResponse::StoreProcfsSequence(()));
13459 }
13460 for from in [3, 5, 9] {
13461 assert_eq!(
13462 send(from, super::GlobalRequest::ProcfsSequence(id)).await.1,
13463 super::GlobalResponse::ProcfsSequence(Some(second.clone())),
13464 "the last one stored, for every process"
13465 );
13466 }
13467 assert_eq!(
13468 send(3, super::GlobalRequest::ProcfsSequence(other)).await.1,
13469 super::GlobalResponse::ProcfsSequence(None),
13470 "another description has its own"
13471 );
13472 }
13473
13474 fn granted(response: super::GlobalResponse) -> super::GlobalResponse {
13477 super::GlobalResponse::Granted(Box::new(response))
13478 }
13479
13480 #[test]
13484 fn a_changed_root_follows_fork_and_goes_at_exit() {
13485 use super::ProcessAuxvTable;
13486 use crate::types::DetPid;
13487 let (parent, child, other) = (
13488 DetPid::from_raw(3),
13489 DetPid::from_raw(4),
13490 DetPid::from_raw(5),
13491 );
13492 let mut table = ProcessAuxvTable::default();
13493 table.clone_root(DetPid::from_raw(1), parent, false);
13494 assert!(!table.root_changed(parent));
13495 table.mark_changed_root(parent);
13496 assert!(table.root_changed(parent));
13497 table.clone_root(parent, child, false);
13500 assert!(table.root_changed(child));
13501 table.clone_root(other, child, false);
13502 assert!(!table.root_changed(child), "a reused number starts afresh");
13503 table.forget(parent);
13505 assert!(table.root_changed(parent));
13506 table.forget_process(parent);
13507 assert!(!table.root_changed(parent));
13508 }
13509
13510 #[test]
13515 fn a_root_is_shared_by_a_filesystem_group() {
13516 use super::ProcessAuxvTable;
13517 use crate::types::DetPid;
13518 let pid = DetPid::from_raw;
13519 let mut table = ProcessAuxvTable::default();
13520 table.clone_root(pid(1), pid(3), false);
13521 table.clone_root(pid(3), pid(4), true);
13523 table.clone_root(pid(3), pid(5), false);
13524 table.mark_changed_root(pid(4));
13525 assert!(
13526 table.root_changed(pid(3)),
13527 "the child's chroot is its creator's"
13528 );
13529 assert!(
13530 !table.root_changed(pid(5)),
13531 "a copied root stays where it was"
13532 );
13533 table.clone_root(pid(5), pid(6), false);
13535 table.mark_changed_root(pid(6));
13536 assert!(!table.root_changed(pid(5)));
13537 table.transfer_root(pid(6), pid(5));
13539 assert!(table.root_changed(pid(5)));
13540 table.forget_process(pid(4));
13542 assert!(table.root_changed(pid(3)));
13543 }
13544
13545 #[test]
13546 fn process_auxv_follows_exec_and_fork() {
13547 use super::ProcessAuxv;
13548 use super::ProcessAuxvTable;
13549 use crate::types::DetPid;
13550 let (parent, child) = (DetPid::from_raw(3), DetPid::from_raw(4));
13551 let mut table = ProcessAuxvTable::default();
13552 assert_eq!(table.current(parent), None);
13553 table.record(parent, vec![1; 16]);
13554 table.inherit(parent, child);
13555 assert_eq!(
13556 table.current(child),
13557 Some(ProcessAuxv {
13558 process: child,
13559 bytes: vec![1; 16]
13560 })
13561 );
13562 table.record(child, vec![2; 32]);
13563 assert_eq!(
13564 table.current(child).map(|entry| entry.bytes),
13565 Some(vec![2; 32])
13566 );
13567 assert_eq!(
13568 table.current(parent).map(|entry| entry.bytes),
13569 Some(vec![1; 16])
13570 );
13571 table.forget(parent);
13572 assert_eq!(table.current(parent), None);
13573 table.inherit(parent, child);
13575 assert_eq!(table.current(child), None);
13576 assert_eq!(table.len(), 0);
13577 }
13578
13579 #[test]
13585 fn process_cmdline_areas_follow_exec_and_fork() {
13586 use super::ProcessAuxvTable;
13587 use crate::procfs::CmdlineArea;
13588 use crate::types::DetPid;
13589 let (parent, child) = (DetPid::from_raw(3), DetPid::from_raw(4));
13590 let area = |start| CmdlineArea {
13591 arg_start: start,
13592 arg_end: start + 8,
13593 env_start: start + 8,
13594 env_end: start + 16,
13595 };
13596 let mut table = ProcessAuxvTable::default();
13597 assert_eq!(table.current_cmdline(parent), None);
13598 table.record_cmdline(parent, Some(area(0x1000)));
13599 table.inherit(parent, child);
13600 assert_eq!(table.current_cmdline(child), Some(area(0x1000)));
13601 table.record_cmdline(child, Some(area(0x2000)));
13602 assert_eq!(table.current_cmdline(child), Some(area(0x2000)));
13603 assert_eq!(table.current_cmdline(parent), Some(area(0x1000)));
13604 table.record_cmdline(child, None);
13606 assert_eq!(table.current_cmdline(child), None);
13607 table.record_cmdline(child, Some(area(0x3000)));
13609 table.forget(child);
13610 assert_eq!(table.current_cmdline(child), Some(area(0x3000)));
13611 table.forget_process(child);
13612 assert_eq!(table.current_cmdline(child), None);
13613 table.forget_process(parent);
13614 table.record_cmdline(child, Some(area(0x4000)));
13616 table.inherit(parent, child);
13617 assert_eq!(table.current_cmdline(child), None);
13618 }
13619
13620 #[test]
13624 fn only_hermits_own_thread_group_is_hermits() {
13625 use super::hermit_task_named;
13626 use crate::types::DetTid;
13627 let me = std::process::id() as i32;
13628 let mut guest = std::process::Command::new("sleep")
13630 .arg("30")
13631 .spawn()
13632 .unwrap();
13633 let caller = DetTid::from_raw(guest.id() as i32);
13634 assert!(hermit_task_named(caller, None, me));
13635 assert!(hermit_task_named(caller, Some(me), me));
13636 let own_thread = DetTid::from_raw(unsafe { libc::gettid() });
13640 assert!(!hermit_task_named(own_thread, None, me));
13641 let (send, receive) = std::sync::mpsc::channel();
13642 let (stop, stopped) = std::sync::mpsc::channel::<()>();
13643 let thread = std::thread::spawn(move || {
13644 send.send(unsafe { libc::gettid() }).unwrap();
13646 let _ = stopped.recv();
13647 });
13648 let other_thread = receive.recv().unwrap();
13649 assert!(hermit_task_named(caller, None, other_thread));
13650 assert!(hermit_task_named(caller, Some(me), other_thread));
13651 let guest_pid = guest.id() as i32;
13652 assert!(!hermit_task_named(caller, None, guest_pid));
13653 assert!(!hermit_task_named(caller, Some(guest_pid), guest_pid));
13654 assert!(!hermit_task_named(caller, Some(guest_pid), other_thread));
13655 assert!(!hermit_task_named(caller, None, 0));
13656 assert!(!hermit_task_named(caller, None, -1));
13657 assert!(!hermit_task_named(DetTid::from_raw(i32::MAX), None, me));
13660 let parent = DetTid::from_raw(unsafe { libc::getppid() });
13662 assert!(!hermit_task_named(parent, None, me));
13663 guest.kill().unwrap();
13664 guest.wait().unwrap();
13665 stop.send(()).unwrap();
13666 thread.join().unwrap();
13667 }
13668
13669 #[test]
13673 fn the_dbt_sigkill_fence_waits_until_the_victim_process_is_gone() {
13674 use super::open_process_pidfd;
13675 use super::wait_for_process_exit;
13676 let runtime = tokio::runtime::Builder::new_current_thread()
13677 .enable_all()
13678 .build()
13679 .unwrap();
13680 let mut victim = std::process::Command::new("sleep")
13681 .arg("30")
13682 .spawn()
13683 .unwrap();
13684 let pid = victim.id() as i32;
13685 let live = open_process_pidfd(pid).expect("a live child has a pidfd");
13686 let waited = runtime.block_on(wait_for_process_exit(
13687 live,
13688 std::time::Duration::from_millis(50),
13689 ));
13690 assert!(waited.unwrap_err().contains("not gone within"));
13691 let dying = open_process_pidfd(pid).expect("a live child has a pidfd");
13692 victim.kill().unwrap();
13693 assert_eq!(
13694 runtime.block_on(wait_for_process_exit(
13695 dying,
13696 std::time::Duration::from_secs(30)
13697 )),
13698 Ok(())
13699 );
13700 victim.wait().unwrap();
13701 assert!(open_process_pidfd(pid).is_err());
13702 }
13703
13704 #[test]
13710 fn the_dbt_sigkill_fence_refuses_rather_than_leave_the_sender_unfenced() {
13711 use super::sigkill_fence_for;
13712 use crate::scheduler::Scheduler;
13713 use crate::types::DetPid;
13714 use crate::types::DetTid;
13715 use crate::types::MmId;
13716 let config = crate::config::Config::default()
13717 .with_backend(|backend| *backend = reverie::BackendCapabilities::DBT);
13718 let mut scheduler = Scheduler::new(&config);
13719 let me = std::process::id() as i32;
13720 let errno = |code| move |_: i32| Err(std::io::Error::from_raw_os_error(code));
13721 let live = DetTid::from_raw(41);
13723 let live_process = DetPid::from_raw(41);
13724 let my_tid = unsafe { libc::gettid() };
13726 scheduler
13727 .register_physical_thread(live, MmId::initial(live_process), me, my_tid)
13728 .unwrap();
13729 let refusal = sigkill_fence_for(&scheduler, live, live_process, true, errno(libc::EMFILE))
13730 .unwrap_err();
13731 assert_eq!(refusal.process, live_process);
13732 assert!(refusal.reason.contains("pidfd_open"), "{}", refusal.reason);
13733 assert!(
13734 sigkill_fence_for(&scheduler, live, live_process, true, errno(libc::ESRCH)).is_err()
13735 );
13736 assert!(
13737 sigkill_fence_for(&scheduler, live, live_process, true, errno(libc::EINVAL)).is_err()
13738 );
13739 assert!(
13740 sigkill_fence_for(
13741 &scheduler,
13742 live,
13743 live_process,
13744 true,
13745 super::open_process_pidfd
13746 )
13747 .unwrap()
13748 .is_some()
13749 );
13750 let gone = DetTid::from_raw(43);
13752 let gone_process = DetPid::from_raw(43);
13753 let (send, receive) = std::sync::mpsc::channel();
13754 let (stop, stopped) = std::sync::mpsc::channel::<()>();
13755 let thread = std::thread::spawn(move || {
13756 send.send(unsafe { libc::gettid() }).unwrap();
13758 let _ = stopped.recv();
13759 });
13760 let gone_tid = receive.recv().unwrap();
13761 scheduler
13762 .register_physical_thread(gone, MmId::initial(gone_process), me, gone_tid)
13763 .unwrap();
13764 stop.send(()).unwrap();
13765 thread.join().unwrap();
13766 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
13771 while scheduler.registered_thread_has_exited(gone) != Some(true) {
13772 assert!(
13773 std::time::Instant::now() < deadline,
13774 "the joined thread's pidfd never reported its exit"
13775 );
13776 std::thread::sleep(std::time::Duration::from_millis(1));
13777 }
13778 assert!(
13779 sigkill_fence_for(&scheduler, gone, gone_process, true, errno(libc::EINVAL))
13780 .unwrap()
13781 .is_none()
13782 );
13783 assert!(
13784 sigkill_fence_for(&scheduler, gone, gone_process, true, errno(libc::ESRCH))
13785 .unwrap()
13786 .is_none()
13787 );
13788 assert!(
13789 sigkill_fence_for(&scheduler, gone, gone_process, true, errno(libc::EMFILE)).is_err()
13790 );
13791 let unregistered = DetTid::from_raw(47);
13796 let empty = DetPid::from_raw(47);
13797 let refusal = sigkill_fence_for(&scheduler, unregistered, empty, true, errno(libc::EMFILE))
13798 .unwrap_err();
13799 assert!(
13800 refusal.reason.contains("has a registered host process"),
13801 "{}",
13802 refusal.reason
13803 );
13804 scheduler.grant_exit_for_test(empty);
13807 assert!(
13808 sigkill_fence_for(&scheduler, unregistered, empty, true, errno(libc::EMFILE))
13809 .unwrap()
13810 .is_none()
13811 );
13812 let leader = DetTid::from_raw(51);
13817 let process = DetPid::from_raw(51);
13818 let sibling = DetTid::from_raw(52);
13819 scheduler.thread_tree.add_child(leader, leader, true);
13820 scheduler.thread_tree.add_child(leader, sibling, false);
13821 let my_tid = unsafe { libc::gettid() };
13823 scheduler
13824 .register_physical_thread(sibling, MmId::initial(process), me, my_tid)
13825 .unwrap();
13826 assert!(
13827 sigkill_fence_for(&scheduler, leader, process, true, super::open_process_pidfd)
13828 .unwrap()
13829 .is_some()
13830 );
13831 assert!(sigkill_fence_for(&scheduler, leader, process, true, errno(libc::ESRCH)).is_err());
13832 assert!(
13838 sigkill_fence_for(
13839 &scheduler,
13840 leader,
13841 process,
13842 false,
13843 super::open_process_pidfd
13844 )
13845 .is_err()
13846 );
13847 assert!(
13848 sigkill_fence_for(&scheduler, unregistered, empty, false, errno(libc::EMFILE)).is_err()
13849 );
13850 let late_leader = DetTid::from_raw(61);
13854 let late_process = DetPid::from_raw(61);
13855 let late_sibling = DetTid::from_raw(62);
13856 scheduler
13857 .thread_tree
13858 .add_child(late_leader, late_leader, true);
13859 scheduler
13860 .thread_tree
13861 .add_child(late_leader, late_sibling, false);
13862 let (send, receive) = std::sync::mpsc::channel();
13863 let (stop, stopped) = std::sync::mpsc::channel::<()>();
13864 let thread = std::thread::spawn(move || {
13865 send.send(unsafe { libc::gettid() }).unwrap();
13867 let _ = stopped.recv();
13868 });
13869 let sibling_tid = receive.recv().unwrap();
13870 scheduler
13871 .register_physical_thread(late_sibling, MmId::initial(late_process), me, sibling_tid)
13872 .unwrap();
13873 stop.send(()).unwrap();
13874 thread.join().unwrap();
13875 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
13876 while scheduler.registered_thread_has_exited(late_sibling) != Some(true) {
13877 assert!(
13878 std::time::Instant::now() < deadline,
13879 "the joined thread's pidfd never reported its exit"
13880 );
13881 std::thread::sleep(std::time::Duration::from_millis(1));
13882 }
13883 assert!(
13884 sigkill_fence_for(
13885 &scheduler,
13886 late_leader,
13887 late_process,
13888 true,
13889 errno(libc::ESRCH)
13890 )
13891 .unwrap()
13892 .is_none()
13893 );
13894 }
13895
13896 #[test]
13901 fn external_scheduler_announces_before_its_future_is_polled() {
13902 #[derive(Clone, Default)]
13903 struct Capture(std::sync::Arc<Mutex<Vec<String>>>);
13904 struct Visitor(String);
13905 impl tracing::field::Visit for Visitor {
13906 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
13907 if field.name() == "message" {
13908 use std::fmt::Write;
13909 write!(self.0, "{value:?}").unwrap();
13910 }
13911 }
13912 }
13913 impl tracing::Subscriber for Capture {
13914 fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool {
13915 *metadata.level() == tracing::Level::INFO
13916 }
13917 fn new_span(&self, _: &tracing::span::Attributes<'_>) -> tracing::span::Id {
13918 tracing::span::Id::from_u64(1)
13919 }
13920 fn record(&self, _: &tracing::span::Id, _: &tracing::span::Record<'_>) {}
13921 fn record_follows_from(&self, _: &tracing::span::Id, _: &tracing::span::Id) {}
13922 fn event(&self, event: &tracing::Event<'_>) {
13923 let mut visitor = Visitor(String::new());
13924 event.record(&mut visitor);
13925 self.0.lock().unwrap().push(visitor.0);
13926 }
13927 fn enter(&self, _: &tracing::span::Id) {}
13928 fn exit(&self, _: &tracing::span::Id) {}
13929 }
13930 let config = Config {
13931 sequentialize_threads: true,
13932 ..Config::default()
13933 };
13934 let state = GlobalState::init_for_external_scheduler(&config);
13935 let captured = Capture::default();
13936 let scheduler = tracing::subscriber::with_default(captured.clone(), || {
13937 state.run_external_scheduler(std::sync::Arc::new(|_| {}))
13938 });
13939 let announcement = "[scheduler] daemon task starting up, waiting for guest thread start..";
13940 assert_eq!(*captured.0.lock().unwrap(), [announcement]);
13941 drop(scheduler);
13942 }
13943
13944 #[test]
13945 fn schedule_event_host_markers_require_command_bootstrap_provenance() {
13946 let event = SchedEvent::branches(DetTid::from_raw(3), 223)
13947 .with_end_rip(std::num::NonZeroUsize::new(0x1234).unwrap())
13948 .with_time(LogicalTime::from_nanos(2230));
13949 let original = serde_json::to_string(&event).unwrap();
13950 let plain = format!(
13951 "{:?}",
13952 super::SchedEventForLog {
13953 event: &event,
13954 command_bootstrap: false
13955 }
13956 );
13957 assert_eq!(plain, format!("{event:?}"));
13958 let marked = format!(
13959 "{:?}",
13960 super::SchedEventForLog {
13961 event: &event,
13962 command_bootstrap: true
13963 }
13964 );
13965 assert_eq!(
13966 marked,
13967 "SchedEvent { dettid: DetPid(3), op: Branch, count: 223, start_rip: None, end_rip: Some(<hostaddr 0x1234>), end_time: Some(LogicalTime(2230)) }"
13968 );
13969 assert_eq!(serde_json::to_string(&event).unwrap(), original);
13970 }
13971
13972 #[test]
13973 fn summary_preemption_views_keep_counts_full_report_and_single_flush() {
13974 let (_config, state, tid, _) = cancellation_test_state();
13975 let directory = tempfile::tempdir().unwrap();
13976 let path = directory.path().join("recording");
13977 let mut writer = crate::preemptions::PreemptionWriter::new(Some(path.clone()));
13978 writer.register_thread(tid, DEFAULT_PRIORITY);
13979 writer.insert_reprioritization(tid, LogicalTime::from_nanos(100), 10, DEFAULT_PRIORITY, 20);
13980 let mut scheduler = state.sched.lock().unwrap();
13981 scheduler.preemption_writer = Some(writer);
13982 let (summary, info_description) = scheduler
13983 .generate_partial_run_summary_for_log(Some(&path))
13984 .unwrap();
13985 assert!(scheduler.preemption_writer.is_none());
13986 let recorded = std::fs::read(&path).unwrap();
13987 let parsed: serde_json::Value = serde_json::from_slice(&recorded).unwrap();
13988 assert_eq!(
13989 parsed["per_thread"][tid.to_string()]["prio_changes"],
13990 serde_json::json!([[100, DEFAULT_PRIORITY]])
13991 );
13992 let count_line = "Record of 1 preemption and reprioritization events:\n";
13993 assert_eq!(info_description.as_deref(), Some(count_line));
13994 assert_eq!(
13995 summary.reprio_descrip.as_deref(),
13996 Some(format!("{count_line} (Writing to file {path:?})\n").as_str())
13997 );
13998 let full = summary.to_string();
13999 let json = serde_json::to_vec(&summary).unwrap();
14000 let info = summary.info(info_description.as_deref()).to_string();
14001 assert!(info.contains(count_line));
14002 assert!(!info.contains("Writing to file"));
14003 assert!(full.contains(&format!("Writing to file {path:?}")));
14004 let _ = scheduler.generate_partial_run_summary(None).unwrap();
14006 assert_eq!(std::fs::read(&path).unwrap(), recorded);
14007 assert_eq!(summary.to_string(), full);
14008 assert_eq!(serde_json::to_vec(&summary).unwrap(), json);
14009 }
14010
14011 #[test]
14012 fn run_summary_records_the_effective_shared_preemption_configuration() {
14013 for (config, expected_maximum, expected_timer) in [
14016 (
14017 Config::default().with_backend(|backend| {
14018 backend.delivers_preemption_timer_events = true;
14019 }),
14020 Some(200_000_000),
14021 true,
14022 ),
14023 (
14024 Config {
14025 max_timeslice: None,
14026 ..Config::default()
14027 }
14028 .with_backend(|backend| {
14029 backend.delivers_preemption_timer_events = true;
14030 }),
14031 None,
14032 false,
14033 ),
14034 (
14035 Config {
14036 max_timeslice: std::num::NonZeroU64::new(12_340),
14037 ..Config::default()
14038 }
14039 .with_backend(|backend| {
14040 backend.delivers_preemption_timer_events = true;
14041 }),
14042 Some(12_340),
14043 true,
14044 ),
14045 (
14046 Config::default().with_backend(|backend| {
14047 backend.delivers_preemption_timer_events = false;
14048 }),
14049 Some(200_000_000),
14050 false,
14051 ),
14052 (
14053 Config {
14054 max_timeslice: None,
14055 ..Config::default()
14056 }
14057 .with_backend(|backend| {
14058 backend.delivers_preemption_timer_events = false;
14059 }),
14060 None,
14061 false,
14062 ),
14063 ] {
14064 let summary = GlobalState::for_tests(&config).into_run_summary().unwrap();
14065 assert_eq!(
14066 summary.preemption,
14067 Some(super::PreemptionSummary {
14068 max_timeslice_ns: expected_maximum,
14069 timer_enabled: Some(expected_timer),
14070 stopless_slice_ends: None,
14071 })
14072 );
14073 }
14074 }
14075
14076 #[test]
14077 fn run_summary_info_keeps_semantics_and_debug_retains_bookkeeping() {
14078 #[derive(Clone)]
14079 struct Capture(std::sync::Arc<Mutex<Vec<(tracing::Level, String)>>>);
14080 struct Visitor(String);
14081 impl tracing::field::Visit for Visitor {
14082 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
14083 use std::fmt::Write;
14084 write!(self.0, "{}={value:?};", field.name()).unwrap();
14085 }
14086 }
14087 impl tracing::Subscriber for Capture {
14088 fn enabled(&self, _: &tracing::Metadata<'_>) -> bool {
14089 true
14090 }
14091 fn new_span(&self, _: &tracing::span::Attributes<'_>) -> tracing::span::Id {
14092 tracing::span::Id::from_u64(1)
14093 }
14094 fn record(&self, _: &tracing::span::Id, _: &tracing::span::Record<'_>) {}
14095 fn record_follows_from(&self, _: &tracing::span::Id, _: &tracing::span::Id) {}
14096 fn event(&self, event: &tracing::Event<'_>) {
14097 let mut visitor = Visitor(String::new());
14098 event.record(&mut visitor);
14099 self.0
14100 .lock()
14101 .unwrap()
14102 .push((*event.metadata().level(), visitor.0));
14103 }
14104 fn enter(&self, _: &tracing::span::Id) {}
14105 fn exit(&self, _: &tracing::span::Id) {}
14106 }
14107 let summary = super::RunSummary {
14108 sched_turns: 4,
14109 schedevent_recorded: 11,
14110 schedevent_replayed: 11,
14111 schedevent_desynced: 2,
14112 desync_descrip: Some("two real desyncs\n".into()),
14113 num_processes: 1,
14114 num_threads: 1,
14115 threads_descrip: "[3]".into(),
14116 syscalls: Some(3),
14117 virttime_elapsed: 2_512_380,
14118 virttime_final: 2_512_380,
14119 timeslice_stats: TimesliceStats {
14120 count: 1,
14121 sum_ns: 512_380,
14122 min_ns: 512_380,
14123 max_ns: 512_380,
14124 },
14125 ..Default::default()
14126 };
14127 let description = "Record of 7 preemption and reprioritization events:\n";
14128 let captured = Capture(Default::default());
14129 tracing::subscriber::with_default(captured.clone(), || {
14130 super::log_run_summary(
14131 "report",
14132 &summary,
14133 Some(description),
14134 Some(std::path::Path::new("/host/recording")),
14135 );
14136 });
14137 let events = captured.0.lock().unwrap();
14138 assert_eq!(events.len(), 2);
14139 assert_eq!(events[0].0, tracing::Level::INFO);
14140 assert_eq!(events[1].0, tracing::Level::DEBUG);
14141 let info = &events[0].1;
14142 for text in [
14143 "1 group leaders of 1 thread(s)",
14144 "3 syscalls",
14145 "4 turns, recorded 11 events (2 desynced)",
14146 "two real desyncs",
14147 "Record of 7 preemption",
14148 "2_512_380ns",
14149 "min=512380ns max=512380ns mean=512380ns count=1",
14150 ] {
14151 assert!(info.contains(text), "missing semantic field {text}: {info}");
14152 }
14153 assert!(!info.contains("/host/recording"));
14154 assert!(!info.contains("replayed"));
14155 assert!(events[1].1.contains("replayed_events=11"));
14156 assert!(events[1].1.contains("/host/recording"));
14157 let baseline = summary.info(Some(description)).to_string();
14158 let mutations: [fn(&mut super::RunSummary); 8] = [
14159 |s| s.sched_turns += 1,
14160 |s| s.schedevent_recorded += 1,
14161 |s| s.schedevent_desynced += 1,
14162 |s| s.num_threads += 1,
14163 |s| s.syscalls = Some(4),
14164 |s| s.virttime_elapsed += 1,
14165 |s| s.timeslice_stats.count += 1,
14166 |s| s.threads_descrip.push_str(",4"),
14167 ];
14168 for mutate in mutations {
14169 let mut changed = summary.clone();
14170 mutate(&mut changed);
14171 assert_ne!(changed.info(Some(description)).to_string(), baseline);
14172 }
14173 assert_ne!(
14174 summary
14175 .info(Some(
14176 "Record of 8 preemption and reprioritization events:\n"
14177 ))
14178 .to_string(),
14179 baseline
14180 );
14181 }
14182
14183 mod backend_failure_tests;
14184 mod comm_alias_open_tests;
14185 mod exec_identity_tests;
14186 mod exec_teardown_tests;
14187 mod fatal_delivery_tests;
14188 mod gated_wait_turn_tests;
14189 mod guest_mask_publication_tests;
14190 mod other_process_procfs_tests;
14191 mod replay_departure_tests;
14192 mod replay_departure_transport;
14193 mod reserved_input_exec_tests;
14194 mod sigkill_deregistration_tests;
14195 mod stopping_refusal_tests;
14196 mod temporary_mask_publication_tests;
14197 mod timerless_clock_tests;
14198 use std::collections::BTreeSet;
14199 use std::os::fd::AsRawFd;
14200 use std::os::fd::FromRawFd;
14201 use std::os::fd::OwnedFd;
14202 use std::sync::Mutex;
14203 use std::time::Duration;
14204
14205 use nix::sys::signal::Signal;
14206 use reverie::GlobalRPC;
14207 use reverie::GlobalTool;
14208 use reverie::Guest;
14209 use reverie::Tid;
14210 use reverie::syscalls::CloneFlags;
14211 use reverie::syscalls::Sysno;
14212
14213 use super::FutexAction;
14214 use super::GlobalRequest;
14215 use super::GlobalResponse;
14216 use super::GlobalState;
14217 use super::MountIdPool;
14218 use super::PendingExecState;
14219 use super::ResumeStatus;
14220 use super::RpcIncarnation;
14221 use super::SchedulerRpcResult;
14222 use super::SigWrapper;
14223 use super::ThreadDeregistration;
14224 use super::TimesliceStats;
14225 use super::format_unsupported_syscall_warning;
14226 use crate::Detcore;
14227 use crate::config::Config;
14228 use crate::config::RunsPostFork;
14229 use crate::ivar::Ivar;
14230 use crate::preemptions::PreemptionRecord;
14231 use crate::resources::ExternalOpId;
14232 use crate::resources::Permission;
14233 use crate::resources::ResourceID;
14234 use crate::resources::Resources;
14235 use crate::scheduler::DEFAULT_PRIORITY;
14236 use crate::scheduler::SchedRequest;
14237 use crate::scheduler::SchedResponse;
14238 use crate::scheduler::SchedValue;
14239 use crate::scheduler::ThreadNextTurn;
14240 use crate::tool_local::ExecFdBlockingOverrides;
14241 use crate::tool_local::ExecFdState;
14242 use crate::types::DetPid;
14243 use crate::types::DetTid;
14244 use crate::types::DetTime;
14245 use crate::types::FutexID;
14246 use crate::types::LogicalTime;
14247 use crate::types::MmId;
14248 use crate::types::Op;
14249 use crate::types::SchedEvent;
14250
14251 #[test]
14252 fn fdinfo_mount_ids_preserve_raw_equivalence_and_distinctness() {
14253 let mut pool = MountIdPool::from_config(&[10, 20, 30], true, &[]);
14254 assert_eq!(pool.determinize(20, None), Some(1));
14257
14258 assert_eq!(pool.determinize(700, None), Some(2));
14261 assert_eq!(pool.determinize(701, None), Some(3));
14262 assert_eq!(pool.determinize(702, None), Some(4));
14263 assert_eq!(pool.determinize(701, None), Some(3));
14264 assert_eq!(pool.determinize(20, None), Some(1));
14265 }
14266
14267 #[test]
14268 fn fdinfo_mount_ids_seed_from_low_level_snapshot_and_refuse_drift() {
14269 let mut pool = MountIdPool::from_config(&[], false, &[]);
14270 assert_eq!(pool.determinize(20, Some(&[10, 20, 30])), Some(1));
14271 assert_eq!(pool.determinize(700, Some(&[10, 20, 30])), Some(2));
14272 assert_eq!(pool.determinize(20, Some(&[10, 20, 30])), Some(1));
14273 assert_eq!(pool.determinize(20, Some(&[10, 99, 30])), None);
14274 assert_eq!(pool.determinize(20, Some(&[10, 20])), None);
14275 assert_eq!(pool.determinize(20, Some(&[10, 20, 30, 40])), None);
14276 }
14277
14278 #[test]
14283 fn mount_ids_do_not_depend_on_rows_the_guest_never_observed() {
14284 let observe = |mut pool: MountIdPool, visible: &[u64]| {
14285 vec![
14286 pool.determinize(700, None),
14287 pool.determinize(701, None),
14288 pool.determinize(20, None),
14289 ]
14290 .into_iter()
14291 .chain(
14292 pool.resolve_mountinfo(visible, visible)
14293 .unwrap()
14294 .into_iter()
14295 .map(Some),
14296 )
14297 .collect::<Vec<_>>()
14298 };
14299 let quiet = observe(
14300 MountIdPool::from_config(&[10, 20, 30], true, &[]),
14301 &[10, 30],
14302 );
14303 let churned = observe(
14304 MountIdPool::from_config(&[10, 20, 25, 26, 27, 30], true, &[]),
14305 &[10, 30],
14306 );
14307 assert_eq!(quiet, [Some(1), Some(2), Some(3), Some(4), Some(5)]);
14308 assert_eq!(quiet, churned);
14309
14310 let mut quiet = MountIdPool::from_config(&[], false, &[]);
14313 let mut churned = MountIdPool::from_config(&[], false, &[]);
14314 assert_eq!(quiet.determinize(700, Some(&[10, 20, 30])), Some(1));
14315 assert_eq!(
14316 churned.determinize(700, Some(&[10, 20, 25, 26, 27, 30])),
14317 Some(1)
14318 );
14319 assert_eq!(quiet.determinize(701, Some(&[10, 20, 30])), Some(2));
14320 assert_eq!(
14321 churned.determinize(701, Some(&[10, 20, 25, 26, 27, 30])),
14322 Some(2)
14323 );
14324 }
14325
14326 #[test]
14327 fn mountinfo_views_keep_earlier_numbers_and_number_the_rest_in_row_order() {
14328 let mut pool = MountIdPool::from_config(&[10, 20, 30, 40], true, &[]);
14329 assert_eq!(pool.determinize(30, None), Some(1));
14330 assert_eq!(pool.determinize(700, None), Some(2));
14331 assert_eq!(
14332 pool.resolve_mountinfo(&[10, 20, 30], &[10, 20, 30]),
14333 Some(vec![3, 4, 1])
14334 );
14335 assert_eq!(
14336 pool.resolve_mountinfo(&[20, 40], &[20, 40]),
14337 Some(vec![4, 5])
14338 );
14339 assert_eq!(pool.resolve_mountinfo(&[40, 10], &[40, 10]), None);
14341 assert_eq!(pool.resolve_mountinfo(&[10, 99], &[10, 99]), None);
14342 assert_eq!(pool.determinize(701, None), Some(6));
14343 assert_eq!(pool.determinize(10, None), Some(3));
14344 }
14345
14346 #[test]
14351 fn captured_namespace_order_never_decides_mountinfo_numbers() {
14352 let mut parent_first = MountIdPool::from_config(&[10, 20, 9], true, &[]);
14353 let mut parent_last = MountIdPool::from_config(&[20, 10], true, &[]);
14354 assert_eq!(
14355 parent_first.resolve_mountinfo(&[10, 20], &[20, 10]),
14356 Some(vec![1, 2])
14357 );
14358 assert_eq!(
14359 parent_last.resolve_mountinfo(&[20, 10], &[20, 10]),
14360 Some(vec![1, 2])
14361 );
14362 assert_eq!(parent_first.determinize(10, None), Some(2));
14363 assert_eq!(parent_last.determinize(10, None), Some(2));
14364 }
14365
14366 #[test]
14367 fn mountinfo_observation_order_must_name_exactly_the_canonical_ids() {
14368 for (canonical, observation) in [
14369 (&[10, 20][..], &[20][..]),
14370 (&[10, 20][..], &[20, 30][..]),
14371 (&[10, 20][..], &[20, 20][..]),
14372 (&[10, 20][..], &[10, 20, 30][..]),
14373 ] {
14374 let mut captured = MountIdPool::from_config(&[10, 20, 30], true, &[]);
14375 assert_eq!(captured.resolve_mountinfo(canonical, observation), None);
14376 assert_eq!(captured.determinize(30, None), Some(1));
14377
14378 let mut uncaptured = MountIdPool::from_config(&[], false, &[]);
14380 assert_eq!(uncaptured.resolve_mountinfo(canonical, observation), None);
14381 assert!(matches!(uncaptured, MountIdPool::Uninitialized));
14382 }
14383 }
14384
14385 #[test]
14386 fn mountinfo_reads_seed_once_and_refuse_later_table_changes() {
14387 let mut pool = MountIdPool::from_config(&[], false, &[]);
14388 assert!(pool.validate_mountinfo_order(&[10, 20, 30]));
14389 assert!(pool.validate_mountinfo_order(&[10, 20, 30]));
14390 assert!(!pool.validate_mountinfo_order(&[10, 99, 30]));
14391
14392 let mut empty = MountIdPool::from_config(&[], false, &[]);
14393 assert!(empty.validate_mountinfo_order(&[]));
14394 assert!(!empty.validate_mountinfo_order(&[10]));
14395 }
14396
14397 #[test]
14398 fn configured_mountinfo_order_accepts_only_ordered_known_subsets() {
14399 let mut pool = MountIdPool::from_config(&[10, 20, 30, 40], true, &[]);
14400 assert!(pool.validate_mountinfo_order(&[10, 30, 40]));
14401 assert_eq!(pool.determinize(30, None), Some(1));
14402 assert!(pool.validate_mountinfo_order(&[20, 40]));
14403 assert!(!pool.validate_mountinfo_order(&[30, 20]));
14404 assert!(!pool.validate_mountinfo_order(&[10, 10]));
14405 }
14406
14407 #[test]
14408 fn configured_mountinfo_order_refuses_new_namespace_ids() {
14409 let mut pool = MountIdPool::from_config(&[10, 20, 30, 40], true, &[]);
14410 assert!(pool.validate_mountinfo_order(&[10, 30, 40]));
14411 assert!(
14412 !pool.validate_mountinfo_order(&[10, 99, 40]),
14413 "a namespace-local mount ID absent from captured provenance must fail closed"
14414 );
14415 }
14416
14417 #[test]
14418 fn fdinfo_mount_ids_refuse_malformed_configured_provenance() {
14419 let mut pool = MountIdPool::from_config(&[10, 10], true, &[]);
14420 assert_eq!(pool.determinize(10, None), None);
14421 let mut pool = MountIdPool::from_config(&[10], true, &[700, 700]);
14422 assert_eq!(pool.determinize(10, None), None);
14423 let mut pool = MountIdPool::from_config(&[], false, &[700]);
14426 assert_eq!(pool.determinize(700, Some(&[10])), None);
14427 assert!(pool.provenance().is_err());
14428 let mut pool = MountIdPool::from_config(&[10], true, &[0]);
14431 assert_eq!(pool.determinize(10, None), None);
14432 assert!(pool.provenance().is_err());
14433 let mut pool = MountIdPool::from_config(&[10, 0], true, &[10, 0]);
14434 assert_eq!(pool.resolve_mountinfo(&[10, 0], &[10, 0]), Some(vec![1, 2]));
14435 assert_eq!(pool.determinize(0, None), Some(0));
14436 }
14437
14438 #[test]
14439 fn raw_zero_is_one_mount_identity_without_descriptor_type_partitioning() {
14440 let mut pool = MountIdPool::from_config(&[10, 0], true, &[]);
14441 assert_eq!(pool.determinize(0, None), Some(0));
14446 assert_eq!(pool.determinize(20, None), Some(1));
14447 assert_eq!(pool.determinize(0, None), Some(0));
14448 }
14449
14450 #[tokio::test]
14451 async fn completing_an_unreported_exit_retires_it_and_records_determinism_loss() {
14452 if !crate::scheduler::exec_teardown_tests::in_isolated_log_test(
14453 module_path!(),
14454 "completing_an_unreported_exit_retires_it_and_records_determinism_loss",
14455 ) {
14456 return;
14457 }
14458 use std::os::unix::process::ExitStatusExt;
14459
14460 use super::open_process_pidfd;
14461 use super::wait_for_process_exit;
14462
14463 struct Reap(std::process::Child);
14464 impl Drop for Reap {
14465 fn drop(&mut self) {
14466 let _ = self.0.kill();
14467 let _ = self.0.wait();
14468 }
14469 }
14470 let mut child = Reap(
14471 std::process::Command::new("sleep")
14472 .arg("30")
14473 .spawn()
14474 .unwrap(),
14475 );
14476 let physical_pid = child.0.id() as i32;
14477 let pidfd = open_process_pidfd(physical_pid).expect("a live child has a process pidfd");
14478 let config = Config {
14479 sequentialize_threads: true,
14480 ..Config::default()
14481 }
14482 .with_backend(|backend| {
14483 backend.process_exits_complete_asynchronously = true;
14484 backend.needs_killed_thread_rpc_cancellation = true;
14485 });
14486 let state = GlobalState::initialize(&config, false);
14487 let dettid = DetTid::from_raw(41);
14488 let mm = MmId::initial(dettid);
14489 assert_ne!(dettid.as_raw(), physical_pid);
14490 let pending_request = {
14491 let mut sched = state.sched.lock().unwrap();
14492 sched.thread_tree.add_child(dettid, dettid, true);
14493 sched.record_thread_mm(dettid, mm);
14494 sched
14495 .register_physical_thread(dettid, mm, physical_pid, physical_pid)
14496 .unwrap();
14497 sched.next_turns.insert(
14498 dettid,
14499 crate::scheduler::ThreadNextTurn {
14500 dettid,
14501 child_tid_addr: 0,
14502 req: Ivar::new(),
14503 resp: Ivar::new(),
14504 protocol: Default::default(),
14505 },
14506 );
14507 sched.next_turns[&dettid].req.clone()
14508 };
14509 assert!(pending_request.is_empty());
14510 child.0.kill().unwrap();
14511 assert!(
14512 wait_for_process_exit(pidfd, Duration::from_secs(5))
14513 .await
14514 .is_ok()
14515 );
14516
14517 state.complete_physical_process_exit(dettid.as_raw());
14520 assert!(pending_request.get().await.is_err());
14521 let sched = state.sched.lock().unwrap();
14522 assert!(!sched.next_turns.contains_key(&dettid));
14523 assert!(sched.process_physically_gone(DetPid::from_raw(41)));
14524 assert!(!sched.process_physically_gone(DetPid::from_raw(physical_pid)));
14525 drop(sched);
14526 assert_eq!(
14527 crate::detlog::determinism_loss().as_deref(),
14528 Some("process 41 exited without deregistering; its exit was not scheduled")
14529 );
14530 let status = child
14531 .0
14532 .wait()
14533 .expect("cleanup must leave the kernel status for the parent");
14534 assert_eq!(status.signal(), Some(libc::SIGKILL));
14535 assert_eq!(status.code(), None);
14536 }
14537
14538 #[tokio::test]
14539 async fn stale_process_exit_cannot_clear_replacement_and_normal_exit_adds_no_loss() {
14540 if !crate::scheduler::exec_teardown_tests::in_isolated_log_test(
14541 module_path!(),
14542 "stale_process_exit_cannot_clear_replacement_and_normal_exit_adds_no_loss",
14543 ) {
14544 return;
14545 }
14546 use nix::fcntl::OFlag;
14547
14548 use super::open_process_pidfd;
14549 use super::wait_for_process_exit;
14550 use crate::fd::DetFd;
14551 use crate::fd::FdType;
14552 use crate::shared_open_files::OpenFileControl;
14553 use crate::types::OpenFileId;
14554
14555 struct Reap(std::process::Child);
14556 impl Drop for Reap {
14557 fn drop(&mut self) {
14558 let _ = self.0.kill();
14559 let _ = self.0.wait();
14560 }
14561 }
14562 let mut child = Reap(
14563 std::process::Command::new("sleep")
14564 .arg("30")
14565 .spawn()
14566 .unwrap(),
14567 );
14568 let physical_pid = child.0.id() as i32;
14569 let pidfd = open_process_pidfd(physical_pid).unwrap();
14570 let config = Config {
14571 sequentialize_threads: true,
14572 backend: reverie::BackendCapabilities::DBT,
14573 ..Config::default()
14574 };
14575 let state = GlobalState::initialize(&config, false);
14576 let process = DetPid::from_raw(41);
14577 let old_mm = MmId::initial(process);
14578 let mm = old_mm.for_exec(process);
14579 let (old, current) = {
14580 let mut sched = state.sched.lock().unwrap();
14581 sched.thread_tree.add_child(process, process, true);
14582 sched.record_thread_mm(process, old_mm);
14583 sched.next_turns.insert(
14584 process,
14585 ThreadNextTurn {
14586 dettid: process,
14587 child_tid_addr: 0,
14588 req: Ivar::new(),
14589 resp: Ivar::new(),
14590 protocol: Default::default(),
14591 },
14592 );
14593 sched
14594 .register_physical_thread(process, old_mm, physical_pid, physical_pid)
14595 .unwrap();
14596 let old = sched.arm_process_exit_watch(
14597 process,
14598 old_mm,
14599 physical_pid,
14600 open_process_pidfd(physical_pid).unwrap(),
14601 );
14602 sched.record_thread_mm(process, mm);
14603 sched
14604 .register_physical_thread(process, mm, physical_pid, physical_pid)
14605 .unwrap();
14606 let current = sched.arm_process_exit_watch(
14607 process,
14608 mm,
14609 physical_pid,
14610 open_process_pidfd(physical_pid).unwrap(),
14611 );
14612 (old, current)
14613 };
14614 let pending = PendingExecState {
14615 caller: process,
14616 process,
14617 mm,
14618 fd_blocking: Default::default(),
14619 };
14620 state
14621 .pending_exec_states
14622 .lock()
14623 .unwrap()
14624 .insert(process, pending.clone());
14625 state
14626 .post_exec_fd_blocking
14627 .lock()
14628 .unwrap()
14629 .insert(process, ExecFdState::default());
14630 let id = OpenFileId::new(process, 0);
14631 {
14632 let mut files = state.shared_open_files.lock().unwrap();
14633 files
14634 .insert(DetFd::new(3, OFlag::O_RDWR, FdType::Regular, id).model_for_test())
14635 .unwrap();
14636 files
14637 .handle(process, process, OpenFileControl::Take { id })
14638 .unwrap();
14639 }
14640 child.0.kill().unwrap();
14641 assert!(
14642 wait_for_process_exit(pidfd, Duration::from_secs(5))
14643 .await
14644 .is_ok()
14645 );
14646 let cleanup = state.process_exit_cleanup();
14647 {
14648 let mut sched = state.sched.lock().unwrap();
14649 assert_eq!(cleanup.complete_observed_locked(&mut sched, &old), None);
14650 assert!(sched.next_turns.contains_key(&process));
14651 assert_eq!(
14652 state.pending_exec_states.lock().unwrap().get(&process),
14653 Some(&pending)
14654 );
14655 assert!(
14656 state
14657 .post_exec_fd_blocking
14658 .lock()
14659 .unwrap()
14660 .contains_key(&process)
14661 );
14662 assert!(state.shared_open_files.lock().unwrap().is_leased(id));
14663 assert_eq!(crate::detlog::determinism_loss(), None);
14664
14665 state.account_deregistered_thread(
14668 &mut sched,
14669 ThreadDeregistration {
14670 dettid: process,
14671 detpid: process,
14672 mm,
14673 thread_start_entered: true,
14674 vfork_origin: None,
14675 timeslice_stats: TimesliceStats::default(),
14676 syscall_count: 0,
14677 syscall_histogram: Default::default(),
14678 chaos_epochs: Vec::new(),
14679 killed_by_group_signal: false,
14680 passed_descriptors_out: false,
14681 },
14682 );
14683 assert_eq!(
14684 cleanup.complete_observed_locked(&mut sched, ¤t),
14685 Some((false, None))
14686 );
14687 assert_eq!(cleanup.complete_observed_locked(&mut sched, ¤t), None);
14688 }
14689 assert!(state.pending_exec_states.lock().unwrap().is_empty());
14690 assert!(state.post_exec_fd_blocking.lock().unwrap().is_empty());
14691 assert!(!state.shared_open_files.lock().unwrap().is_leased(id));
14692 assert_eq!(crate::detlog::determinism_loss(), None);
14693 assert!(child.0.wait().unwrap().code().is_none());
14694 }
14695
14696 #[test]
14701 fn completing_a_process_that_never_registered_records_it_gone() {
14702 let config = Config {
14703 sequentialize_threads: true,
14704 ..Config::default()
14705 }
14706 .with_backend(|backend| {
14707 backend.process_exits_complete_asynchronously = true;
14708 backend.needs_killed_thread_rpc_cancellation = true;
14709 });
14710 let state = GlobalState::initialize(&config, false);
14711 state.complete_physical_process_exit(57);
14712 let sched = state.sched.lock().unwrap();
14713 assert!(sched.process_physically_gone(DetPid::from_raw(57)));
14714 assert!(!sched.thread_tree.contains(&DetTid::from_raw(57)));
14715 assert!(!sched.next_turns.contains_key(&DetTid::from_raw(57)));
14716 }
14717
14718 #[test]
14722 fn detcore_lets_the_backend_skip_empty_signal_boundaries() {
14723 for (signal_control, sequentialize_threads) in
14724 [(true, true), (true, false), (false, true), (false, false)]
14725 {
14726 let config = Config {
14727 sequentialize_threads,
14728 ..Config::default()
14729 }
14730 .with_backend(|backend| {
14731 backend.provides_process_signal_control = signal_control;
14732 });
14733 let state = GlobalState::initialize(&config, false);
14734 assert!(state.skip_empty_signal_boundaries());
14735 }
14736 }
14737
14738 #[test]
14750 fn backend_exit_reports_take_the_path_the_backend_capabilities_select() {
14751 use reverie::BackendCapabilities;
14752 for (name, backend) in [
14753 ("PTRACE", BackendCapabilities::PTRACE),
14754 ("E9PATCH", BackendCapabilities::E9PATCH),
14755 ("LITEINST_IN_GUEST", BackendCapabilities::LITEINST_IN_GUEST),
14756 ] {
14757 let publishes = backend.reports_child_exit_publication;
14758 assert_eq!(publishes, name != "LITEINST_IN_GUEST", "{name}");
14759 let config = Config {
14760 sequentialize_threads: true,
14761 backend,
14762 ..Config::default()
14763 };
14764 let state = GlobalState::initialize(&config, false);
14765 let detpid = DetPid::from_raw(43);
14766 let leader = DetTid::from_raw(43);
14767 state.pending_exec_states.lock().unwrap().insert(
14768 detpid,
14769 PendingExecState {
14770 caller: leader,
14771 process: detpid,
14772 mm: MmId::initial(detpid),
14773 fd_blocking: Default::default(),
14774 },
14775 );
14776 state.post_exec_fd_blocking.lock().unwrap().insert(
14777 leader,
14778 ExecFdState::from([42].into_iter().collect::<ExecFdBlockingOverrides>()),
14779 );
14780 {
14781 let mut sched = state.sched.lock().unwrap();
14782 sched.install_test_child_exit_publication_hold(detpid);
14783 assert_eq!(
14786 sched.begin_physical_process_exit(detpid),
14787 !publishes,
14788 "{name}"
14789 );
14790 }
14791
14792 <GlobalState as GlobalTool>::on_backend_process_exited(&state, detpid.as_raw());
14793
14794 let sched = state.sched.lock().unwrap();
14795 let exec_state_kept = state
14796 .pending_exec_states
14797 .lock()
14798 .unwrap()
14799 .contains_key(&detpid);
14800 let fd_blocking_kept = state
14801 .post_exec_fd_blocking
14802 .lock()
14803 .unwrap()
14804 .contains_key(&leader);
14805 if publishes {
14806 assert_eq!(
14808 sched.test_child_exit_publication_state(detpid),
14809 (false, false),
14810 "{name}"
14811 );
14812 assert!(!sched.process_physically_gone(detpid), "{name}");
14813 assert!(exec_state_kept, "{name}");
14814 assert!(fd_blocking_kept, "{name}");
14815 } else {
14816 assert!(sched.pending_async_process_exits().is_empty(), "{name}");
14817 assert!(sched.process_physically_gone(detpid), "{name}");
14818 assert!(!exec_state_kept, "{name}");
14819 assert!(!fd_blocking_kept, "{name}");
14820 assert_eq!(
14822 sched.test_child_exit_publication_state(detpid),
14823 (true, false),
14824 "{name}"
14825 );
14826 }
14827 }
14828 }
14829
14830 #[test]
14831 fn recorded_assignment_order_rebuilds_the_same_mapping_in_any_replay_order() {
14832 let mut recording = MountIdPool::from_config(&[], false, &[]);
14833 assert_eq!(recording.determinize(700, Some(&[10, 20])), Some(1));
14834 assert_eq!(
14835 recording.resolve_mountinfo(&[10, 20], &[10, 20]),
14836 Some(vec![2, 3])
14837 );
14838 assert_eq!(recording.determinize(701, None), Some(4));
14839 let provenance = recording.provenance().unwrap().unwrap();
14840 assert_eq!(provenance.mountinfo_order, [10, 20]);
14841 assert_eq!(provenance.assignment_order, [700, 10, 20, 701]);
14842
14843 let mut replay = MountIdPool::from_config(
14846 &provenance.mountinfo_order,
14847 true,
14848 &provenance.assignment_order,
14849 );
14850 assert_eq!(replay.determinize(701, None), Some(4));
14851 assert_eq!(replay.resolve_mountinfo(&[20], &[20]), Some(vec![3]));
14852 assert_eq!(replay.determinize(10, None), Some(2));
14853 assert_eq!(replay.determinize(700, None), Some(1));
14854 assert_eq!(
14855 replay.provenance().unwrap().unwrap().assignment_order,
14856 provenance.assignment_order
14857 );
14858 }
14859
14860 #[tokio::test]
14861 async fn plain_replay_registration_records_actual_root_clone_and_vfork_policy() {
14862 use crate::preemptions::PreemptionRecord;
14863 use crate::preemptions::boundary_recording::Recording;
14864 use crate::scheduler::runqueue::REPLAY_DEFERRED_PRIORITY;
14865 use crate::scheduler::runqueue::REPLAY_FOREGROUND_PRIORITY;
14866 use crate::types::SyscallPhase;
14867 let root = DetTid::from_raw(3);
14868 let clone = DetTid::from_raw(5);
14869 let vfork = DetTid::from_raw(7);
14870 let directory = tempfile::tempdir().unwrap();
14871 let path = directory.path().join("schedule.json");
14872 Recording::Legacy(PreemptionRecord::from_sched_events(vec![
14873 SchedEvent::syscall(root, Sysno::getpid, SyscallPhase::Prehook),
14874 ]))
14875 .write(&path)
14876 .unwrap();
14877 let config = Config {
14878 sequentialize_threads: false,
14879 replay_schedule_from: Some(path),
14880 record_preemptions: true,
14881 ..Config::default()
14882 };
14883 let state = GlobalState::initialize(&config, false);
14884 let mm = MmId::initial(root);
14885 let before = {
14886 let sched = state.sched.lock().unwrap();
14887 (sched.turn, sched.committed_time)
14888 };
14889 for (child, flags, is_vfork, sender, priority) in [
14890 (root, None, false, root, REPLAY_FOREGROUND_PRIORITY),
14891 (
14892 clone,
14893 Some(CloneFlags::CLONE_THREAD | CloneFlags::CLONE_VM | CloneFlags::CLONE_SIGHAND),
14894 false,
14895 root,
14896 REPLAY_DEFERRED_PRIORITY,
14897 ),
14898 (
14899 vfork,
14900 Some(CloneFlags::CLONE_VFORK | CloneFlags::CLONE_VM),
14901 true,
14902 vfork,
14903 REPLAY_DEFERRED_PRIORITY,
14904 ),
14905 ] {
14906 assert_eq!(
14907 state
14908 .recv_create_child_thread(
14909 Tid::from_raw(sender.as_raw()),
14910 mm,
14911 super::ChildRegistration {
14912 parent_dettid: root,
14913 parent_detpid: root,
14914 child_dettid: child,
14915 child_tid_addr: 0,
14916 flags,
14917 exit_signal: libc::SIGCHLD,
14918 physical_ids: None,
14919 maybe_priority: Some(priority),
14920 parent_is_kernel_blocked: is_vfork,
14921 }
14922 )
14923 .await,
14924 SchedulerRpcResult::Continue(())
14925 );
14926 let sched = state.sched.lock().unwrap();
14927 assert_eq!(sched.priorities[&child], priority);
14928 assert_eq!(
14929 sched
14930 .preemption_writer
14931 .as_ref()
14932 .unwrap()
14933 .current_priority(child),
14934 if is_vfork {
14935 DEFAULT_PRIORITY - 1
14936 } else {
14937 DEFAULT_PRIORITY
14938 }
14939 );
14940 assert_eq!((sched.turn, sched.committed_time), before);
14941 }
14942 }
14943
14944 #[tokio::test]
14945 async fn boundary_owner_is_allocated_by_actual_root_registration() {
14946 for current_image in [false, true] {
14947 let config = Config {
14948 sequentialize_threads: false,
14949 ..Config::default()
14950 };
14951 let state = GlobalState::initialize(&config, false);
14952 state.sched.lock().unwrap().boundary_owners =
14953 Some(crate::slice_boundary::lifecycle::Registry::default());
14954 let root = DetTid::from_raw(3);
14955 let mm = if current_image {
14956 MmId::initial(root).for_exec(root)
14957 } else {
14958 MmId::initial(root)
14959 };
14960 let before = {
14961 let scheduler = state.sched.lock().unwrap();
14962 (scheduler.turn, scheduler.committed_time)
14963 };
14964 assert_eq!(
14965 state
14966 .recv_create_child_thread(
14967 Tid::from_raw(root.as_raw()),
14968 mm,
14969 super::ChildRegistration {
14970 parent_dettid: root,
14971 parent_detpid: root,
14972 child_dettid: root,
14973 child_tid_addr: 0,
14974 flags: None,
14975 exit_signal: libc::SIGCHLD,
14976 physical_ids: None,
14977 maybe_priority: Some(DEFAULT_PRIORITY),
14978 parent_is_kernel_blocked: false,
14979 }
14980 )
14981 .await,
14982 SchedulerRpcResult::Continue(())
14983 );
14984 let scheduler = state.sched.lock().unwrap();
14985 let owner = scheduler
14986 .boundary_owners
14987 .as_ref()
14988 .unwrap()
14989 .owner(root, mm)
14990 .unwrap();
14991 assert_eq!(owner.tid, root);
14992 assert_eq!(owner.birth_tid, root);
14993 assert_eq!(owner.birth_generation, 0);
14994 assert_eq!(owner.thread_generation, 0);
14995 assert_eq!(owner.address_space_creator, mm.creator());
14996 assert_eq!(owner.address_space_generation, mm.generation());
14997 assert_eq!((scheduler.turn, scheduler.committed_time), before);
14998 }
14999 }
15000
15001 #[tokio::test]
15002 async fn initial_current_root_registration_is_consistent_and_single_use() {
15003 let config = Config {
15004 sequentialize_threads: false,
15005 ..Config::default()
15006 }
15007 .with_backend(|backend| backend.tool_shares_guest_descriptor_table = true);
15008 let mut state = GlobalState::initialize(&config, false);
15009 let pipe = reverie::InheritedInput::pipe(0).unwrap();
15010 state.install_inherited_input(pipe.lease().unwrap());
15011 let root = DetTid::from_raw(3);
15012 let initial = MmId::initial(root);
15013 let current = initial.for_exec(root);
15014 let registration = || super::ChildRegistration {
15015 parent_dettid: root,
15016 parent_detpid: root,
15017 child_dettid: root,
15018 child_tid_addr: 0,
15019 flags: None,
15020 exit_signal: libc::SIGCHLD,
15021 physical_ids: None,
15022 maybe_priority: Some(DEFAULT_PRIORITY),
15023 parent_is_kernel_blocked: false,
15024 };
15025 assert!(matches!(
15026 state
15027 .recv_create_child_thread(Tid::from_raw(root.as_raw()), current, registration())
15028 .await,
15029 SchedulerRpcResult::Continue(())
15030 ));
15031 assert_eq!(state.root_dettid.get(), Some(&root));
15032 assert_eq!(
15033 state.inherited_input.lock().unwrap().as_ref().unwrap().root,
15034 Some((root, current))
15035 );
15036 let response = state
15039 .receive_rpc(
15040 Tid::from_raw(root.as_raw()),
15041 (
15042 DetTime::new(&config),
15043 initial,
15044 GlobalRequest::MarkPastFirstExecve(root, None, None, None),
15045 ),
15046 )
15047 .await
15048 .1;
15049 assert_eq!(response, GlobalResponse::ThreadExited);
15050 assert!(state.inherited_input.lock().unwrap().is_some());
15051 let response = state
15052 .receive_rpc(
15053 Tid::from_raw(root.as_raw()),
15054 (
15055 DetTime::new(&config),
15056 current,
15057 GlobalRequest::MarkPastFirstExecve(root, None, None, None),
15058 ),
15059 )
15060 .await
15061 .1;
15062 let GlobalResponse::MarkPastFirstExecve(completion) = response else {
15063 panic!("{response:?}")
15064 };
15065 assert_eq!(completion.initial_input.as_ref(), Some(pipe.identity()));
15066 assert!(state.inherited_input.lock().unwrap().is_none());
15067 {
15068 let sched = state.sched.lock().unwrap();
15069 assert_eq!(sched.thread_mm(root), Some(current));
15070 assert!(sched.rpc_incarnation_matches(root, current));
15071 assert!(!sched.rpc_incarnation_matches(root, initial));
15072 assert!(sched.thread_was_registered(root));
15073 }
15074 assert!(matches!(
15075 state
15076 .recv_create_child_thread(Tid::from_raw(root.as_raw()), current, registration())
15077 .await,
15078 SchedulerRpcResult::ThreadExited
15079 ));
15080 assert_eq!(state.sched.lock().unwrap().thread_mm(root), Some(current));
15081 }
15082
15083 #[tokio::test]
15084 async fn boundary_same_leader_exec_completion_returns_actual_new_owner_without_a_turn() {
15085 let (config, state, tid, process) = cancellation_test_state();
15086 install_test_registration(&state, tid, Ivar::new());
15087 let old_mm = MmId::initial(tid);
15088 let new_mm = old_mm.for_exec(tid);
15089 let old_owner = {
15090 let mut scheduler = state.sched.lock().unwrap();
15091 scheduler.install_test_exec_incarnation(tid, old_mm);
15092 let mut owners = crate::slice_boundary::lifecycle::Registry::default();
15093 let owner = owners.fresh(tid, old_mm).unwrap();
15094 scheduler.boundary_owners = Some(owners);
15095 owner
15096 };
15097 state.pending_exec_states.lock().unwrap().insert(
15098 process,
15099 PendingExecState {
15100 caller: tid,
15101 process,
15102 mm: old_mm,
15103 fd_blocking: Default::default(),
15104 },
15105 );
15106 let before = {
15107 let scheduler = state.sched.lock().unwrap();
15108 (scheduler.turn, scheduler.committed_time)
15109 };
15110 let response = state
15111 .receive_rpc(
15112 Tid::from_raw(tid.as_raw()),
15113 (
15114 DetTime::new(&config),
15115 new_mm,
15116 GlobalRequest::MarkPastFirstExecve(process, None, None, None),
15117 ),
15118 )
15119 .await;
15120 let GlobalResponse::WithSliceBoundaryOwner(owner, response) = response.1 else {
15121 panic!("lost actual exec owner")
15122 };
15123 assert!(matches!(*response, GlobalResponse::MarkPastFirstExecve(_)));
15124 assert_eq!(
15125 (owner.birth_tid, owner.birth_generation),
15126 (old_owner.birth_tid, old_owner.birth_generation)
15127 );
15128 assert_eq!(owner.thread_generation, old_owner.thread_generation);
15129 assert_eq!(owner.address_space_generation, new_mm.generation());
15130 let scheduler = state.sched.lock().unwrap();
15131 assert_eq!(
15132 scheduler
15133 .boundary_owners
15134 .as_ref()
15135 .unwrap()
15136 .owner(tid, new_mm)
15137 .unwrap(),
15138 owner
15139 );
15140 assert!(
15141 scheduler
15142 .boundary_owners
15143 .as_ref()
15144 .unwrap()
15145 .owner(tid, old_mm)
15146 .is_err()
15147 );
15148 assert_eq!((scheduler.turn, scheduler.committed_time), before);
15149 }
15150
15151 #[tokio::test]
15152 async fn initial_current_root_rejects_stale_nonroot_and_wrong_incarnations() {
15153 let root = DetTid::from_raw(3);
15154 let other = DetTid::from_raw(4);
15155 let current = MmId::initial(root).for_exec(root);
15156 for case in 0..8 {
15157 let config = Config {
15158 sequentialize_threads: false,
15159 ..Config::default()
15160 };
15161 let state = GlobalState::initialize(&config, false);
15162 let mut registration = super::ChildRegistration {
15163 parent_dettid: root,
15164 parent_detpid: root,
15165 child_dettid: root,
15166 child_tid_addr: 0,
15167 flags: None,
15168 exit_signal: libc::SIGCHLD,
15169 physical_ids: None,
15170 maybe_priority: Some(DEFAULT_PRIORITY),
15171 parent_is_kernel_blocked: false,
15172 };
15173 let mut sender = root;
15174 let mut mm = current;
15175 match case {
15176 0 => sender = other,
15177 1 => registration.parent_dettid = other,
15178 2 => registration.parent_detpid = other,
15179 3 => registration.child_tid_addr = 8,
15180 4 => registration.parent_is_kernel_blocked = true,
15181 5 => mm = current.for_exec(root),
15182 6 => mm = MmId::initial(other).for_exec(other),
15183 7 => {
15184 sender = other;
15187 state
15188 .sched
15189 .lock()
15190 .unwrap()
15191 .thread_tree
15192 .add_child(other, other, true);
15193 }
15194 _ => unreachable!(),
15195 }
15196 assert!(matches!(
15197 state
15198 .recv_create_child_thread(Tid::from_raw(sender.as_raw()), mm, registration)
15199 .await,
15200 SchedulerRpcResult::ThreadExited
15201 ));
15202 assert!(state.root_dettid.get().is_none());
15203 assert_eq!(state.sched.lock().unwrap().thread_mm(root), None);
15204 }
15205 }
15206
15207 #[tokio::test]
15208 async fn registered_parent_creation_preserves_the_child_mm_after_initial_image_birth() {
15209 let parent = DetTid::from_raw(3);
15210 let child = DetTid::from_raw(4);
15211 for parent_mm in [
15212 MmId::initial(parent),
15213 MmId::initial(parent).for_exec(parent),
15214 ] {
15215 for flags in [None, Some(CloneFlags::empty())] {
15216 let config = Config {
15217 sequentialize_threads: false,
15218 ..Config::default()
15219 };
15220 let state = GlobalState::initialize(&config, false);
15221 let registration = |child_dettid, flags| super::ChildRegistration {
15222 parent_dettid: parent,
15223 parent_detpid: parent,
15224 child_dettid,
15225 child_tid_addr: 0,
15226 flags,
15227 exit_signal: libc::SIGCHLD,
15228 physical_ids: None,
15229 maybe_priority: Some(DEFAULT_PRIORITY),
15230 parent_is_kernel_blocked: false,
15231 };
15232 assert!(matches!(
15233 state
15234 .recv_create_child_thread(
15235 Tid::from_raw(parent.as_raw()),
15236 parent_mm,
15237 registration(parent, None),
15238 )
15239 .await,
15240 SchedulerRpcResult::Continue(())
15241 ));
15242 assert!(matches!(
15243 state
15244 .recv_create_child_thread(
15245 Tid::from_raw(parent.as_raw()),
15246 parent_mm,
15247 registration(child, flags),
15248 )
15249 .await,
15250 SchedulerRpcResult::Continue(())
15251 ));
15252 let sched = state.sched.lock().unwrap();
15253 assert_eq!(sched.thread_mm(parent), Some(parent_mm));
15254 assert_eq!(sched.thread_mm(child), Some(MmId::initial(child)));
15255 assert!(sched.thread_was_registered(parent));
15256 assert!(sched.thread_was_registered(child));
15257 assert!(sched.rpc_incarnation_matches(parent, parent_mm));
15258 assert_eq!(state.root_dettid.get(), Some(&parent));
15259 }
15260 }
15261 }
15262
15263 #[tokio::test]
15272 async fn auxv_rows_follow_fork_failed_exec_and_exit_through_the_handlers() {
15273 use super::AuxvFromKernel;
15274 use super::ChildRegistration;
15275
15276 let config = Config {
15277 sequentialize_threads: false,
15278 ..Config::default()
15279 }
15280 .with_backend(|backend| backend.reports_child_exit_publication = true);
15281 let state = GlobalState::initialize(&config, false);
15282 let root = DetTid::from_raw(3);
15283 let (forked, shared) = (DetTid::from_raw(4), DetTid::from_raw(5));
15284 let root_mm = MmId::initial(root);
15285 assert!(matches!(
15286 state
15287 .recv_create_child_thread(
15288 Tid::from_raw(root.as_raw()),
15289 root_mm,
15290 ChildRegistration {
15291 parent_dettid: root,
15292 parent_detpid: root,
15293 child_dettid: root,
15294 child_tid_addr: 0,
15295 flags: None,
15296 exit_signal: libc::SIGCHLD,
15297 physical_ids: None,
15298 maybe_priority: Some(DEFAULT_PRIORITY),
15299 parent_is_kernel_blocked: false,
15300 },
15301 )
15302 .await,
15303 SchedulerRpcResult::Continue(())
15304 ));
15305 let rpc = |task: DetTid, mm: MmId, request: GlobalRequest| {
15306 state.receive_rpc(
15307 Tid::from_raw(task.as_raw()),
15308 (DetTime::new(&config), mm, request),
15309 )
15310 };
15311 let response = rpc(
15312 root,
15313 root_mm,
15314 GlobalRequest::RecordProcessAuxv(vec![7; 32], None),
15315 )
15316 .await
15317 .1;
15318 assert_eq!(response, GlobalResponse::RecordProcessAuxv(()));
15319 for (task, flags) in [
15320 (forked, CloneFlags::empty()),
15321 (shared, CloneFlags::CLONE_VM),
15322 ] {
15323 assert!(matches!(
15324 state
15325 .recv_create_child_thread(
15326 Tid::from_raw(root.as_raw()),
15327 root_mm,
15328 ChildRegistration {
15329 parent_dettid: root,
15330 parent_detpid: root,
15331 child_dettid: task,
15332 child_tid_addr: 0,
15333 flags: Some(flags),
15334 exit_signal: libc::SIGCHLD,
15335 physical_ids: None,
15336 maybe_priority: Some(DEFAULT_PRIORITY),
15337 parent_is_kernel_blocked: false,
15338 },
15339 )
15340 .await,
15341 SchedulerRpcResult::Continue(())
15342 ));
15343 }
15344 let current = |task: DetTid| {
15345 rpc(
15346 root,
15347 root_mm,
15348 GlobalRequest::ProcessAuxv(DetPid::from_raw(task.as_raw())),
15349 )
15350 };
15351 for task in [root, forked, shared] {
15352 let GlobalResponse::ProcessAuxv(Some(entry)) = current(task).await.1 else {
15353 panic!("{task} has no vector");
15354 };
15355 assert_eq!(entry.bytes, vec![7; 32], "{task}");
15356 }
15357 assert_eq!(state.process_auxv.lock().unwrap().len(), 3);
15358 let forked_mm = MmId::initial(forked);
15361 for first in [true, false] {
15362 let response = rpc(
15363 forked,
15364 forked_mm,
15365 GlobalRequest::AuxvFromKernel(AuxvFromKernel::Implausible, None),
15366 )
15367 .await
15368 .1;
15369 assert_eq!(response, GlobalResponse::AuxvFromKernel(first));
15370 }
15371 assert_eq!(current(forked).await.1, GlobalResponse::ProcessAuxv(None));
15372 assert_eq!(state.process_auxv.lock().unwrap().len(), 2);
15373 for task in [forked, shared, root] {
15375 state.on_backend_process_exited(task.as_raw());
15376 }
15377 assert_eq!(state.process_auxv.lock().unwrap().len(), 0);
15378 }
15379
15380 #[tokio::test]
15381 async fn inherited_input_belongs_only_to_the_registered_root_initial_image() {
15382 check_inherited_input_root_ownership(super::ROOT_DETPID, DetTid::from_raw(4)).await;
15383 }
15384
15385 #[tokio::test]
15386 async fn inherited_input_uses_registered_root_identity_instead_of_pid_three() {
15387 check_inherited_input_root_ownership(DetTid::from_raw(37), DetTid::from_raw(3)).await;
15389 }
15390
15391 async fn check_inherited_input_root_ownership(root: DetTid, child: DetTid) {
15392 use super::ChildRegistration;
15393
15394 let config = Config {
15395 sequentialize_threads: false,
15396 ..Config::default()
15397 }
15398 .with_backend(|backend| backend.tool_shares_guest_descriptor_table = true);
15399 let mut state = GlobalState::initialize(&config, false);
15400 let pipe = reverie::InheritedInput::pipe(0).unwrap();
15401 state.install_inherited_input(pipe.lease().unwrap());
15402 let worker = DetTid::from_raw(root.as_raw() + 2);
15403 let root_mm = MmId::initial(root);
15404 for (parent, task, flags) in [
15405 (root, root, None),
15406 (root, child, Some(CloneFlags::empty())),
15407 (
15408 root,
15409 worker,
15410 Some(CloneFlags::CLONE_VM | CloneFlags::CLONE_SIGHAND | CloneFlags::CLONE_THREAD),
15411 ),
15412 ] {
15413 assert!(matches!(
15414 state
15415 .recv_create_child_thread(
15416 Tid::from_raw(parent.as_raw()),
15417 root_mm,
15418 ChildRegistration {
15419 parent_dettid: parent,
15420 parent_detpid: root,
15421 child_dettid: task,
15422 child_tid_addr: 0,
15423 flags,
15424 exit_signal: libc::SIGCHLD,
15425 physical_ids: None,
15426 maybe_priority: Some(DEFAULT_PRIORITY),
15427 parent_is_kernel_blocked: false,
15428 },
15429 )
15430 .await,
15431 SchedulerRpcResult::Continue(())
15432 ));
15433 }
15434 assert_eq!(
15435 state.inherited_input.lock().unwrap().as_ref().unwrap().root,
15436 Some((root, root_mm))
15437 );
15438
15439 for (task, detpid, mm) in [
15442 (child, child, MmId::initial(child)),
15443 (worker, root, root_mm),
15444 ] {
15445 let response = state
15446 .receive_rpc(
15447 Tid::from_raw(task.as_raw()),
15448 (
15449 DetTime::new(&config),
15450 mm,
15451 GlobalRequest::MarkPastFirstExecve(detpid, None, None, None),
15452 ),
15453 )
15454 .await
15455 .1;
15456 let GlobalResponse::MarkPastFirstExecve(non_root_state) = response else {
15457 panic!("{response:?}")
15458 };
15459 assert!(non_root_state.initial_input.is_none());
15460 assert!(state.inherited_input.lock().unwrap().is_some());
15461 }
15462
15463 let response = state
15465 .receive_rpc(
15466 Tid::from_raw(root.as_raw()),
15467 (
15468 DetTime::new(&config),
15469 root_mm.for_exec(root),
15470 GlobalRequest::MarkPastFirstExecve(root, None, None, None),
15471 ),
15472 )
15473 .await
15474 .1;
15475 assert_eq!(
15479 response,
15480 GlobalResponse::MarkPastFirstExecve(Default::default())
15481 );
15482 assert!(state.inherited_input.lock().unwrap().is_some());
15483
15484 let response = state
15485 .receive_rpc(
15486 Tid::from_raw(root.as_raw()),
15487 (
15488 DetTime::new(&config),
15489 root_mm,
15490 GlobalRequest::MarkPastFirstExecve(root, None, None, None),
15491 ),
15492 )
15493 .await
15494 .1;
15495 let GlobalResponse::MarkPastFirstExecve(root_state) = response else {
15496 panic!("{response:?}")
15497 };
15498 assert_eq!(root_state.initial_input.as_ref(), Some(pipe.identity()));
15499 assert!(state.inherited_input.lock().unwrap().is_none());
15500 let response = state
15501 .receive_rpc(
15502 Tid::from_raw(root.as_raw()),
15503 (
15504 DetTime::new(&config),
15505 root_mm,
15506 GlobalRequest::MarkPastFirstExecve(root, None, None, None),
15507 ),
15508 )
15509 .await
15510 .1;
15511 let GlobalResponse::MarkPastFirstExecve(repeated) = response else {
15512 panic!("{response:?}")
15513 };
15514 assert!(repeated.initial_input.is_none());
15515
15516 let response = state
15519 .receive_rpc(
15520 Tid::from_raw(root.as_raw()),
15521 (
15522 DetTime::new(&config),
15523 root_mm,
15524 GlobalRequest::PrepareExec(root, root_mm, root_state),
15525 ),
15526 )
15527 .await
15528 .1;
15529 assert_eq!(response, GlobalResponse::ThreadExited);
15530 }
15531
15532 #[tokio::test]
15533 async fn failed_root_registration_does_not_bind_inherited_input() {
15534 for raw_root in [3, 37] {
15535 let config = Config {
15536 sequentialize_threads: false,
15537 ..Config::default()
15538 }
15539 .with_backend(|backend| backend.tool_shares_guest_descriptor_table = true);
15540 let mut state = GlobalState::initialize(&config, false);
15541 let pipe = reverie::InheritedInput::pipe(0).unwrap();
15542 state.install_inherited_input(pipe.lease().unwrap());
15543 let root = DetTid::from_raw(raw_root);
15544 let root_mm = MmId::initial(root);
15545 let response = state
15546 .recv_create_child_thread(
15547 Tid::from_raw(raw_root),
15548 root_mm,
15549 super::ChildRegistration {
15550 parent_dettid: root,
15551 parent_detpid: root,
15552 child_dettid: root,
15553 child_tid_addr: 0,
15554 flags: None,
15555 exit_signal: libc::SIGCHLD,
15556 physical_ids: Some((-1, -1)),
15559 maybe_priority: Some(DEFAULT_PRIORITY),
15560 parent_is_kernel_blocked: false,
15561 },
15562 )
15563 .await;
15564 assert!(matches!(response, SchedulerRpcResult::ThreadExited));
15565 let input = state.inherited_input.lock().unwrap();
15566 let input = input
15567 .as_ref()
15568 .expect("failed registration consumed the lease");
15569 assert!(input.root.is_none(), "failed registration bound the root");
15570 assert_eq!(input.lease.identity(), pipe.identity());
15571 }
15572 }
15573
15574 fn cancellation_test_state() -> (Config, GlobalState, DetTid, DetPid) {
15575 let config = Config {
15576 sequentialize_threads: true,
15577 ..Config::default()
15578 }
15579 .with_backend(|backend| {
15580 backend.needs_killed_thread_rpc_cancellation = true;
15581 });
15582 cancellation_test_state_with_config(config)
15583 }
15584
15585 fn cancellation_test_state_with_config(
15586 config: Config,
15587 ) -> (Config, GlobalState, DetTid, DetPid) {
15588 let state = GlobalState::initialize(&config, false);
15589 let dettid = DetTid::from_raw(17);
15590 let detpid = DetPid::from_raw(17);
15591 state
15592 .sched
15593 .lock()
15594 .unwrap()
15595 .thread_tree
15596 .add_child(dettid, dettid, true);
15597 state
15600 .sched
15601 .lock()
15602 .unwrap()
15603 .thread_tree
15604 .set_process_group(detpid, detpid);
15605 (config, state, dettid, detpid)
15606 }
15607
15608 fn install_test_registration(state: &GlobalState, dettid: DetTid, request: Ivar<SchedRequest>) {
15609 let mut scheduler = state.sched.lock().unwrap();
15610 assert!(!scheduler.thread_is_logically_killed(dettid));
15611 scheduler.next_turns.insert(
15612 dettid,
15613 ThreadNextTurn {
15614 dettid,
15615 child_tid_addr: 0,
15616 req: request,
15617 resp: Ivar::new(),
15618 protocol: Default::default(),
15619 },
15620 );
15621 scheduler.priorities.insert(dettid, DEFAULT_PRIORITY);
15622 scheduler.runqueue_push_back(dettid);
15623 }
15624
15625 async fn boundary_producer_roundtrip(
15629 enabled: bool,
15630 input: Option<&std::path::Path>,
15631 output: Option<&std::path::Path>,
15632 ) -> (Vec<Resources>, LogicalTime, LogicalTime, u64, u64) {
15633 use detcore_model::schedule::boundary::Entry;
15634 use detcore_model::schedule::boundary::Phase;
15635 use detcore_model::schedule::boundary::Record;
15636 use reverie::Tool;
15637
15638 use crate::preemptions::boundary_recording::Recording;
15639 let (config, state, tid, process) = if input.is_some() || output.is_some() {
15640 cancellation_test_state_with_config(
15641 Config {
15642 sequentialize_threads: true,
15643 replay_schedule_from: input.map(std::path::Path::to_path_buf),
15644 strict_schedule_boundaries: true,
15645 record_preemptions: true,
15646 record_preemptions_to: output.map(std::path::Path::to_path_buf),
15647 ..Config::default()
15648 }
15649 .with_backend(|backend| backend.needs_killed_thread_rpc_cancellation = true),
15650 )
15651 } else {
15652 cancellation_test_state()
15653 };
15654 install_test_registration(&state, tid, Ivar::new());
15655 let mut thread = crate::ThreadState::new(tid, &config, ());
15656 thread.detpid = Some(process);
15657 thread.next_timeslice(&config);
15658 if enabled {
15659 thread.slice_boundary = Some(crate::slice_boundary::Observations::new(&thread, 0));
15660 let mut scheduler = state.sched.lock().unwrap();
15661 let mut owners = crate::slice_boundary::lifecycle::Registry::default();
15662 owners.fresh(tid, thread.mm_id).unwrap();
15663 scheduler.boundary_owners = Some(owners);
15664 scheduler.record_thread_mm(tid, thread.mm_id);
15665 if input.is_some() {
15666 let budget = scheduler
15667 .boundary_budget_for_live_owner(tid)
15668 .unwrap()
15669 .unwrap();
15670 crate::slice_boundary::grant::install(&mut thread, budget).unwrap();
15671 }
15672 let mut writer =
15673 crate::preemptions::PreemptionWriter::new(output.map(std::path::Path::to_path_buf))
15674 .with_slice_boundaries()
15675 .unwrap();
15676 writer.register_thread(tid, DEFAULT_PRIORITY);
15677 scheduler.preemption_writer = Some(writer);
15678 }
15679 let mut guest = ExternalRegistrationGuest {
15680 global: &state,
15681 config: &config,
15682 thread,
15683 requests: Mutex::new(Vec::new()),
15684 post_exec: false,
15685 ppid: None,
15686 private_memory: None,
15687 };
15688 let tool: Detcore = Detcore::new(Tid::from_raw(tid.as_raw()), &config);
15689 let mut turns = Vec::new();
15690 for _ in 0..2 {
15691 if let Some(mut observed) = guest.thread.slice_boundary.take() {
15692 observed
15693 .callback(&guest.thread, Entry::Cpuid, Phase::Prehook)
15694 .unwrap();
15695 guest.thread.slice_boundary = Some(observed);
15696 }
15697 let mut request = Box::pin(tool.end_timeslice(&mut guest));
15698 for _ in 0..8 {
15699 assert!(futures::poll!(&mut request).is_pending());
15700 if state.sched.lock().unwrap().next_turns[&tid]
15701 .req
15702 .try_read()
15703 .is_some()
15704 {
15705 break;
15706 }
15707 }
15708 let posted = state.sched.lock().unwrap().next_turns[&tid]
15709 .req
15710 .try_read()
15711 .unwrap()
15712 .unwrap();
15713 assert!(
15714 posted.slice_boundary.is_none(),
15715 "stored/reposted resource must not retain an annotation"
15716 );
15717 assert!(posted.upgraded_to_runnable().slice_boundary.is_none());
15718 let turn = crate::scheduler::do_a_turn_blocking(
15719 state.sched.clone(),
15720 state.global_time.clone(),
15721 &Err(crate::scheduler::SkipTurn),
15722 )
15723 .await
15724 .expect("the original request must commit exactly one turn");
15725 assert!(turn.slice_boundary.is_none());
15726 assert!(
15727 turn.slice_expired,
15728 "a slice that ends because its time ran out marks its request"
15729 );
15730 turns.push(turn);
15731 request.await;
15732 }
15733 let mut scheduler = state.sched.lock().unwrap();
15734 if enabled {
15735 let writer = scheduler.preemption_writer.take().unwrap();
15736 let encoded = if let Some(path) = output {
15737 writer.flush().unwrap();
15738 std::fs::read_to_string(path).unwrap()
15739 } else {
15740 writer.into_string()
15741 };
15742 let Recording::Boundaries(recording) = Recording::decode(encoded.as_bytes()).unwrap()
15743 else {
15744 panic!("lost typed recording")
15745 };
15746 let records = recording.schedule().records();
15747 assert_eq!(records.len(), 2);
15748 for (index, record) in records.iter().enumerate() {
15749 let Record::TimesliceEnd(boundary) = record else {
15750 panic!("introduced an ordinary event")
15751 };
15752 assert_eq!(boundary.identity.slice, index as u64 + 1);
15753 assert_eq!(boundary.identity.checkin, index as u64 + 1);
15754 assert_eq!(boundary.identity.committed_rcbs, 0);
15755 assert_eq!(boundary.identity.entry, Entry::Cpuid);
15756 }
15757 assert_eq!(scheduler.recorded_event_count, 2);
15758 if input.is_some() {
15759 assert_eq!(
15760 scheduler
15761 .generate_partial_run_summary(None)
15762 .unwrap()
15763 .schedevent_replayed,
15764 2
15765 );
15766 }
15767 }
15768 (
15769 turns,
15770 scheduler.committed_time,
15771 guest.thread.thread_logical_time.as_nanos(),
15772 scheduler.turn,
15773 guest.thread.stats.timeslice_count,
15774 )
15775 }
15776
15777 #[tokio::test]
15778 async fn boundary_actual_reset_and_resource_transport_preserve_original_turns_and_clocks() {
15779 assert_eq!(
15780 boundary_producer_roundtrip(true, None, None).await,
15781 boundary_producer_roundtrip(false, None, None).await
15782 );
15783 }
15784
15785 #[tokio::test]
15786 async fn boundary_recorded_bytes_replay_two_original_yields_with_exact_clock_and_turn_parity() {
15787 let dir = tempfile::tempdir().unwrap();
15788 let first = dir.path().join("first.json");
15789 let replay = dir.path().join("replay.json");
15790 let recorded = boundary_producer_roundtrip(true, None, Some(&first)).await;
15791 let replayed = boundary_producer_roundtrip(true, Some(&first), Some(&replay)).await;
15792 assert_eq!(recorded, replayed);
15793 assert_eq!(
15794 std::fs::read(first).unwrap(),
15795 std::fs::read(replay).unwrap()
15796 );
15797 }
15798
15799 async fn boundary_sched_yield_handler(
15803 input: Option<&std::path::Path>,
15804 typed: bool,
15805 chaos: bool,
15806 timers: bool,
15807 stale_owner: bool,
15808 ) -> (
15809 crate::preemptions::boundary_recording::Recording,
15810 LogicalTime,
15811 LogicalTime,
15812 u64,
15813 u64,
15814 ) {
15815 use detcore_model::schedule::boundary::Cause;
15816 use detcore_model::schedule::boundary::Entry;
15817 use detcore_model::schedule::boundary::Phase;
15818 use detcore_model::schedule::boundary::Record;
15819 use reverie::Tool;
15820
15821 use crate::preemptions::boundary_recording::Recording;
15822
15823 let (config, state, tid, process) = cancellation_test_state_with_config(
15824 Config {
15825 sequentialize_threads: true,
15826 replay_schedule_from: input.map(std::path::Path::to_path_buf),
15827 strict_schedule_boundaries: typed && !chaos && input.is_some(),
15828 chaos,
15829 max_timeslice: if timers {
15830 Config::default().max_timeslice
15831 } else {
15832 None
15833 },
15834 target_timeslice: None,
15835 ..Config::default()
15836 }
15837 .with_backend(|backend| backend.needs_killed_thread_rpc_cancellation = true),
15838 );
15839 install_test_registration(&state, tid, Ivar::new());
15840 let mut thread = crate::ThreadState::new(tid, &config, ());
15841 thread.detpid = Some(process);
15842 thread.next_timeslice(&config);
15843 let initial_slices = thread.stats.timeslice_count;
15844 {
15845 let mut scheduler = state.sched.lock().unwrap();
15846 let mut writer = crate::preemptions::PreemptionWriter::new(None);
15847 if typed {
15848 thread.slice_boundary = Some(crate::slice_boundary::Observations::new(&thread, 0));
15849 let mut owners = crate::slice_boundary::lifecycle::Registry::default();
15850 owners.fresh(tid, thread.mm_id).unwrap();
15851 scheduler.boundary_owners = Some(owners);
15852 scheduler.record_thread_mm(tid, thread.mm_id);
15853 if let Some(budget) = scheduler.boundary_budget_for_live_owner(tid).unwrap() {
15854 crate::slice_boundary::grant::install(&mut thread, budget).unwrap();
15855 }
15856 writer = writer.with_slice_boundaries().unwrap();
15857 }
15858 writer.register_thread(tid, DEFAULT_PRIORITY);
15859 scheduler.preemption_writer = Some(writer);
15860 }
15861 let mut guest = ExternalRegistrationGuest {
15862 global: &state,
15863 config: &config,
15864 thread,
15865 requests: Mutex::new(Vec::new()),
15866 post_exec: false,
15867 ppid: None,
15868 private_memory: None,
15869 };
15870 let tool: Detcore = Detcore::new(Tid::from_raw(tid.as_raw()), &config);
15871 for occurrence in 1..=2 {
15872 if let Some(mut observed) = guest.thread.slice_boundary.take() {
15873 observed
15874 .syscall(&guest.thread, Sysno::sched_yield, Phase::Prehook)
15875 .unwrap();
15876 guest.thread.slice_boundary = Some(observed);
15877 }
15878 if stale_owner {
15879 guest.thread.mm_id = guest.thread.mm_id.for_exec(tid);
15880 }
15881 let before_turn = state.sched.lock().unwrap().turn;
15882 let before_requests = guest.requests.lock().unwrap().len();
15883 let mut request = Box::pin(
15884 tool.handle_sched_yield(&mut guest, reverie::syscalls::SchedYield::default()),
15885 );
15886 for _ in 0..8 {
15887 assert!(futures::poll!(&mut request).is_pending());
15888 if state.sched.lock().unwrap().next_turns[&tid]
15889 .req
15890 .try_read()
15891 .is_some()
15892 {
15893 break;
15894 }
15895 }
15896 let posted = state.sched.lock().unwrap().next_turns[&tid]
15897 .req
15898 .try_read()
15899 .unwrap()
15900 .unwrap();
15901 assert!(posted.slice_boundary.is_none());
15902 assert!(posted.upgraded_to_runnable().slice_boundary.is_none());
15903 let expected = if input.is_none() && !chaos {
15907 ResourceID::SchedYield
15908 } else {
15909 ResourceID::SleepUntil(LogicalTime::from_nanos(0))
15910 };
15911 assert_eq!(posted.resources.len(), 1);
15912 assert_eq!(posted.resources.get(&expected), Some(&Permission::W));
15913 let turn = crate::scheduler::do_a_turn_blocking(
15914 state.sched.clone(),
15915 state.global_time.clone(),
15916 &Err(crate::scheduler::SkipTurn),
15917 )
15918 .await
15919 .expect("one sched_yield must commit its original turn");
15920 assert!(turn.slice_boundary.is_none());
15921 assert!(
15922 !turn.slice_expired,
15923 "an explicit sched_yield is a syscall turn, not an expired slice"
15924 );
15925 assert_eq!(request.await.unwrap(), 0);
15926 assert_eq!(state.sched.lock().unwrap().turn, before_turn + 1);
15927 assert_eq!(guest.requests.lock().unwrap().len(), before_requests + 1);
15928 if let Some(mut observed) = guest.thread.slice_boundary.take() {
15929 observed
15930 .syscall(&guest.thread, Sysno::sched_yield, Phase::Posthook)
15931 .unwrap();
15932 guest.thread.slice_boundary = Some(observed);
15933 }
15934 let closes = typed && !chaos;
15935 assert_eq!(
15936 guest.thread.stats.timeslice_count,
15937 initial_slices + if closes && timers { occurrence } else { 0 }
15938 );
15939 }
15940 let mut scheduler = state.sched.lock().unwrap();
15941 let recording = Recording::decode(
15942 scheduler
15943 .preemption_writer
15944 .take()
15945 .unwrap()
15946 .into_string()
15947 .as_bytes(),
15948 )
15949 .unwrap();
15950 if let Recording::Boundaries(recording) = &recording {
15951 let records = recording.schedule().records();
15952 assert_eq!(records.len(), if chaos { 0 } else { 2 });
15953 for (index, record) in records.iter().enumerate() {
15954 let Record::TimesliceEnd(boundary) = record else {
15955 panic!("not a boundary")
15956 };
15957 assert_eq!(boundary.identity.cause, Cause::ExplicitSchedYield);
15958 assert_eq!(boundary.identity.phase, Phase::Prehook);
15959 assert_eq!(
15960 boundary.identity.entry,
15961 Entry::Syscall {
15962 syscall: Sysno::sched_yield,
15963 occurrence: index as u64 + 1
15964 }
15965 );
15966 assert_eq!(boundary.identity.slice, index as u64 + 1);
15967 assert_eq!(boundary.identity.checkin, index as u64 * 2 + 1);
15968 assert_eq!(boundary.identity.committed_rcbs, 0);
15969 assert_eq!(boundary.count, 1);
15970 }
15971 assert_eq!(
15972 scheduler.recorded_event_count,
15973 u64::try_from(records.len()).unwrap()
15974 );
15975 if input.is_some() && !chaos {
15976 assert_eq!(
15977 scheduler
15978 .generate_partial_run_summary(None)
15979 .unwrap()
15980 .schedevent_replayed,
15981 2
15982 );
15983 }
15984 } else {
15985 assert_eq!(scheduler.recorded_event_count, 0);
15986 }
15987 (
15988 recording,
15989 scheduler.committed_time,
15990 guest.thread.thread_logical_time.as_nanos(),
15991 scheduler.turn,
15992 guest.thread.stats.timeslice_count,
15993 )
15994 }
15995
15996 #[tokio::test]
15997 async fn boundary_actual_sched_yield_handler_replays_with_and_without_timers() {
15998 let directory = tempfile::tempdir().unwrap();
15999 for timers in [false, true] {
16000 let recorded = boundary_sched_yield_handler(None, true, false, timers, false).await;
16001 let input = directory.path().join(format!("{timers}.json"));
16002 recorded.0.write(&input).unwrap();
16003 let replayed =
16004 boundary_sched_yield_handler(Some(&input), true, false, timers, false).await;
16005 assert_eq!(recorded, replayed);
16007 }
16008 }
16009
16010 #[tokio::test]
16011 async fn boundary_actual_sched_yield_handler_preserves_legacy_and_chaos_weak_yield() {
16012 use crate::preemptions::boundary_recording::Recording;
16013 let directory = tempfile::tempdir().unwrap();
16014 let input = directory.path().join("legacy.json");
16015 Recording::Legacy(PreemptionRecord::from_sched_events(vec![]))
16016 .write(&input)
16017 .unwrap();
16018 for timers in [false, true] {
16019 boundary_sched_yield_handler(Some(&input), false, false, timers, false).await;
16020 }
16021 boundary_sched_yield_handler(Some(&input), false, true, true, false).await;
16024 boundary_sched_yield_handler(Some(&input), true, true, true, false).await;
16025 }
16026
16027 async fn boundary_bad_yield_replay(kind: &str) {
16028 use detcore_model::schedule::boundary::Cause;
16029 use detcore_model::schedule::boundary::Entry;
16030 use detcore_model::schedule::boundary::Record;
16031 use detcore_model::schedule::boundary::Schedule;
16032
16033 use crate::preemptions::boundary_recording::BoundaryRecording;
16034 use crate::preemptions::boundary_recording::Recording;
16035
16036 let recorded = boundary_sched_yield_handler(None, true, false, false, false)
16037 .await
16038 .0;
16039 let Recording::Boundaries(recorded) = recorded else {
16040 panic!("not typed")
16041 };
16042 let mut records = recorded.schedule().records().to_vec();
16043 records.truncate(1);
16044 if kind == "missing" {
16045 records.clear();
16046 } else if kind != "owner" {
16047 let Record::TimesliceEnd(boundary) = &mut records[0] else {
16048 unreachable!()
16049 };
16050 match kind {
16051 "cause" => boundary.identity.cause = Cause::SoftwareCheckin,
16052 "occurrence" => {
16053 boundary.identity.entry = Entry::Syscall {
16054 syscall: Sysno::sched_yield,
16055 occurrence: 2,
16056 }
16057 }
16058 _ => panic!("unknown negative fixture"),
16059 }
16060 }
16061 let input = tempfile::tempdir().unwrap();
16062 let path = input.path().join("bad.json");
16063 Recording::Boundaries(
16064 BoundaryRecording::new(
16065 PreemptionRecord::from_sched_events(vec![]),
16066 Schedule::new(records).unwrap(),
16067 )
16068 .unwrap(),
16069 )
16070 .write(&path)
16071 .unwrap();
16072 boundary_sched_yield_handler(Some(&path), true, false, false, kind == "owner").await;
16073 panic!("actual sched_yield handler accepted {kind}");
16074 }
16075
16076 #[tokio::test]
16077 async fn boundary_actual_sched_yield_handler_refuses_missing_marker() {
16078 if in_child_expecting_refusal(
16079 "boundary_actual_sched_yield_handler_refuses_missing_marker",
16080 "Exhausted",
16081 ) {
16082 boundary_bad_yield_replay("missing").await;
16083 }
16084 }
16085
16086 #[tokio::test]
16087 async fn boundary_actual_sched_yield_handler_refuses_wrong_cause() {
16088 if in_child_expecting_refusal(
16089 "boundary_actual_sched_yield_handler_refuses_wrong_cause",
16090 "expected Identity",
16091 ) {
16092 boundary_bad_yield_replay("cause").await;
16093 }
16094 }
16095
16096 #[tokio::test]
16097 async fn boundary_actual_sched_yield_handler_refuses_wrong_occurrence() {
16098 if in_child_expecting_refusal(
16099 "boundary_actual_sched_yield_handler_refuses_wrong_occurrence",
16100 "expected Identity",
16101 ) {
16102 boundary_bad_yield_replay("occurrence").await;
16103 }
16104 }
16105
16106 #[tokio::test]
16107 async fn boundary_actual_sched_yield_handler_refuses_stale_owner() {
16108 if in_child_expecting_refusal(
16109 "boundary_actual_sched_yield_handler_refuses_stale_owner",
16110 "stale observation owner",
16111 ) {
16112 boundary_bad_yield_replay("owner").await;
16113 }
16114 }
16115
16116 #[test]
16117 fn boundary_resource_union_refuses_either_annotated_operand_before_mutation() {
16118 let tid = DetTid::from_raw(17);
16119 let config = Config::default();
16120 let mut thread = crate::ThreadState::new(tid, &config, ());
16121 let mut observed = crate::slice_boundary::Observations::new(&thread, 0);
16122 observed
16123 .callback(
16124 &thread,
16125 detcore_model::schedule::boundary::Entry::Cpuid,
16126 detcore_model::schedule::boundary::Phase::Prehook,
16127 )
16128 .unwrap();
16129 let boundary = observed
16130 .close_slice(&mut thread, &config, false, None)
16131 .unwrap()
16132 .boundary;
16133 for side in 0..2 {
16134 let mut left = Resources::new(tid);
16135 let mut right = Resources::new(tid);
16136 right.insert(ResourceID::MemAddrSpace(tid), Permission::RW);
16137 if side == 0 {
16138 left.slice_boundary = Some(Box::new(boundary.clone()));
16139 } else {
16140 right.slice_boundary = Some(Box::new(boundary.clone()));
16141 }
16142 let annotated = if side == 0 { &left } else { &right };
16143 assert!(
16144 std::panic::catch_unwind(std::panic::AssertUnwindSafe(
16145 || annotated.upgraded_to_runnable()
16146 ))
16147 .is_err()
16148 );
16149 let before = left.clone();
16150 assert!(
16151 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| left.union(&right)))
16152 .is_err()
16153 );
16154 assert_eq!(left, before);
16155 }
16156 }
16157
16158 async fn boundary_grant_roundtrip(
16159 route: u8,
16160 with_budget: bool,
16161 automatic: bool,
16162 ) -> (
16163 Resources,
16164 LogicalTime,
16165 LogicalTime,
16166 u64,
16167 (Option<LogicalTime>, Option<LogicalTime>, u64),
16168 ) {
16169 let input = tempfile::tempdir().unwrap();
16170 let (config, state, tid, process) = if automatic {
16171 use detcore_model::schedule::boundary::Boundary;
16172 use detcore_model::schedule::boundary::Record;
16173 use detcore_model::schedule::boundary::Schedule;
16174
16175 use crate::preemptions::boundary_recording::BoundaryRecording;
16176 use crate::preemptions::boundary_recording::Recording;
16177 let tid = DetTid::from_raw(17);
16178 let budget = crate::slice_boundary::grant::tests::budget(tid, MmId::initial(tid), 91);
16179 let recording = Recording::Boundaries(
16180 BoundaryRecording::new(
16181 PreemptionRecord::from_sched_events(vec![]),
16182 Schedule::new(vec![Record::TimesliceEnd(Boundary {
16183 identity: budget.expected(),
16184 count: 1,
16185 start_rip: None,
16186 end_rip: None,
16187 end_time: None,
16188 })])
16189 .unwrap(),
16190 )
16191 .unwrap(),
16192 );
16193 let path = input.path().join("recording.json");
16194 recording.write(&path).unwrap();
16195 cancellation_test_state_with_config(
16196 Config {
16197 sequentialize_threads: true,
16198 replay_schedule_from: Some(path),
16199 strict_schedule_boundaries: true,
16200 ..Config::default()
16201 }
16202 .with_backend(|backend| backend.needs_killed_thread_rpc_cancellation = true),
16203 )
16204 } else {
16205 cancellation_test_state()
16206 };
16207 install_test_registration(&state, tid, Ivar::new());
16208 let mut thread = crate::ThreadState::new(tid, &config, ());
16209 thread.detpid = Some(process);
16210 if route != 1 {
16211 thread.next_timeslice(&config);
16212 }
16213 thread.slice_boundary = Some(crate::slice_boundary::Observations::new(&thread, 0));
16214 let budget = crate::slice_boundary::grant::tests::budget(tid, thread.mm_id, 91);
16215 if with_budget {
16216 let mut scheduler = state.sched.lock().unwrap();
16217 let mut owners = crate::slice_boundary::lifecycle::Registry::default();
16218 owners.fresh(tid, thread.mm_id).unwrap();
16219 scheduler.boundary_owners = Some(owners);
16220 if automatic {
16221 scheduler.record_thread_mm(tid, thread.mm_id);
16222 assert_eq!(
16223 scheduler.boundary_budget_for_live_owner(tid).unwrap(),
16224 Some(budget)
16225 );
16226 } else {
16227 scheduler
16228 .boundary_budgets
16229 .queue(thread.mm_id, budget)
16230 .unwrap();
16231 }
16232 if route == 1 {
16233 thread.slice_boundary = None;
16236 }
16237 }
16238 let original_slices = thread.stats.timeslice_count;
16239 let original_maximum = thread.max_timeslice_end;
16240 let mut guest = ExternalRegistrationGuest {
16241 global: &state,
16242 config: &config,
16243 thread,
16244 requests: Mutex::new(Vec::new()),
16245 post_exec: false,
16246 ppid: None,
16247 private_memory: None,
16248 };
16249 let mut request = Box::pin(async {
16250 match route {
16251 0 => {
16252 let mut resources = Resources::new(tid);
16253 resources.insert(ResourceID::MemAddrSpace(process), Permission::RW);
16254 assert_eq!(
16255 super::resource_request(&mut guest, resources).await,
16256 ResumeStatus::Normal
16257 );
16258 }
16259 1 => assert_eq!(
16260 super::thread_start_request(&config, &mut guest, process).await,
16261 None
16262 ),
16263 2 => {
16264 let response =
16265 super::send_and_update_time(&mut guest, GlobalRequest::ResumeExec(process))
16266 .await;
16267 assert_eq!(
16268 response.1,
16269 GlobalResponse::ResumeExec(ResumeStatus::Normal, None)
16270 );
16271 }
16272 _ => unreachable!(),
16273 }
16274 });
16275 for _ in 0..8 {
16276 assert!(futures::poll!(&mut request).is_pending());
16277 if state.sched.lock().unwrap().next_turns[&tid]
16278 .req
16279 .try_read()
16280 .is_some()
16281 {
16282 break;
16283 }
16284 }
16285 assert!(
16286 state.sched.lock().unwrap().next_turns[&tid]
16287 .req
16288 .try_read()
16289 .is_some()
16290 );
16291 let turn = crate::scheduler::do_a_turn_blocking(
16292 state.sched.clone(),
16293 state.global_time.clone(),
16294 &Err(crate::scheduler::SkipTurn),
16295 )
16296 .await
16297 .expect("the original resource turn must commit");
16298 request.await;
16299 if route == 1 {
16300 assert_eq!(guest.thread.replay_rcb_end, None);
16304 assert_eq!(guest.thread.stats.timeslice_count, original_slices);
16305 super::finish_thread_start(&config, &mut guest, None);
16306 }
16307 assert_eq!(guest.thread.replay_rcb_end, with_budget.then_some(91));
16308 assert_eq!(
16309 guest.thread.stats.timeslice_count,
16310 original_slices + u64::from(route == 1)
16311 );
16312 if route != 1 {
16313 assert_eq!(guest.thread.max_timeslice_end, original_maximum);
16314 }
16315 let scheduler = state.sched.lock().unwrap();
16316 (
16317 turn,
16318 scheduler.committed_time,
16319 guest.thread.thread_logical_time.as_nanos(),
16320 scheduler.turn,
16321 (
16322 guest.thread.max_timeslice_end,
16323 guest.thread.end_of_timeslice,
16324 guest.thread.stats.timeslice_count,
16325 ),
16326 )
16327 }
16328
16329 #[tokio::test]
16330 async fn boundary_budget_survives_real_resource_start_and_exec_resume_rpc_without_an_extra_turn()
16331 {
16332 for route in 0..3 {
16333 assert_eq!(
16334 boundary_grant_roundtrip(route, true, false).await,
16335 boundary_grant_roundtrip(route, false, false).await
16336 );
16337 }
16338 }
16339
16340 #[tokio::test]
16341 async fn boundary_loaded_cursor_delivers_only_through_the_existing_grant_and_start_reset() {
16342 for route in 0..3 {
16343 assert_eq!(
16344 boundary_grant_roundtrip(route, true, true).await,
16345 boundary_grant_roundtrip(route, false, false).await,
16346 );
16347 }
16348 }
16349
16350 struct ExternalRegistrationGuest<'a> {
16353 global: &'a GlobalState,
16354 config: &'a Config,
16355 thread: crate::ThreadState<()>,
16356 requests: Mutex<Vec<GlobalRequest>>,
16357 post_exec: bool,
16358 ppid: Option<reverie::Pid>,
16359 private_memory: Option<reverie::RuntimePrivateMemory>,
16360 }
16361
16362 struct ExternalRegistrationStack;
16363 struct ExternalRegistrationStackGuard;
16364
16365 impl Drop for ExternalRegistrationStackGuard {
16366 fn drop(&mut self) {}
16367 }
16368
16369 impl reverie::Stack for ExternalRegistrationStack {
16370 type StackGuard = ExternalRegistrationStackGuard;
16371
16372 fn size(&self) -> usize {
16373 panic!("external registration must not use a guest stack")
16374 }
16375
16376 fn capacity(&self) -> usize {
16377 panic!("external registration must not use a guest stack")
16378 }
16379
16380 fn push<'stack, T>(&mut self, _value: T) -> reverie::syscalls::Addr<'stack, T> {
16381 panic!("external registration must not use a guest stack")
16382 }
16383
16384 fn reserve<'stack, T>(&mut self) -> reverie::syscalls::AddrMut<'stack, T> {
16385 panic!("external registration must not use a guest stack")
16386 }
16387
16388 fn commit(self) -> Result<Self::StackGuard, reverie::syscalls::Errno> {
16389 panic!("external registration must not use a guest stack")
16390 }
16391 }
16392
16393 #[reverie::tool]
16394 impl GlobalRPC<GlobalState> for ExternalRegistrationGuest<'_> {
16395 async fn send_rpc(
16396 &self,
16397 message: <GlobalState as GlobalTool>::Request,
16398 ) -> <GlobalState as GlobalTool>::Response {
16399 self.requests.lock().unwrap().push(message.2.clone());
16400 self.global
16401 .receive_rpc(Tid::from_raw(self.thread.dettid.as_raw()), message)
16402 .await
16403 }
16404
16405 fn config(&self) -> &Config {
16406 self.config
16407 }
16408 }
16409
16410 #[reverie::tool]
16411 impl Guest<Detcore> for ExternalRegistrationGuest<'_> {
16412 type Memory = reverie::syscalls::LocalMemory;
16413 type Stack = ExternalRegistrationStack;
16414
16415 fn tid(&self) -> reverie::Pid {
16416 reverie::Pid::from_raw(self.thread.dettid.as_raw())
16417 }
16418
16419 fn pid(&self) -> reverie::Pid {
16420 reverie::Pid::from_raw(self.thread.detpid.unwrap().as_raw())
16421 }
16422
16423 fn ppid(&self) -> Option<reverie::Pid> {
16424 self.ppid
16425 }
16426
16427 fn runtime_private_memory_regions(&self) -> Option<reverie::RuntimePrivateMemory> {
16428 self.private_memory.clone()
16429 }
16430
16431 fn auxv(&self) -> reverie::Auxv {
16432 assert!(self.post_exec, "external registration must not read auxv");
16433 reverie::Auxv::from_entries([])
16434 }
16435
16436 fn memory(&self) -> Self::Memory {
16437 panic!("external registration must not access guest memory")
16438 }
16439
16440 fn thread_state_mut(&mut self) -> &mut crate::ThreadState<()> {
16441 &mut self.thread
16442 }
16443
16444 fn thread_state(&self) -> &crate::ThreadState<()> {
16445 &self.thread
16446 }
16447
16448 async fn regs(&mut self) -> libc::user_regs_struct {
16449 assert!(
16450 self.post_exec,
16451 "external registration must not read guest registers"
16452 );
16453 unsafe { std::mem::zeroed() }
16456 }
16457
16458 async fn stack(&mut self) -> Self::Stack {
16459 panic!("external registration must not use a guest stack")
16460 }
16461
16462 async fn daemonize(&mut self) {
16463 panic!("external registration must not daemonize")
16464 }
16465
16466 async fn inject<S: reverie::syscalls::SyscallInfo>(
16467 &mut self,
16468 _syscall: S,
16469 ) -> Result<i64, reverie::syscalls::Errno> {
16470 panic!("external registration must not inject a syscall")
16471 }
16472
16473 async fn tail_inject<S: reverie::syscalls::SyscallInfo>(
16474 &mut self,
16475 _syscall: S,
16476 ) -> reverie::Never {
16477 panic!("external registration unexpectedly retired its live parent")
16478 }
16479
16480 fn set_timer(&mut self, _schedule: reverie::TimerSchedule) -> Result<(), reverie::Error> {
16481 panic!("external registration must not set a timer")
16482 }
16483
16484 fn set_timer_precise(
16485 &mut self,
16486 _schedule: reverie::TimerSchedule,
16487 ) -> Result<(), reverie::Error> {
16488 panic!("external registration must not set a timer")
16489 }
16490
16491 fn read_clock(&mut self) -> Result<u64, reverie::Error> {
16492 panic!("external registration must not read a host clock")
16493 }
16494 }
16495
16496 struct RetirementGuest<'a> {
16497 global: &'a GlobalState,
16498 config: &'a Config,
16499 thread: crate::ThreadState<()>,
16500 requests: Mutex<Vec<GlobalRequest>>,
16501 retired: std::sync::atomic::AtomicBool,
16502 hold_private_memory_publication: bool,
16503 }
16504
16505 #[reverie::tool]
16506 impl GlobalRPC<GlobalState> for RetirementGuest<'_> {
16507 async fn send_rpc(
16508 &self,
16509 message: <GlobalState as GlobalTool>::Request,
16510 ) -> <GlobalState as GlobalTool>::Response {
16511 self.requests.lock().unwrap().push(message.2.clone());
16512 if self.hold_private_memory_publication
16513 && matches!(message.2, GlobalRequest::PublishRuntimePrivateMemory(_))
16514 {
16515 futures::future::pending::<()>().await;
16520 }
16521 self.global
16522 .receive_rpc(Tid::from_raw(self.thread.dettid.as_raw()), message)
16523 .await
16524 }
16525
16526 fn config(&self) -> &Config {
16527 self.config
16528 }
16529 }
16530
16531 #[reverie::tool]
16532 impl Guest<Detcore> for RetirementGuest<'_> {
16533 type Memory = reverie::syscalls::LocalMemory;
16534 type Stack = ExternalRegistrationStack;
16535
16536 fn tid(&self) -> reverie::Pid {
16537 reverie::Pid::from_raw(self.thread.dettid.as_raw())
16538 }
16539
16540 fn pid(&self) -> reverie::Pid {
16541 reverie::Pid::from_raw(self.thread.detpid.unwrap().as_raw())
16542 }
16543
16544 fn ppid(&self) -> Option<reverie::Pid> {
16545 None
16546 }
16547
16548 fn memory(&self) -> Self::Memory {
16549 panic!("external registration must not access guest memory")
16550 }
16551
16552 fn thread_state_mut(&mut self) -> &mut crate::ThreadState<()> {
16553 &mut self.thread
16554 }
16555
16556 fn thread_state(&self) -> &crate::ThreadState<()> {
16557 &self.thread
16558 }
16559
16560 fn runtime_private_memory_regions(&self) -> Option<reverie::RuntimePrivateMemory> {
16561 self.hold_private_memory_publication
16562 .then_some(reverie::RuntimePrivateMemory::Unavailable)
16563 }
16564
16565 async fn regs(&mut self) -> libc::user_regs_struct {
16566 panic!("external registration must not read guest registers")
16567 }
16568
16569 async fn stack(&mut self) -> Self::Stack {
16570 panic!("external registration must not use a guest stack")
16571 }
16572
16573 async fn daemonize(&mut self) {
16574 panic!("external registration must not daemonize")
16575 }
16576
16577 async fn inject<S: reverie::syscalls::SyscallInfo>(
16578 &mut self,
16579 _syscall: S,
16580 ) -> Result<i64, reverie::syscalls::Errno> {
16581 panic!("external registration must not inject a syscall")
16582 }
16583
16584 async fn retire_current_thread(&mut self) -> reverie::Never {
16585 self.retired
16586 .store(true, std::sync::atomic::Ordering::SeqCst);
16587 futures::future::pending().await
16588 }
16589
16590 async fn cancel_current_thread(&mut self) -> reverie::Never {
16591 panic!("scheduler retirement must not request group cancellation")
16592 }
16593
16594 async fn tail_inject<S: reverie::syscalls::SyscallInfo>(
16595 &mut self,
16596 _syscall: S,
16597 ) -> reverie::Never {
16598 panic!("external registration unexpectedly retired its live parent")
16599 }
16600
16601 fn set_timer(&mut self, _schedule: reverie::TimerSchedule) -> Result<(), reverie::Error> {
16602 panic!("external registration must not set a timer")
16603 }
16604
16605 fn set_timer_precise(
16606 &mut self,
16607 _schedule: reverie::TimerSchedule,
16608 ) -> Result<(), reverie::Error> {
16609 panic!("external registration must not set a timer")
16610 }
16611
16612 fn read_clock(&mut self) -> Result<u64, reverie::Error> {
16613 panic!("external registration must not read a host clock")
16614 }
16615 }
16616
16617 #[tokio::test]
16621 async fn a_cancelled_request_preserves_descriptor_exports_for_its_exit() {
16622 use reverie::Tool;
16623
16624 let config = Config {
16625 sequentialize_threads: true,
16626 ..Config::default()
16627 };
16628 let state = GlobalState::initialize(&config, false);
16629 let reader = DetTid::from_raw(3);
16630 let sender = DetTid::from_raw(5);
16631 let tool: Detcore = Detcore::new(Tid::from_raw(sender.as_raw()), &config);
16632 let mut thread = crate::ThreadState::new(sender, &config, ());
16633 thread.detpid = Some(sender);
16634 thread.passed_descriptors_out_unreported = true;
16635 {
16636 let mut sched = state.sched.lock().unwrap();
16637 sched.thread_tree.add_child(reader, reader, true);
16638 sched.thread_tree.add_child(reader, sender, true);
16639 for tid in [reader, sender] {
16640 sched.register_thread_for_test(tid);
16641 sched.install_test_exec_incarnation(tid, MmId::initial(tid));
16642 }
16643 }
16644 let mut guest = RetirementGuest {
16645 global: &state,
16646 config: &config,
16647 thread,
16648 requests: Mutex::new(Vec::new()),
16649 retired: std::sync::atomic::AtomicBool::new(false),
16650 hold_private_memory_publication: true,
16651 };
16652 {
16653 let request = super::resource_request(&mut guest, Resources::new(sender));
16654 let mut request = std::pin::pin!(request);
16655 assert!(futures::poll!(request.as_mut()).is_pending());
16656 }
16659 assert!(matches!(
16660 guest.requests.lock().unwrap().as_slice(),
16661 [GlobalRequest::PublishRuntimePrivateMemory(
16662 reverie::RuntimePrivateMemory::Unavailable
16663 )]
16664 ));
16665 assert!(
16666 state.sched.lock().unwrap().next_turns[&sender]
16667 .req
16668 .try_read()
16669 .is_none(),
16670 "no resource request reached the scheduler"
16671 );
16672 assert!(
16673 guest.thread.passed_descriptors_out_unreported,
16674 "cancellation before publication must retain the export for exit"
16675 );
16676 let thread = guest.thread.clone();
16677 tool.on_exit_thread(
16678 Tid::from_raw(sender.as_raw()),
16679 &guest,
16680 thread,
16681 reverie::ExitStatus::Signaled(reverie::Signal::SIGKILL, false),
16682 )
16683 .await
16684 .unwrap();
16685 {
16686 let sched = state.sched.lock().unwrap();
16687 assert!(!sched.next_turns.contains_key(&sender));
16688 assert_eq!(sched.turn, 0, "exit publication must not take a turn");
16689 }
16690 super::robust_exit_clock_tests::assert_reader_after_sender_exit(&state, reader, true).await;
16691 }
16692
16693 #[tokio::test]
16697 async fn a_direct_futex_wait_publishes_descriptor_exports_before_parking() {
16698 for exported in [false, true] {
16699 let config = Config {
16700 sequentialize_threads: true,
16701 ..Config::default()
16702 };
16703 let state = GlobalState::initialize(&config, false);
16704 let reader = DetTid::from_raw(3);
16705 let sender = DetTid::from_raw(5);
16706 let mut thread = crate::ThreadState::new(sender, &config, ());
16707 thread.detpid = Some(sender);
16708 thread.passed_descriptors_out_unreported = exported;
16709 {
16710 let mut sched = state.sched.lock().unwrap();
16711 sched.thread_tree.add_child(reader, reader, true);
16712 sched.thread_tree.add_child(reader, sender, true);
16713 for tid in [reader, sender] {
16714 sched.register_thread_for_test(tid);
16715 sched.install_test_exec_incarnation(tid, MmId::initial(tid));
16716 sched.default_signal_state_for_test(tid);
16717 }
16718 sched.runqueue_push_back(sender);
16719 }
16720 let mut guest = RetirementGuest {
16721 global: &state,
16722 config: &config,
16723 thread,
16724 requests: Mutex::new(Vec::new()),
16725 retired: std::sync::atomic::AtomicBool::new(false),
16726 hold_private_memory_publication: false,
16727 };
16728 let futex = FutexID::private(MmId::initial(sender), 0x1000);
16729 {
16730 let request = super::futex_action(
16731 &mut guest,
16732 FutexAction::WaitRequest(
16733 None,
16734 Some(super::FutexSignalWatch {
16735 unblocked: u64::MAX,
16736 defers_default_stops: false,
16737 pid: sender.as_raw(),
16738 tid: sender.as_raw(),
16739 }),
16740 ),
16741 &futex,
16742 0,
16743 u32::MAX,
16744 );
16745 let mut request = std::pin::pin!(request);
16746 assert!(futures::poll!(request.as_mut()).is_pending());
16747 {
16748 let sched = state.sched.lock().unwrap();
16749 assert_eq!(sched.blocked.futex_waiters[&futex].len(), 1);
16750 assert_eq!(sched.futex_waiters_for_test(futex), [sender]);
16751 assert!(!sched.run_queue.contains_tid(sender));
16752 assert_eq!(sched.turn, 0, "export publication must not take a turn");
16753 }
16754 super::robust_exit_clock_tests::assert_reader_after_sender_exit(
16755 &state, reader, exported,
16756 )
16757 .await;
16758 }
16759 assert!(matches!(
16760 guest.requests.lock().unwrap().as_slice(),
16761 [GlobalRequest::FutexAction(..)]
16762 ));
16763 }
16764 }
16765
16766 #[tokio::test]
16771 async fn sigalrm_sends_and_producer_armings_are_refused_before_the_kernel() {
16772 use reverie::Tool;
16773 use reverie::syscalls;
16774
16775 let (config, state, tid, pid) = cancellation_test_state();
16776 install_test_registration(&state, tid, Ivar::new());
16777 {
16778 let mut sched = state.sched.lock().unwrap();
16779 sched.set_sigalrm_handled(pid, true);
16780 sched.set_running_for_test(tid);
16781 }
16782 let tool: Detcore = Detcore::new(Tid::from_raw(tid.as_raw()), &config);
16783 let mut thread = tool.init_thread_state(Tid::from_raw(tid.as_raw()), None);
16784 thread.detpid = Some(pid);
16785 let mut guest = RetirementGuest {
16786 global: &state,
16787 config: &config,
16788 thread,
16789 requests: Mutex::new(Vec::new()),
16790 retired: std::sync::atomic::AtomicBool::new(false),
16791 hold_private_memory_publication: false,
16792 };
16793 let (raw_pid, raw_tid) = (pid.as_raw(), tid.as_raw());
16794 let eperm = |result: Result<i64, reverie::Error>, what: &str| {
16795 assert!(
16796 matches!(result, Err(reverie::Error::Errno(errno)) if errno == reverie::Errno::EPERM),
16797 "{what}: {result:?}"
16798 );
16799 };
16800
16801 let sigalrm = libc::SIGALRM;
16802 let tgkill = syscalls::Tgkill::new()
16803 .with_tgid(raw_pid)
16804 .with_tid(raw_tid)
16805 .with_sig(sigalrm);
16806 eperm(tool.handle_tgkill(&mut guest, tgkill).await, "tgkill");
16807 let tkill = syscalls::Tkill::new().with_tid(raw_tid).with_sig(sigalrm);
16808 eperm(tool.handle_tkill(&mut guest, tkill).await, "tkill");
16809 let kill = syscalls::Kill::new().with_pid(raw_pid).with_sig(sigalrm);
16810 eperm(tool.handle_kill(&mut guest, kill).await, "kill");
16811 let queue = syscalls::RtSigqueueinfo::new()
16812 .with_tgid(raw_pid)
16813 .with_sig(sigalrm);
16814 eperm(
16815 tool.handle_rt_sigqueueinfo(&mut guest, queue).await,
16816 "rt_sigqueueinfo",
16817 );
16818 let tgqueue = syscalls::RtTgsigqueueinfo::new()
16819 .with_tgid(raw_pid)
16820 .with_tid(raw_tid)
16821 .with_sig(sigalrm);
16822 eperm(
16823 tool.handle_rt_tgsigqueueinfo(&mut guest, tgqueue).await,
16824 "rt_tgsigqueueinfo",
16825 );
16826 let pdeathsig = syscalls::Prctl::new()
16827 .with_option(libc::PR_SET_PDEATHSIG)
16828 .with_arg2(sigalrm as libc::c_ulong);
16829 eperm(
16830 tool.handle_prctl(&mut guest, pdeathsig).await,
16831 "PR_SET_PDEATHSIG",
16832 );
16833 let timer = syscalls::TimerCreate::new()
16835 .with_clockid(syscalls::ClockId::CLOCK_MONOTONIC)
16836 .with_timerid(reverie::syscalls::AddrMut::from_raw(0x1000));
16837 eperm(
16838 tool.handle_timer_create(&mut guest, timer).await,
16839 "timer_create",
16840 );
16841
16842 let requests = guest.requests.lock().unwrap();
16843 assert_eq!(requests.len(), 7);
16844 assert!(
16845 requests
16846 .iter()
16847 .all(|request| matches!(request, GlobalRequest::Sigalrm(_))),
16848 "{requests:?}"
16849 );
16850 }
16851
16852 #[tokio::test]
16856 async fn sigalrm_phase1_gate_refuses_unplaced_calls() {
16857 use reverie::Tool;
16858 use reverie::syscalls::Syscall;
16859 use reverie::syscalls::SyscallArgs;
16860 use reverie::syscalls::Sysno;
16861
16862 let (config, state, tid, pid) = cancellation_test_state();
16863 install_test_registration(&state, tid, Ivar::new());
16864 let tool: Detcore = Detcore::new(Tid::from_raw(tid.as_raw()), &config);
16865 let mut thread = tool.init_thread_state(Tid::from_raw(tid.as_raw()), None);
16866 thread.detpid = Some(pid);
16867 thread.sigalrm_handled = true;
16868 let guest = RetirementGuest {
16869 global: &state,
16870 config: &config,
16871 thread,
16872 requests: Mutex::new(Vec::new()),
16873 retired: std::sync::atomic::AtomicBool::new(false),
16874 hold_private_memory_publication: false,
16875 };
16876 let raw = |sysno, a0, a1| Syscall::from_raw(sysno, SyscallArgs::new(a0, a1, 0, 0, 0, 0));
16877
16878 for (sysno, a0, a1) in [
16879 (Sysno::rt_sigsuspend, 0x1000, 8),
16880 (Sysno::signalfd4, usize::MAX, 0x1000),
16881 (Sysno::epoll_pwait2, 3, 0),
16882 (Sysno::nanosleep, 0x1000, 0),
16883 (Sysno::fcntl, 3, libc::F_SETLKW as usize),
16884 (Sysno::execve, 0x1000, 0),
16885 (Sysno::read, 0, 0x1000),
16887 (Sysno::read, 77, 0x1000),
16888 (Sysno::close, 2, 0),
16890 (Sysno::ioctl, 1, libc::TCGETS as usize),
16892 (Sysno::getresuid, 0x1000, 0x2000),
16895 (Sysno::getresgid, 0x1000, 0x2000),
16896 (Sysno::getgroups, 1, 0x1000),
16897 (Sysno::getppid, 0, 0),
16898 (Sysno::getpgid, 0, 0),
16899 (Sysno::getsid, 0, 0),
16900 ] {
16901 assert_eq!(
16902 tool.sigalrm_phase1_gate(&guest, raw(sysno, a0, a1)),
16903 Some(nix::errno::Errno::EOPNOTSUPP),
16904 "{sysno}"
16905 );
16906 }
16907 for (sysno, a0, a1) in [
16908 (Sysno::getpid, 0, 0),
16909 (Sysno::write, 1, 0x1000),
16910 (Sysno::getuid, 0, 0),
16911 (Sysno::geteuid, 0, 0),
16912 (Sysno::getgid, 0, 0),
16913 (Sysno::getegid, 0, 0),
16914 ] {
16915 assert_eq!(
16916 tool.sigalrm_phase1_gate(&guest, raw(sysno, a0, a1)),
16917 None,
16918 "{sysno}"
16919 );
16920 }
16921 assert!(guest.requests.lock().unwrap().is_empty(), "no RPC");
16922 }
16923
16924 #[tokio::test]
16928 async fn a_file_lease_is_refused_while_a_guest_handles_sigalrm() {
16929 use reverie::Tool;
16930 use reverie::syscalls;
16931 use reverie::syscalls::FcntlCmd;
16932
16933 let (config, state, tid, pid) = cancellation_test_state();
16934 install_test_registration(&state, tid, Ivar::new());
16935 {
16936 let mut sched = state.sched.lock().unwrap();
16937 sched.set_sigalrm_handled(DetPid::from_raw(99), true);
16938 sched.set_running_for_test(tid);
16939 }
16940 let tool: Detcore = Detcore::new(Tid::from_raw(tid.as_raw()), &config);
16941 let mut thread = tool.init_thread_state(Tid::from_raw(tid.as_raw()), None);
16942 thread.detpid = Some(pid);
16943 let mut guest = RetirementGuest {
16944 global: &state,
16945 config: &config,
16946 thread,
16947 requests: Mutex::new(Vec::new()),
16948 retired: std::sync::atomic::AtomicBool::new(false),
16949 hold_private_memory_publication: false,
16950 };
16951 for lease in [libc::F_RDLCK, libc::F_WRLCK] {
16952 let fcntl = syscalls::Fcntl::new()
16953 .with_fd(3)
16954 .with_cmd(FcntlCmd::F_SETLEASE(lease));
16955 let result = tool.handle_fcntl(&mut guest, fcntl).await;
16956 assert!(
16957 matches!(result, Err(reverie::Error::Errno(errno)) if errno == reverie::Errno::EPERM),
16958 "{lease}: {result:?}"
16959 );
16960 }
16961 let requests = guest.requests.lock().unwrap();
16962 assert_eq!(requests.len(), 2);
16963 assert!(requests.iter().all(|request| matches!(
16964 request,
16965 GlobalRequest::Sigalrm(super::SigalrmControl::ArmProducer)
16966 )));
16967 }
16968
16969 #[tokio::test]
16979 async fn a_sigkilled_threads_late_ordinary_request_stays_unanswered() {
16980 use std::sync::atomic::Ordering;
16981
16982 use reverie::Tool;
16983
16984 let config = Config {
16985 sequentialize_threads: true,
16986 ..Config::default()
16987 };
16988 let [root, sender, victim] = [1, 2, 3].map(DetPid::from_raw);
16989 let commit = |state: &GlobalState| {
16990 let mut sched = state.sched.lock().unwrap();
16991 let token = sched.reserve_sigkill(sender, victim, 0).unwrap().token;
16992 assert_eq!(sched.commit_sigkill(token, true).len(), 1);
16993 assert!(sched.is_unaccounted_sigkill_victim(victim));
16994 };
16995 for late in ["filed", "rejoin", "plain"] {
16998 let state = GlobalState::initialize(&config, false);
16999 {
17000 let mut sched = state.sched.lock().unwrap();
17001 sched.thread_tree.add_child(root, root, true);
17002 sched.thread_tree.add_child(root, sender, true);
17003 sched.thread_tree.add_child(root, victim, true);
17004 }
17005 for tid in [root, sender, victim] {
17006 install_test_registration(&state, tid, Ivar::new());
17007 state
17008 .sched
17009 .lock()
17010 .unwrap()
17011 .record_thread_mm(tid, MmId::initial(tid));
17012 }
17013 if late != "filed" {
17014 commit(&state);
17015 }
17016 let tool: Detcore = Detcore::new(Tid::from_raw(victim.as_raw()), &config);
17017 let mut thread = tool.init_thread_state(Tid::from_raw(victim.as_raw()), None);
17018 thread.detpid = Some(victim);
17019 let mut guest = RetirementGuest {
17020 global: &state,
17021 config: &config,
17022 thread,
17023 requests: Mutex::new(Vec::new()),
17024 retired: std::sync::atomic::AtomicBool::new(false),
17025 hold_private_memory_publication: false,
17026 };
17027 {
17028 let mut rejoin = Resources::new(victim);
17029 rejoin.insert(
17030 ResourceID::BlockedExternalContinue(ExternalOpId::new(victim, 7)),
17031 Permission::RW,
17032 );
17033 let pending = if late == "plain" {
17034 let mut call = std::pin::pin!(super::send_and_update_time(
17035 &mut guest,
17036 GlobalRequest::GlobalTimeLowerBound
17037 ));
17038 futures::poll!(call.as_mut()).is_pending()
17039 } else {
17040 let mut call = std::pin::pin!(super::resource_request(&mut guest, rejoin));
17041 let mut pending = futures::poll!(call.as_mut()).is_pending();
17042 if late == "filed" {
17043 commit(&state);
17044 for _ in 0..8 {
17045 tokio::task::yield_now().await;
17046 pending &= futures::poll!(call.as_mut()).is_pending();
17047 }
17048 }
17049 pending
17050 };
17051 assert!(pending, "{late}: the callback resumed");
17052 }
17053 assert!(
17054 !guest.retired.load(Ordering::SeqCst),
17055 "{late}: retire_current_thread tail-injects exit"
17056 );
17057 assert_eq!(guest.requests.lock().unwrap().len(), 1, "{late}");
17058 let (_, ack) = state
17060 .receive_rpc(
17061 Tid::from_raw(victim.as_raw()),
17062 (
17063 guest.thread.thread_logical_time.clone(),
17064 guest.thread.mm_id,
17065 GlobalRequest::RobustListWakes(Vec::new()),
17066 ),
17067 )
17068 .await;
17069 assert_eq!(ack, GlobalResponse::RobustListWakes(Vec::new()), "{late}");
17070 for request in [
17074 GlobalRequest::Sigalrm(super::SigalrmControl::ArmProducer),
17075 GlobalRequest::RecordDeterminismLoss("victim notice".to_string()),
17076 ] {
17077 let (_, response) = state
17078 .receive_rpc(
17079 Tid::from_raw(victim.as_raw()),
17080 (
17081 guest.thread.thread_logical_time.clone(),
17082 guest.thread.mm_id,
17083 request,
17084 ),
17085 )
17086 .await;
17087 assert_eq!(response, GlobalResponse::Retired, "{late}");
17088 }
17089 }
17090 }
17091
17092 #[tokio::test]
17098 async fn a_victims_request_granted_across_the_commit_stays_unanswered() {
17099 use reverie::Tool;
17100
17101 let config = Config {
17102 sequentialize_threads: true,
17103 ..Config::default()
17104 };
17105 let [root, sender, victim] = [1, 2, 3].map(DetPid::from_raw);
17106 let state = GlobalState::initialize(&config, false);
17107 {
17108 let mut sched = state.sched.lock().unwrap();
17109 sched.thread_tree.add_child(root, root, true);
17110 sched.thread_tree.add_child(root, sender, true);
17111 sched.thread_tree.add_child(root, victim, true);
17112 }
17113 for tid in [root, sender, victim] {
17114 install_test_registration(&state, tid, Ivar::new());
17115 state
17116 .sched
17117 .lock()
17118 .unwrap()
17119 .record_thread_mm(tid, MmId::initial(tid));
17120 }
17121 let tool: Detcore = Detcore::new(Tid::from_raw(victim.as_raw()), &config);
17122 let mut thread = tool.init_thread_state(Tid::from_raw(victim.as_raw()), None);
17123 thread.detpid = Some(victim);
17124 let mut guest = RetirementGuest {
17125 global: &state,
17126 config: &config,
17127 thread,
17128 requests: Mutex::new(Vec::new()),
17129 retired: std::sync::atomic::AtomicBool::new(false),
17130 hold_private_memory_publication: false,
17131 };
17132 let mut rejoin = Resources::new(victim);
17133 rejoin.insert(
17134 ResourceID::BlockedExternalContinue(ExternalOpId::new(victim, 7)),
17135 Permission::RW,
17136 );
17137 let response = state.sched.lock().unwrap().next_turns[&victim].resp.clone();
17138 {
17139 let mut call = std::pin::pin!(super::resource_request(&mut guest, rejoin));
17140 assert!(futures::poll!(call.as_mut()).is_pending());
17142 response.put(SchedResponse::Go(None));
17145 {
17146 let mut sched = state.sched.lock().unwrap();
17147 let token = sched.reserve_sigkill(sender, victim, 0).unwrap().token;
17148 assert_eq!(sched.commit_sigkill(token, true).len(), 1);
17149 }
17150 for _ in 0..8 {
17151 tokio::task::yield_now().await;
17152 assert!(
17153 futures::poll!(call.as_mut()).is_pending(),
17154 "the granted response resumed a SIGKILLed thread's callback"
17155 );
17156 }
17157 }
17158 assert!(!guest.retired.load(std::sync::atomic::Ordering::SeqCst));
17159 }
17160
17161 fn in_child_expecting_refusal(name: &str, expected: &str) -> bool {
17166 const CHILD: &str = "HERMIT_TEST_REFUSAL_CHILD";
17167 let test = format!("{}::{name}", module_path!().split_once("::").unwrap().1);
17168 if std::env::var(CHILD).as_deref() == Ok(test.as_str()) {
17169 return true;
17170 }
17171 let output = std::process::Command::new(std::env::current_exe().unwrap())
17172 .args(["--exact", &test, "--nocapture"])
17173 .env(CHILD, &test)
17174 .output()
17175 .unwrap();
17176 let stderr = String::from_utf8_lossy(&output.stderr);
17177 assert_eq!(
17178 output.status.code(),
17179 Some(detcore_model::HERMIT_POLICY_REFUSAL_EXIT),
17180 "{test}: the child was not refused\n{stderr}"
17181 );
17182 assert!(
17183 stderr.contains(expected),
17184 "{test}: missing {expected:?}\n{stderr}"
17185 );
17186 false
17187 }
17188
17189 async fn send_dbt_sigkill_notice(victim: Option<i32>) {
17193 let config = Config {
17194 sequentialize_threads: true,
17195 ..Config::default()
17196 }
17197 .with_backend(|backend| *backend = reverie::BackendCapabilities::DBT);
17198 let state = GlobalState::initialize(&config, false);
17199 let [root, target] = [1, 3].map(DetPid::from_raw);
17200 {
17201 let mut sched = state.sched.lock().unwrap();
17202 sched.thread_tree.add_child(root, root, true);
17203 sched.thread_tree.add_child(root, target, true);
17204 }
17205 for tid in [root, target] {
17206 install_test_registration(&state, tid, Ivar::new());
17207 state
17208 .sched
17209 .lock()
17210 .unwrap()
17211 .record_thread_mm(tid, MmId::initial(tid));
17212 }
17213 {
17214 let mut sched = state.sched.lock().unwrap();
17215 sched.sigkill_fence_valve = std::time::Duration::from_millis(20);
17216 if let Some(pid) = victim {
17217 sched
17218 .register_physical_thread(target, MmId::initial(target), pid, pid)
17219 .unwrap();
17220 }
17221 }
17222 let rpc = state.receive_rpc(
17223 Tid::from_raw(root.as_raw()),
17224 (
17225 crate::ThreadState::<()>::new(root, &config, ())
17226 .thread_logical_time
17227 .clone(),
17228 MmId::initial(root),
17229 GlobalRequest::NotifySignalPending(
17230 target,
17231 SigWrapper(libc::SIGKILL),
17232 Some(target),
17233 super::SignalScope::Process,
17234 ),
17235 ),
17236 );
17237 let _ = tokio::time::timeout(std::time::Duration::from_secs(10), rpc).await;
17238 }
17239
17240 #[tokio::test]
17245 async fn a_dbt_sigkill_notice_past_its_valve_refuses_before_answering() {
17246 if !in_child_expecting_refusal(
17247 "a_dbt_sigkill_notice_past_its_valve_refuses_before_answering",
17248 "cannot be held until it is gone: it was not gone within",
17249 ) {
17250 return;
17251 }
17252 let mut victim = std::process::Command::new("sleep");
17257 victim
17258 .arg("60")
17259 .stdin(std::process::Stdio::null())
17260 .stdout(std::process::Stdio::null())
17261 .stderr(std::process::Stdio::null());
17262 unsafe {
17264 use std::os::unix::process::CommandExt;
17265 victim.pre_exec(|| {
17266 if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) == 0 {
17267 Ok(())
17268 } else {
17269 Err(std::io::Error::last_os_error())
17270 }
17271 });
17272 }
17273 let mut victim = victim.spawn().unwrap();
17274 send_dbt_sigkill_notice(Some(victim.id() as i32)).await;
17275 let _ = victim.kill();
17276 let _ = victim.wait();
17277 panic!("the SIGKILL notice answered its sender although the victim was not gone");
17278 }
17279
17280 #[tokio::test]
17287 async fn a_dbt_sigkill_notice_with_no_host_registration_refuses_before_answering() {
17288 if !in_child_expecting_refusal(
17289 "a_dbt_sigkill_notice_with_no_host_registration_refuses_before_answering",
17290 "nor any other thread of it has a registered host process",
17291 ) {
17292 return;
17293 }
17294 send_dbt_sigkill_notice(None).await;
17295 panic!("the SIGKILL notice answered its sender without a fence");
17296 }
17297
17298 #[tokio::test]
17303 async fn a_sigkill_commit_wait_past_its_valve_refuses_the_run() {
17304 let config = Config {
17305 sequentialize_threads: true,
17306 ..Config::default()
17307 };
17308 let state = GlobalState::initialize(&config, false);
17309 let [root, sender, target] = [1, 2, 3].map(DetPid::from_raw);
17310 {
17311 let mut sched = state.sched.lock().unwrap();
17312 sched.thread_tree.add_child(root, root, true);
17313 sched.thread_tree.add_child(root, sender, true);
17314 sched.thread_tree.add_child(root, target, true);
17315 }
17316 for tid in [root, sender, target] {
17317 install_test_registration(&state, tid, Ivar::new());
17318 state
17319 .sched
17320 .lock()
17321 .unwrap()
17322 .record_thread_mm(tid, MmId::initial(tid));
17323 }
17324 let token = {
17325 let mut sched = state.sched.lock().unwrap();
17326 sched.sigkill_fence_valve = std::time::Duration::from_millis(20);
17327 sched.reserve_sigkill(sender, target, 0).unwrap().token
17328 };
17329 let rpc = state.receive_rpc(
17330 Tid::from_raw(sender.as_raw()),
17331 (
17332 crate::ThreadState::<()>::new(sender, &config, ())
17333 .thread_logical_time
17334 .clone(),
17335 MmId::initial(sender),
17336 GlobalRequest::CommitSigkill(token, true),
17337 ),
17338 );
17339 let (_, response) = tokio::time::timeout(std::time::Duration::from_secs(10), rpc)
17340 .await
17341 .expect("the commit's wait outlived its valve");
17342 assert_eq!(response, GlobalResponse::CommitSigkill(()));
17343 let sched = state.sched.lock().unwrap();
17344 let refusal = sched
17345 .sigkill_fence_refusal_for_test()
17346 .expect("no refusal recorded");
17347 assert!(refusal.waited >= std::time::Duration::from_millis(20));
17348 assert_eq!(refusal.unaccounted, [target]);
17349 }
17350
17351 #[tokio::test]
17361 async fn victims_owner_death_wakes_through_the_handler_do_not_depend_on_receipt_order() {
17362 async fn run(fuzz: bool, reverse: bool) -> (Vec<Vec<DetTid>>, Vec<DetTid>, u64) {
17363 let config = Config {
17364 sequentialize_threads: true,
17365 fuzz_futexes: fuzz,
17366 ..Config::default()
17367 };
17368 let state = GlobalState::initialize(&config, false);
17369 let [root, sender, target, worker] = [1, 2, 3, 4].map(DetPid::from_raw);
17370 let mm = MmId::initial(target);
17371 let futexes = [0x404100, 0x404200].map(|addr| FutexID::private(mm, addr));
17372 {
17373 let mut sched = state.sched.lock().unwrap();
17374 sched.thread_tree.add_child(root, root, true);
17375 sched.thread_tree.add_child(root, sender, true);
17376 sched.thread_tree.add_child(root, target, true);
17377 sched.thread_tree.add_child(target, worker, false);
17378 }
17379 for tid in [root, sender, target, worker] {
17380 install_test_registration(&state, tid, Ivar::new());
17381 state
17382 .sched
17383 .lock()
17384 .unwrap()
17385 .record_thread_mm(tid, MmId::initial(tid));
17386 }
17387 for (futex, waiters) in futexes.iter().zip([[11, 12, 13], [14, 15, 16]]) {
17388 for raw in waiters {
17389 let tid = DetTid::from_raw(raw);
17390 let mut sched = state.sched.lock().unwrap();
17391 sched.thread_tree.add_child(root, tid, true);
17392 sched.next_turns.insert(
17393 tid,
17394 ThreadNextTurn {
17395 dettid: tid,
17396 child_tid_addr: 0,
17397 req: Ivar::new(),
17398 resp: Ivar::new(),
17399 protocol: Default::default(),
17400 },
17401 );
17402 sched.priorities.insert(tid, DEFAULT_PRIORITY);
17403 sched.sleep_futex_waiter(&tid, *futex, None, u32::MAX, None);
17404 }
17405 }
17406 {
17407 let mut sched = state.sched.lock().unwrap();
17408 let token = sched.reserve_sigkill(sender, target, 0).unwrap().token;
17409 assert_eq!(sched.commit_sigkill(token, true).len(), 2);
17410 }
17411 let mut receipts = [(target, futexes[0]), (worker, futexes[1])];
17412 if reverse {
17413 receipts.reverse();
17414 }
17415 for (victim, futex) in receipts {
17416 let (_, ack) = state
17417 .receive_rpc(
17418 Tid::from_raw(victim.as_raw()),
17419 (
17420 crate::ThreadState::<()>::new(victim, &config, ())
17421 .thread_logical_time
17422 .clone(),
17423 MmId::initial(victim),
17424 GlobalRequest::RobustListWakes(vec![(victim, futex)]),
17425 ),
17426 )
17427 .await;
17428 assert_eq!(
17429 ack,
17430 GlobalResponse::RobustListWakes(Vec::new()),
17431 "a victim's batch is retained, not run at receipt"
17432 );
17433 state.sched.lock().unwrap().step2_drain_prefix_for_test();
17434 let deregistration = ThreadDeregistration {
17437 dettid: victim,
17438 detpid: target,
17439 mm: MmId::initial(victim),
17440 thread_start_entered: true,
17441 vfork_origin: None,
17442 timeslice_stats: TimesliceStats {
17443 count: 1,
17444 sum_ns: 10,
17445 min_ns: 10,
17446 max_ns: 10,
17447 },
17448 syscall_count: 3,
17449 syscall_histogram: Default::default(),
17450 chaos_epochs: Vec::new(),
17451 killed_by_group_signal: false,
17452 passed_descriptors_out: false,
17453 };
17454 for _ in 0..2 {
17455 let (_, done) = state
17456 .receive_rpc(
17457 Tid::from_raw(victim.as_raw()),
17458 (
17459 crate::ThreadState::<()>::new(victim, &config, ())
17460 .thread_logical_time
17461 .clone(),
17462 MmId::initial(victim),
17463 GlobalRequest::DeregisterThread(deregistration.clone()),
17464 ),
17465 )
17466 .await;
17467 assert_eq!(done, GlobalResponse::DeregisterThread(()));
17468 }
17469 state.sched.lock().unwrap().step2_drain_prefix_for_test();
17470 }
17471 assert!(
17472 state.sched.lock().unwrap().sigkill_fence_holds(),
17473 "the victim's exit is not yet published to its parent"
17474 );
17475 <GlobalState as GlobalTool>::on_backend_process_exited(&state, target.as_raw());
17476 state.sched.lock().unwrap().step2_drain_prefix_for_test();
17477 {
17478 let sched = state.sched.lock().unwrap();
17479 assert!(
17480 !sched.sigkill_fence_holds(),
17481 "every victim is accounted and published"
17482 );
17483 for victim in [target, worker] {
17484 assert_eq!(
17485 sched
17486 .timeslice_stats_for_test(victim)
17487 .map(|stats| stats.count),
17488 Some(1),
17489 "victim {victim} accounted exactly once"
17490 );
17491 }
17492 }
17493 let mut sched = state.sched.lock().unwrap();
17494 let still_waiting = futexes
17495 .iter()
17496 .map(|futex| sched.futex_waiters_for_test(*futex))
17497 .collect();
17498 let queue = sched.run_queue_for_test();
17499 (still_waiting, queue, sched.next_fuzz_draw_for_test())
17500 }
17501 for fuzz in [false, true] {
17502 let forward = run(fuzz, false).await;
17503 assert_eq!(forward.0.iter().map(Vec::len).collect::<Vec<_>>(), [2, 2]);
17504 assert_eq!(forward, run(fuzz, true).await, "fuzz={fuzz}");
17505 }
17506 }
17507
17508 #[tokio::test]
17516 async fn a_victims_receipt_before_or_after_the_commit_retires_it_the_same_way() {
17517 use reverie::Tool;
17518
17519 let config = Config {
17520 sequentialize_threads: true,
17521 ..Config::default()
17522 };
17523 let [root, sender, victim] = [1, 2, 3].map(DetPid::from_raw);
17524 for early in [true, false] {
17525 let state = GlobalState::initialize(&config, false);
17526 {
17527 let mut sched = state.sched.lock().unwrap();
17528 sched.thread_tree.add_child(root, root, true);
17529 sched.thread_tree.add_child(root, sender, true);
17530 sched.thread_tree.add_child(root, victim, true);
17531 }
17532 for tid in [root, sender, victim] {
17533 install_test_registration(&state, tid, Ivar::new());
17534 state
17535 .sched
17536 .lock()
17537 .unwrap()
17538 .record_thread_mm(tid, MmId::initial(tid));
17539 }
17540 let tool: Detcore = Detcore::new(Tid::from_raw(victim.as_raw()), &config);
17541 let mut thread = tool.init_thread_state(Tid::from_raw(victim.as_raw()), None);
17542 thread.detpid = Some(victim);
17543 let clock = thread.thread_logical_time.clone();
17544 let mut guest = RetirementGuest {
17545 global: &state,
17546 config: &config,
17547 thread,
17548 requests: Mutex::new(Vec::new()),
17549 retired: std::sync::atomic::AtomicBool::new(false),
17550 hold_private_memory_publication: false,
17551 };
17552 let token = state
17553 .sched
17554 .lock()
17555 .unwrap()
17556 .reserve_sigkill(sender, victim, 0)
17557 .unwrap()
17558 .token;
17559 let deregister = || {
17560 state.receive_rpc(
17561 Tid::from_raw(victim.as_raw()),
17562 (
17563 clock.clone(),
17564 MmId::initial(victim),
17565 GlobalRequest::DeregisterThread(ThreadDeregistration {
17566 dettid: victim,
17567 detpid: victim,
17568 mm: MmId::initial(victim),
17569 thread_start_entered: true,
17570 vfork_origin: None,
17571 timeslice_stats: TimesliceStats {
17572 count: 1,
17573 sum_ns: 10,
17574 min_ns: 10,
17575 max_ns: 10,
17576 },
17577 syscall_count: 3,
17578 syscall_histogram: Default::default(),
17579 chaos_epochs: Vec::new(),
17580 killed_by_group_signal: false,
17581 passed_descriptors_out: false,
17582 }),
17583 ),
17584 )
17585 };
17586 {
17587 let mut rejoin = Resources::new(victim);
17588 rejoin.insert(
17589 ResourceID::BlockedExternalContinue(ExternalOpId::new(victim, 7)),
17590 Permission::RW,
17591 );
17592 let mut call = std::pin::pin!(super::resource_request(&mut guest, rejoin));
17593 assert!(futures::poll!(call.as_mut()).is_pending());
17594 if early {
17595 assert_eq!(deregister().await.1, GlobalResponse::DeregisterThread(()));
17596 }
17597 state.sched.lock().unwrap().commit_sigkill(token, true);
17598 if !early {
17599 assert_eq!(deregister().await.1, GlobalResponse::DeregisterThread(()));
17600 }
17601 for _ in 0..4 {
17602 tokio::task::yield_now().await;
17603 assert!(futures::poll!(call.as_mut()).is_pending(), "early={early}");
17604 }
17605 }
17606 assert!(!guest.retired.load(std::sync::atomic::Ordering::SeqCst));
17607 assert!(
17608 state.sched.lock().unwrap().sigkill_fence_holds(),
17609 "early={early}: the exit is not yet published"
17610 );
17611 <GlobalState as GlobalTool>::on_backend_process_exited(&state, victim.as_raw());
17612 let sched = state.sched.lock().unwrap();
17613 assert!(!sched.sigkill_fence_holds(), "early={early}");
17614 assert!(sched.process_is_unreaped_zombie(victim), "early={early}");
17615 assert_eq!(
17616 sched
17617 .timeslice_stats_for_test(victim)
17618 .map(|stats| stats.count),
17619 Some(1),
17620 "early={early}"
17621 );
17622 }
17623 }
17624
17625 #[tokio::test]
17626 async fn thread_exited_uses_natural_retirement_for_killed_stale_and_failed_rpcs() {
17627 use std::sync::atomic::Ordering;
17628
17629 use reverie::Tool;
17630
17631 for cause in ["killed", "stale image", "backend failure", "live"] {
17632 let (config, state, tid, pid) = cancellation_test_state();
17633 install_test_registration(&state, tid, Ivar::new());
17634 let tool: Detcore = Detcore::new(Tid::from_raw(tid.as_raw()), &config);
17635 let mut thread = tool.init_thread_state(Tid::from_raw(tid.as_raw()), None);
17636 thread.detpid = Some(pid);
17637 thread.thread_logical_time.add_syscall_with_cost(37);
17638 match cause {
17639 "killed" => {
17640 state
17641 .sched
17642 .lock()
17643 .unwrap()
17644 .logically_kill_thread(&tid, &pid, thread.mm_id)
17645 }
17646 "stale image" => {
17647 state
17648 .sched
17649 .lock()
17650 .unwrap()
17651 .install_test_exec_incarnation(tid, thread.mm_id);
17652 thread.mm_id = thread.mm_id.for_exec(pid);
17653 }
17654 "backend failure" => state.report_backend_failure(reverie::BackendFailure {
17655 pid: Tid::from_raw(pid.as_raw()),
17656 tid: Tid::from_raw(tid.as_raw()),
17657 phase: "natural retirement control",
17658 }),
17659 "live" => {}
17660 _ => unreachable!(),
17661 }
17662 let mut guest = RetirementGuest {
17663 global: &state,
17664 config: &config,
17665 thread,
17666 requests: Mutex::new(Vec::new()),
17667 retired: std::sync::atomic::AtomicBool::new(false),
17668 hold_private_memory_publication: false,
17669 };
17670 let before = guest.thread.thread_logical_time.as_nanos();
17671 {
17672 let mut call = std::pin::pin!(super::send_and_update_time(
17673 &mut guest,
17674 GlobalRequest::GlobalTimeLowerBound
17675 ));
17676 if cause == "live" {
17677 assert!(matches!(
17678 futures::poll!(call.as_mut()),
17679 std::task::Poll::Ready((_, GlobalResponse::GlobalTimeLowerBound(_)))
17680 ));
17681 } else {
17682 assert!(futures::poll!(call.as_mut()).is_pending(), "{cause}");
17683 }
17684 }
17685 assert_eq!(
17689 guest.retired.load(Ordering::SeqCst),
17690 matches!(cause, "killed" | "stale image"),
17691 "{cause}"
17692 );
17693 assert_eq!(guest.requests.lock().unwrap().len(), 1);
17694 assert_eq!(guest.thread.thread_logical_time.as_nanos(), before);
17695 if cause == "backend failure" {
17696 assert!(state.sched.lock().unwrap().backend_failed());
17697 assert!(
17698 futures::poll!(std::pin::pin!(state.wait_for_backend_failure())).is_ready()
17699 );
17700 }
17701 }
17702 }
17703
17704 #[tokio::test]
17710 async fn an_ordinary_request_first_publishes_changed_dispositions() {
17711 use reverie::Tool;
17712
17713 use crate::syscalls::signal_shadow::CopiedDispositions;
17714 use crate::syscalls::signal_shadow::PublishedDispositions;
17715
17716 let config = Config {
17717 sequentialize_threads: true,
17718 ..Config::default()
17719 }
17720 .with_backend(|backend| detcore_model::config::set_kernel_signal_facts(backend, true));
17721 let state = GlobalState::initialize(&config, false);
17722 let leader = DetTid::from_raw(17);
17723 let process = DetPid::from_raw(17);
17724 state
17725 .sched
17726 .lock()
17727 .unwrap()
17728 .thread_tree
17729 .add_child(leader, leader, true);
17730 install_test_registration(&state, leader, Ivar::new());
17731 let tool = <Detcore as Tool>::new(reverie::Pid::from_raw(leader.as_raw()), &config);
17732 let mut thread = tool.init_thread_state(Tid::from_raw(leader.as_raw()), None);
17733 thread.detpid = Some(process);
17734 let usr1 = 1 << (libc::SIGUSR1 - 1);
17735 thread.signal_shadow = Some(crate::signal_state::SignalShadow::seeded(usr1, 0));
17736 let mut guest = ExternalRegistrationGuest {
17737 global: &state,
17738 config: &config,
17739 thread,
17740 requests: Mutex::new(Vec::new()),
17741 post_exec: false,
17742 ppid: None,
17743 private_memory: None,
17744 };
17745 let mutation = |n: u32| GlobalRequest::RecordHostMutation {
17746 path: format!("/a5-step-2b/{n}"),
17747 };
17748 let ignoring = CopiedDispositions {
17749 ignored: usr1,
17750 caught: 0,
17751 };
17752 let held = |state: &GlobalState| {
17753 state
17754 .sched
17755 .lock()
17756 .unwrap()
17757 .published_dispositions
17758 .get(&process)
17759 .copied()
17760 };
17761
17762 super::send_and_update_time(&mut guest, mutation(1)).await;
17763 super::send_and_update_time(&mut guest, mutation(2)).await;
17764 assert_eq!(
17765 *guest.requests.lock().unwrap(),
17766 vec![
17767 GlobalRequest::PublishSignalDispositions(1, Some(ignoring)),
17768 GlobalRequest::PublishSignalMask(Some(0)),
17769 mutation(1),
17770 mutation(2),
17771 ],
17772 "published once, before the first request"
17773 );
17774 let entry = |generation, dispositions| PublishedDispositions {
17776 generation,
17777 dispositions,
17778 ..Default::default()
17779 };
17780 let record = |generation, accepted, dispositions| PublishedDispositions {
17781 generation,
17782 accepted,
17783 dispositions,
17784 };
17785 assert_eq!(held(&state), Some(entry(1, Some(ignoring))));
17786 assert_eq!(
17787 *guest.thread.published_dispositions.lock().unwrap(),
17788 record(1, 1, Some(ignoring))
17789 );
17790 assert!(
17791 state
17792 .sched
17793 .lock()
17794 .unwrap()
17795 .published_dispositions_for(leader, libc::SIGUSR1)
17796 .is_some()
17797 );
17798
17799 guest.requests.lock().unwrap().clear();
17802 crate::syscalls::signal_shadow::before_clone(
17803 &mut guest,
17804 (libc::CLONE_VM | libc::CLONE_SIGHAND) as u64,
17805 )
17806 .await;
17807 assert_eq!(
17808 *guest.requests.lock().unwrap(),
17809 vec![GlobalRequest::PublishSignalDispositions(2, None)]
17810 );
17811 assert_eq!(held(&state), Some(entry(2, None)));
17812 assert_eq!(
17813 *guest.thread.published_dispositions.lock().unwrap(),
17814 record(2, 2, None)
17815 );
17816 super::send_and_update_time(&mut guest, mutation(3)).await;
17817 assert_eq!(
17818 guest.requests.lock().unwrap().len(),
17819 2,
17820 "nothing more to withdraw"
17821 );
17822
17823 guest.thread.signal_shadow = Some(crate::signal_state::SignalShadow::seeded(usr1, 0));
17826 let mut turn = state.sched.lock().unwrap().next_turns.remove(&leader);
17827 guest.requests.lock().unwrap().clear();
17828 super::send_and_update_time(&mut guest, mutation(4)).await;
17829 assert_eq!(held(&state), Some(entry(2, None)), "refused");
17830 assert_eq!(
17831 *guest.thread.published_dispositions.lock().unwrap(),
17832 record(3, 2, None)
17833 );
17834 state
17835 .sched
17836 .lock()
17837 .unwrap()
17838 .next_turns
17839 .insert(leader, turn.take().unwrap());
17840 super::send_and_update_time(&mut guest, mutation(5)).await;
17841 assert_eq!(held(&state), Some(entry(4, Some(ignoring))));
17842 assert_eq!(
17843 *guest.thread.published_dispositions.lock().unwrap(),
17844 record(4, 4, Some(ignoring))
17845 );
17846
17847 let stale = entry(
17852 50,
17853 Some(CopiedDispositions {
17854 ignored: 0,
17855 caught: usr1,
17856 }),
17857 );
17858 state
17859 .sched
17860 .lock()
17861 .unwrap()
17862 .published_dispositions
17863 .insert(process, stale);
17864 *guest.thread.published_dispositions.lock().unwrap() = record(4, 4, None);
17865 super::send_and_update_time(&mut guest, mutation(6)).await;
17866 assert_eq!(held(&state), Some(entry(51, Some(ignoring))));
17867 assert_eq!(
17868 *guest.thread.published_dispositions.lock().unwrap(),
17869 record(51, 51, Some(ignoring))
17870 );
17871 state
17874 .sched
17875 .lock()
17876 .unwrap()
17877 .published_dispositions
17878 .insert(process, entry(52, stale.dispositions));
17879 *guest.thread.published_dispositions.lock().unwrap() = record(51, 51, None);
17880 super::send_and_update_time(&mut guest, mutation(7)).await;
17881 assert_eq!(held(&state), Some(entry(53, Some(ignoring))));
17882 assert_eq!(
17883 *guest.thread.published_dispositions.lock().unwrap(),
17884 record(53, 53, Some(ignoring))
17885 );
17886 assert_eq!(
17887 *guest.requests.lock().unwrap(),
17888 vec![
17889 GlobalRequest::PublishSignalDispositions(3, Some(ignoring)),
17890 mutation(4),
17891 GlobalRequest::PublishSignalDispositions(4, Some(ignoring)),
17892 mutation(5),
17893 GlobalRequest::PublishSignalDispositions(5, Some(ignoring)),
17894 GlobalRequest::PublishSignalDispositions(51, Some(ignoring)),
17895 mutation(6),
17896 GlobalRequest::PublishSignalDispositions(52, Some(ignoring)),
17897 GlobalRequest::PublishSignalDispositions(53, Some(ignoring)),
17898 mutation(7),
17899 ]
17900 );
17901 }
17902
17903 #[tokio::test]
17909 async fn an_ordinary_request_first_publishes_a_changed_mask() {
17910 use reverie::Tool;
17911
17912 let config = Config {
17913 sequentialize_threads: true,
17914 ..Config::default()
17915 }
17916 .with_backend(|backend| detcore_model::config::set_kernel_signal_facts(backend, true));
17917 let state = GlobalState::initialize(&config, false);
17918 let leader = DetTid::from_raw(17);
17919 let process = DetPid::from_raw(17);
17920 state
17921 .sched
17922 .lock()
17923 .unwrap()
17924 .thread_tree
17925 .add_child(leader, leader, true);
17926 install_test_registration(&state, leader, Ivar::new());
17927 let tool = <Detcore as Tool>::new(reverie::Pid::from_raw(leader.as_raw()), &config);
17928 let mut thread = tool.init_thread_state(Tid::from_raw(leader.as_raw()), None);
17929 thread.detpid = Some(process);
17930 let usr1 = 1 << (libc::SIGUSR1 - 1);
17931 thread.signal_shadow = Some(crate::signal_state::SignalShadow::seeded(0, usr1));
17932 let mut guest = ExternalRegistrationGuest {
17933 global: &state,
17934 config: &config,
17935 thread,
17936 requests: Mutex::new(Vec::new()),
17937 post_exec: false,
17938 ppid: None,
17939 private_memory: None,
17940 };
17941 let mutation = |n: u32| GlobalRequest::RecordHostMutation {
17942 path: format!("/a5-step-2b-mask/{n}"),
17943 };
17944 let masks = |guest: &ExternalRegistrationGuest<'_>| -> Vec<GlobalRequest> {
17945 guest
17946 .requests
17947 .lock()
17948 .unwrap()
17949 .iter()
17950 .filter(|request| !matches!(request, GlobalRequest::PublishSignalDispositions(..)))
17951 .cloned()
17952 .collect()
17953 };
17954 let held = |state: &GlobalState| {
17955 state
17956 .sched
17957 .lock()
17958 .unwrap()
17959 .published_masks
17960 .get(&leader)
17961 .copied()
17962 };
17963
17964 super::send_and_update_time(&mut guest, mutation(1)).await;
17965 super::send_and_update_time(&mut guest, mutation(2)).await;
17966 assert_eq!(
17969 *guest.requests.lock().unwrap(),
17970 vec![
17971 GlobalRequest::PublishSignalDispositions(
17972 1,
17973 Some(crate::syscalls::signal_shadow::CopiedDispositions {
17974 ignored: 0,
17975 caught: 0,
17976 })
17977 ),
17978 GlobalRequest::PublishSignalMask(Some(usr1)),
17979 mutation(1),
17980 mutation(2),
17981 ],
17982 "published once, before the first request"
17983 );
17984 assert_eq!(held(&state), Some(usr1));
17985 assert_eq!(guest.thread.published_mask, Some(Some(usr1)));
17986 assert_eq!(
17987 state
17988 .sched
17989 .lock()
17990 .unwrap()
17991 .published_mask_for(leader, libc::SIGUSR1),
17992 Some(usr1)
17993 );
17994
17995 guest
17998 .thread
17999 .signal_shadow
18000 .as_mut()
18001 .unwrap()
18002 .thread
18003 .wait_mask = Some(0);
18004 guest.requests.lock().unwrap().clear();
18005 super::send_and_update_time(&mut guest, mutation(3)).await;
18006 assert_eq!(
18007 masks(&guest),
18008 vec![GlobalRequest::PublishSignalMask(None), mutation(3)]
18009 );
18010 assert_eq!(held(&state), None);
18011
18012 guest
18015 .thread
18016 .signal_shadow
18017 .as_mut()
18018 .unwrap()
18019 .thread
18020 .wait_mask = None;
18021 let mut turn = state.sched.lock().unwrap().next_turns.remove(&leader);
18022 guest.requests.lock().unwrap().clear();
18023 super::send_and_update_time(&mut guest, mutation(4)).await;
18024 assert_eq!(held(&state), None, "refused");
18025 assert_eq!(guest.thread.published_mask, Some(None));
18026 state
18027 .sched
18028 .lock()
18029 .unwrap()
18030 .next_turns
18031 .insert(leader, turn.take().unwrap());
18032 super::send_and_update_time(&mut guest, mutation(5)).await;
18033 assert_eq!(
18034 masks(&guest),
18035 vec![
18036 GlobalRequest::PublishSignalMask(Some(usr1)),
18037 mutation(4),
18038 GlobalRequest::PublishSignalMask(Some(usr1)),
18039 mutation(5),
18040 ]
18041 );
18042 assert_eq!(held(&state), Some(usr1));
18043
18044 guest
18047 .thread
18048 .signal_shadow
18049 .as_ref()
18050 .unwrap()
18051 .forget_dispositions();
18052 crate::syscalls::signal_shadow::at_exec(&mut guest);
18053 assert!(guest.thread.signal_shadow.is_none(), "the copy was lost");
18054 guest.requests.lock().unwrap().clear();
18055 super::send_and_update_time(&mut guest, mutation(6)).await;
18056 assert_eq!(
18057 masks(&guest),
18058 vec![GlobalRequest::PublishSignalMask(None), mutation(6)]
18059 );
18060 assert_eq!(held(&state), None);
18061 }
18062
18063 async fn check_external_child_tid_registration(
18064 flags: CloneFlags,
18065 supplied_address: usize,
18066 expected_address: usize,
18067 ) {
18068 use reverie::Tool;
18069
18070 let config = Config {
18071 sequentialize_threads: true,
18072 runs_post_fork: crate::RunsPostFork::Parent,
18075 ..Config::default()
18076 }
18077 .with_backend(|backend| {
18078 backend.needs_killed_thread_rpc_cancellation = true;
18079 });
18080 let state = GlobalState::initialize(&config, false);
18081 let parent = DetTid::from_raw(17);
18082 let parent_pid = DetPid::from_raw(17);
18083 state
18084 .sched
18085 .lock()
18086 .unwrap()
18087 .thread_tree
18088 .add_child(parent, parent, true);
18089 install_test_registration(&state, parent, Ivar::new());
18090 let tool = Detcore::new(reverie::Pid::from_raw(parent.as_raw()), &config);
18091 let mut thread = tool.init_thread_state(Tid::from_raw(parent.as_raw()), None);
18092 thread.detpid = Some(parent_pid);
18093 let mut guest = ExternalRegistrationGuest {
18094 global: &state,
18095 config: &config,
18096 thread,
18097 requests: Mutex::new(Vec::new()),
18098 post_exec: false,
18099 ppid: None,
18100 private_memory: None,
18101 };
18102 let child = DetTid::from_raw(18);
18103 let exit_signal = if flags.contains(CloneFlags::CLONE_THREAD) {
18104 0
18105 } else {
18106 libc::SIGCHLD
18107 };
18108 tokio::time::timeout(Duration::from_secs(2), async {
18109 let mut registration = std::pin::pin!(tool.register_external_child(
18110 &mut guest,
18111 Tid::from_raw(child.as_raw()),
18112 supplied_address,
18113 flags,
18114 exit_signal,
18115 None,
18116 ));
18117 assert!(futures::poll!(registration.as_mut()).is_pending());
18118 let committed = crate::scheduler::do_a_turn_blocking(
18122 state.sched.clone(),
18123 state.global_time.clone(),
18124 &Err(crate::scheduler::SkipTurn),
18125 )
18126 .await
18127 .expect("the parent continuation must commit");
18128 assert_eq!(committed.tid, parent);
18129 assert_eq!(
18130 committed.resources,
18131 std::collections::HashMap::from([(
18132 crate::resources::ResourceID::ParentContinue { parent, child },
18133 crate::resources::Permission::W,
18134 )]),
18135 );
18136 registration.await;
18137 })
18138 .await
18139 .expect("external child registration must return without running a guest");
18140
18141 assert_eq!(guest.thread.clone_flags, None);
18142 assert_eq!(guest.thread.dettid, parent);
18143 let mut scheduler = state.sched.lock().unwrap();
18144 assert_eq!(scheduler.next_turns.len(), 2);
18145 assert!(scheduler.next_turns.contains_key(&parent));
18146 assert_eq!(
18147 scheduler.next_turns[&child].child_tid_addr,
18148 expected_address
18149 );
18150 assert_eq!(
18151 *guest.requests.lock().unwrap(),
18152 vec![GlobalRequest::CreateChildThread(
18153 child,
18154 parent_pid,
18155 expected_address,
18156 Some(flags),
18157 exit_signal,
18158 None,
18159 Some(DEFAULT_PRIORITY),
18160 )],
18161 "external registration must send one correctly gated real RPC"
18162 );
18163 let child_pid = if flags.contains(CloneFlags::CLONE_THREAD) {
18164 parent_pid
18165 } else {
18166 child
18167 };
18168 let child_mm = MmId::for_clone(
18169 MmId::initial(parent_pid),
18170 child,
18171 flags.contains(CloneFlags::CLONE_VM),
18172 );
18173 scheduler.logically_kill_thread(&child, &child_pid, child_mm);
18174 assert!(!scheduler.next_turns.contains_key(&child));
18175 assert!(scheduler.next_turns.contains_key(&parent));
18176 assert_eq!(
18177 scheduler.child_tid_was_cleared(
18178 FutexID::private(child_mm, supplied_address),
18179 child.as_raw(),
18180 ),
18181 expected_address != 0,
18182 "exit must use only the registered child-TID address"
18183 );
18184 assert!(!scheduler.child_tid_was_cleared(FutexID::private(child_mm, 0), child.as_raw(),));
18185 assert!(!scheduler.child_tid_was_cleared(
18186 FutexID::private(child_mm, supplied_address),
18187 parent.as_raw(),
18188 ));
18189 }
18190
18191 #[tokio::test]
18192 async fn external_registration_without_child_cleartid_disables_exit_wake() {
18193 for kind in [
18194 CloneFlags::empty(),
18195 CloneFlags::CLONE_THREAD | CloneFlags::CLONE_VM | CloneFlags::CLONE_SIGHAND,
18196 ] {
18197 for registration in [CloneFlags::empty(), CloneFlags::CLONE_CHILD_SETTID] {
18198 check_external_child_tid_registration(kind | registration, 0x1234, 0).await;
18199 }
18200 }
18201 }
18202
18203 #[tokio::test]
18204 async fn external_registration_with_child_cleartid_preserves_exact_exit_wake() {
18205 for kind in [
18206 CloneFlags::empty(),
18207 CloneFlags::CLONE_THREAD | CloneFlags::CLONE_VM | CloneFlags::CLONE_SIGHAND,
18208 ] {
18209 for registration in [
18210 CloneFlags::CLONE_CHILD_CLEARTID,
18211 CloneFlags::CLONE_CHILD_CLEARTID | CloneFlags::CLONE_CHILD_SETTID,
18212 ] {
18213 check_external_child_tid_registration(kind | registration, 0x1234, 0x1234).await;
18214 check_external_child_tid_registration(kind | registration, 0, 0).await;
18215 }
18216 }
18217 }
18218
18219 #[tokio::test]
18220 async fn set_child_tid_address_rpc_updates_and_resets_the_registration() {
18221 let (config, state, dettid, detpid) = cancellation_test_state();
18222 install_test_registration(&state, dettid, Ivar::new());
18223 state
18224 .sched
18225 .lock()
18226 .unwrap()
18227 .next_turns
18228 .get_mut(&dettid)
18229 .unwrap()
18230 .child_tid_addr = 0x1234;
18231
18232 let response = state
18233 .receive_rpc(
18234 reverie::Tid::from_raw(dettid.as_raw()),
18235 (
18236 DetTime::new(&config),
18237 MmId::initial(detpid),
18238 GlobalRequest::SetChildTidAddress(0),
18239 ),
18240 )
18241 .await;
18242
18243 assert_eq!(response.1, GlobalResponse::SetChildTidAddress(()));
18244 assert_eq!(
18245 state
18246 .sched
18247 .lock()
18248 .unwrap()
18249 .next_turns
18250 .get(&dettid)
18251 .unwrap()
18252 .child_tid_addr,
18253 0
18254 );
18255 }
18256
18257 #[tokio::test]
18262 async fn sigkill_notifications_record_the_doomed_process() {
18263 let (config, state, dettid, detpid) = cancellation_test_state();
18264 let notify = |request| {
18265 state.receive_rpc(
18266 reverie::Tid::from_raw(dettid.as_raw()),
18267 (DetTime::new(&config), MmId::initial(detpid), request),
18268 )
18269 };
18270 let victim = DetPid::from_raw(41);
18271 assert_eq!(
18272 notify(GlobalRequest::NotifySignalPending(
18273 DetTid::from_raw(41),
18274 SigWrapper(libc::SIGKILL),
18275 Some(victim),
18276 super::SignalScope::Process,
18277 ))
18278 .await
18279 .1,
18280 GlobalResponse::NotifySignalPending(())
18281 );
18282 assert_eq!(
18283 state.sched.lock().unwrap().sigkill_recorded(victim),
18284 (true, false)
18285 );
18286 let other = DetPid::from_raw(43);
18287 notify(GlobalRequest::NotifySignalPending(
18288 DetTid::from_raw(43),
18289 SigWrapper(libc::SIGTERM),
18290 Some(other),
18291 super::SignalScope::Process,
18292 ))
18293 .await;
18294 assert_eq!(
18295 state.sched.lock().unwrap().sigkill_recorded(other),
18296 (false, false)
18297 );
18298 assert_eq!(
18300 notify(GlobalRequest::NotifyUnresolvedSigkill(Some(detpid)))
18301 .await
18302 .1,
18303 GlobalResponse::NotifyUnresolvedSigkill(())
18304 );
18305 assert_eq!(
18306 state.sched.lock().unwrap().sigkill_recorded(detpid),
18307 (true, false)
18308 );
18309 assert_eq!(
18310 notify(GlobalRequest::NotifyUnresolvedSigkill(None)).await.1,
18311 GlobalResponse::NotifyUnresolvedSigkill(())
18312 );
18313 assert_eq!(
18314 state.sched.lock().unwrap().sigkill_recorded(other),
18315 (false, true)
18316 );
18317 }
18318
18319 async fn child_start_clock_trajectory(
18320 start_before_selection: bool,
18321 child_first: bool,
18322 ) -> (Vec<LogicalTime>, Vec<DetTid>, bool) {
18323 let config = Config {
18324 sequentialize_threads: true,
18325 runs_post_fork: if child_first {
18326 RunsPostFork::Child
18327 } else {
18328 RunsPostFork::Parent
18329 },
18330 ..Config::default()
18331 };
18332 let state = GlobalState::initialize(&config, false);
18333 let parent = DetTid::from_raw(17);
18334 let parent_pid = parent;
18335 state
18336 .sched
18337 .lock()
18338 .unwrap()
18339 .thread_tree
18340 .add_child(parent, parent, true);
18341 let child = DetTid::from_raw(parent.as_raw() + 1);
18342 let parent_mm = MmId::initial(parent_pid);
18343 let child_mm = MmId::for_clone(parent_mm, child, false);
18344 install_test_registration(&state, parent, Ivar::new());
18345 let epoch = DetTime::new(&config).as_nanos();
18346 let mut parent_clock = DetTime::new(&config);
18347 parent_clock.advance_to(epoch + LogicalTime::from_nanos(1_001));
18348 let child_clock = parent_clock.clone_for_child();
18349
18350 let mut registration = Box::pin(state.receive_rpc(
18353 Tid::from_raw(parent.as_raw()),
18354 (
18355 parent_clock.clone(),
18356 parent_mm,
18357 GlobalRequest::CreateChildThread(
18358 child,
18359 parent_pid,
18360 0,
18361 Some(CloneFlags::empty()),
18362 libc::SIGCHLD,
18363 None,
18364 Some(DEFAULT_PRIORITY),
18365 ),
18366 ),
18367 ));
18368 assert!(futures::poll!(&mut registration).is_pending());
18369 let child_request = state.sched.lock().unwrap().next_turns[&child].req.clone();
18370 let mut startup = Box::pin(state.receive_rpc(
18371 Tid::from_raw(child.as_raw()),
18372 (
18373 child_clock.clone(),
18374 child_mm,
18375 GlobalRequest::StartNewThread(child, child, None, None),
18376 ),
18377 ));
18378 if start_before_selection {
18379 assert!(futures::poll!(&mut startup).is_pending());
18381 assert!(futures::poll!(&mut startup).is_pending());
18382 assert!(child_request.try_read().is_some());
18383 }
18384
18385 let skipped = Err(crate::scheduler::SkipTurn);
18386 let mut turn = Box::pin(crate::scheduler::do_a_turn_blocking(
18387 state.sched.clone(),
18388 state.global_time.clone(),
18389 &skipped,
18390 ));
18391 if !start_before_selection && child_first {
18392 assert!(futures::poll!(&mut turn).is_pending());
18393 assert_eq!(child_request.to_string(), "<ivar HasWaiter>");
18394 assert!(futures::poll!(&mut startup).is_pending());
18395 assert!(futures::poll!(&mut startup).is_pending());
18396 assert!(child_request.try_read().is_some());
18397 }
18398 let first = turn.await.expect("first post-fork turn must commit");
18399 if !start_before_selection && !child_first {
18400 assert!(child_request.try_read().is_none());
18401 assert!(futures::poll!(&mut startup).is_pending());
18402 assert!(futures::poll!(&mut startup).is_pending());
18403 assert!(child_request.try_read().is_some());
18404 }
18405 let child_resource = ResourceID::MemAddrSpace(child);
18406 let parent_resource = ResourceID::ParentContinue { parent, child };
18407 let (first_tid, first_resource, first_permission, second_resource, second_permission) =
18408 if child_first {
18409 (
18410 child,
18411 &child_resource,
18412 Permission::RW,
18413 &parent_resource,
18414 Permission::W,
18415 )
18416 } else {
18417 (
18418 parent,
18419 &parent_resource,
18420 Permission::W,
18421 &child_resource,
18422 Permission::RW,
18423 )
18424 };
18425 assert_eq!(first.tid, first_tid);
18426 assert_eq!(first.resources.len(), 1);
18427 assert_eq!(first.resources.get(first_resource), Some(&first_permission));
18428 if child_first {
18429 assert_eq!(
18430 startup.as_mut().await,
18431 (None, granted(GlobalResponse::StartNewThread(None)))
18432 );
18433 } else {
18434 assert_eq!(
18435 registration.as_mut().await,
18436 (None, granted(GlobalResponse::CreateChildThread(None)))
18437 );
18438 }
18439 let first_time = state.sched.lock().unwrap().committed_time;
18440 assert_eq!(first_time, epoch + LogicalTime::from_nanos(1_001));
18441 assert_eq!(
18442 state.global_time.lock().unwrap().threads_time(child),
18443 child_clock.as_nanos()
18444 );
18445
18446 let (mut first_clock, first_mm) = if child_first {
18449 (child_clock, child_mm)
18450 } else {
18451 (parent_clock, parent_mm)
18452 };
18453 first_clock.advance_to(first_clock.as_nanos() + LogicalTime::from_nanos(1));
18454 let mut resources = Resources::new(first_tid);
18455 resources.insert(ResourceID::MemAddrSpace(first_tid), Permission::RW);
18456 let mut work = Box::pin(state.receive_rpc(
18457 Tid::from_raw(first_tid.as_raw()),
18458 (
18459 first_clock,
18460 first_mm,
18461 GlobalRequest::RequestResources(resources, first_tid),
18462 ),
18463 ));
18464 assert!(futures::poll!(&mut work).is_pending());
18465 let second = crate::scheduler::do_a_turn_blocking(
18466 state.sched.clone(),
18467 state.global_time.clone(),
18468 &Ok(first),
18469 )
18470 .await
18471 .expect("other thread's continuation must commit");
18472 assert_eq!(second.resources.len(), 1);
18473 assert_eq!(
18474 second.resources.get(second_resource),
18475 Some(&second_permission)
18476 );
18477 if child_first {
18478 assert_eq!(
18479 registration.await,
18480 (None, granted(GlobalResponse::CreateChildThread(None)))
18481 );
18482 } else {
18483 assert_eq!(
18484 startup.await,
18485 (None, granted(GlobalResponse::StartNewThread(None)))
18486 );
18487 }
18488 let mut scheduler = state.sched.lock().unwrap();
18489 let next_time = scheduler.committed_time;
18490 assert_eq!(next_time, epoch + LogicalTime::from_nanos(501_002));
18491 let queue = scheduler.run_queue.tids().copied().collect();
18492 let next_random = scheduler.child_runs_first_post_fork(RunsPostFork::Random);
18493 (vec![first_time, next_time], queue, next_random)
18494 }
18495
18496 #[tokio::test]
18508 async fn a_clock_observation_grants_no_turn_and_moves_no_time() {
18509 let (config, state, holder, detpid) = cancellation_test_state();
18510 let sibling = DetTid::from_raw(holder.as_raw() + 1);
18511 let mm = MmId::initial(detpid);
18512 state
18513 .sched
18514 .lock()
18515 .unwrap()
18516 .thread_tree
18517 .add_child(holder, sibling, false);
18518 install_test_registration(&state, holder, Ivar::new());
18519 install_test_registration(&state, sibling, Ivar::new());
18520 let mut clock = DetTime::new(&config);
18521 let epoch = clock.as_nanos();
18522 clock.advance_to(epoch + LogicalTime::from_nanos(1_000));
18523 let sibling_clock = clock.clone_for_child();
18524 let request = |tid: DetTid| {
18525 let mut resources = Resources::new(tid);
18526 resources.insert(ResourceID::MemAddrSpace(tid), Permission::RW);
18527 GlobalRequest::RequestResources(resources, detpid)
18528 };
18529 let mut holder_turn = Box::pin(state.receive_rpc(
18530 Tid::from_raw(holder.as_raw()),
18531 (clock.clone(), mm, request(holder)),
18532 ));
18533 assert!(futures::poll!(&mut holder_turn).is_pending());
18534 let mut sibling_turn = Box::pin(state.receive_rpc(
18535 Tid::from_raw(sibling.as_raw()),
18536 (sibling_clock, mm, request(sibling)),
18537 ));
18538 assert!(futures::poll!(&mut sibling_turn).is_pending());
18539 let committed = crate::scheduler::do_a_turn_blocking(
18540 state.sched.clone(),
18541 state.global_time.clone(),
18542 &Err(crate::scheduler::SkipTurn),
18543 )
18544 .await
18545 .expect("one of the two requests is granted");
18546 let (granted, granted_turn, mut waiting, granted_clock) = if committed.tid == holder {
18548 (holder, holder_turn, sibling_turn, clock.clone())
18549 } else {
18550 (sibling, sibling_turn, holder_turn, clock.clone_for_child())
18551 };
18552 assert!(matches!(granted_turn.await.1, GlobalResponse::Granted(_)));
18553 let turn = state.sched.lock().unwrap().turn;
18554 let before = state.global_time.lock().unwrap().as_nanos();
18555 let mut answers = Vec::new();
18556 for _ in 0..2 {
18557 let (_, answer) = state
18558 .receive_rpc(
18559 Tid::from_raw(granted.as_raw()),
18560 (
18561 granted_clock.clone(),
18562 mm,
18563 GlobalRequest::GlobalTimeLowerBound,
18564 ),
18565 )
18566 .await;
18567 answers.push(answer);
18568 for _ in 0..8 {
18569 tokio::task::yield_now().await;
18570 }
18571 assert!(
18572 futures::poll!(&mut waiting).is_pending(),
18573 "the waiting thread gets no turn"
18574 );
18575 }
18576 let after = state.global_time.lock().unwrap().as_nanos();
18577 assert_eq!(answers[0], answers[1], "the same time twice");
18578 assert!(
18579 matches!(answers[0], GlobalResponse::GlobalTimeLowerBound(_)),
18580 "a plain answer, not a hand-off: {:?}",
18581 answers[0]
18582 );
18583 assert_eq!(after, before, "no time moved");
18584 assert_eq!(state.sched.lock().unwrap().turn, turn, "no turn committed");
18585 }
18586
18587 #[tokio::test]
18588 async fn child_start_clock_is_independent_of_first_rpc_arrival() {
18589 for child_first in [true, false] {
18590 assert_eq!(
18591 child_start_clock_trajectory(true, child_first).await,
18592 child_start_clock_trajectory(false, child_first).await,
18593 );
18594 }
18595 }
18596
18597 #[tokio::test]
18598 async fn vfork_registration_does_not_charge_inherited_work_before_startup() {
18599 let (config, state, parent, parent_pid) = cancellation_test_state();
18600 let child = DetTid::from_raw(parent.as_raw() + 1);
18601 let mm = MmId::initial(parent_pid);
18602 install_test_registration(&state, parent, Ivar::new());
18603 let mut parent_clock = DetTime::new(&config);
18604 let epoch = parent_clock.as_nanos();
18605 parent_clock.advance_to(epoch + LogicalTime::from_nanos(1_001));
18606 let child_clock = parent_clock.clone_for_child();
18607 let mut resources = Resources::new(parent);
18608 resources.insert(
18609 ResourceID::BlockingVfork(ExternalOpId::new(parent, 1)),
18610 Permission::RW,
18611 );
18612 let mut blocking = Box::pin(state.receive_rpc(
18613 Tid::from_raw(parent.as_raw()),
18614 (
18615 parent_clock,
18616 mm,
18617 GlobalRequest::RequestResources(resources, parent_pid),
18618 ),
18619 ));
18620 assert!(futures::poll!(&mut blocking).is_pending());
18621 let background = crate::scheduler::do_a_turn_blocking(
18622 state.sched.clone(),
18623 state.global_time.clone(),
18624 &Err(crate::scheduler::SkipTurn),
18625 )
18626 .await;
18627 assert!(background.is_err());
18628 assert_eq!(
18629 blocking.await,
18630 (
18631 None,
18632 granted(GlobalResponse::RequestResources(ResumeStatus::Normal))
18633 )
18634 );
18635 let before_child = state.global_time.lock().unwrap().as_nanos();
18636 assert_eq!(before_child, epoch + LogicalTime::from_nanos(1_001));
18637
18638 let created = state
18640 .receive_rpc(
18641 Tid::from_raw(child.as_raw()),
18642 (
18643 child_clock.clone(),
18644 mm,
18645 GlobalRequest::CreateVforkChildThread(
18646 parent,
18647 parent_pid,
18648 child,
18649 0,
18650 CloneFlags::CLONE_VFORK | CloneFlags::CLONE_VM,
18651 libc::SIGCHLD,
18652 Some(DEFAULT_PRIORITY - 1),
18653 ),
18654 ),
18655 )
18656 .await;
18657 assert_eq!(created, (None, GlobalResponse::CreateChildThread(None)));
18658 assert_eq!(state.global_time.lock().unwrap().as_nanos(), before_child);
18659 assert_eq!(
18660 state.global_time.lock().unwrap().threads_time(child),
18661 child_clock.as_nanos()
18662 );
18663
18664 let mut startup = Box::pin(state.receive_rpc(
18665 Tid::from_raw(child.as_raw()),
18666 (
18667 child_clock,
18668 mm,
18669 GlobalRequest::StartNewThread(child, child, None, None),
18670 ),
18671 ));
18672 assert!(futures::poll!(&mut startup).is_pending());
18673 assert!(futures::poll!(&mut startup).is_pending());
18674 let first = crate::scheduler::do_a_turn_blocking(
18675 state.sched.clone(),
18676 state.global_time.clone(),
18677 &background,
18678 )
18679 .await
18680 .expect("vfork child must receive its first turn");
18681 assert_eq!(first.tid, child);
18682 assert_eq!(
18683 startup.await,
18684 (None, granted(GlobalResponse::StartNewThread(None)))
18685 );
18686 assert_eq!(state.global_time.lock().unwrap().as_nanos(), before_child);
18687 }
18688
18689 fn leave_earlier_thread_behind(
18695 state: &GlobalState,
18696 parent: DetTid,
18697 id: DetTid,
18698 clock: Option<&DetTime>,
18699 ) {
18700 state
18701 .sched
18702 .lock()
18703 .unwrap()
18704 .thread_tree
18705 .add_child(parent, id, true);
18706 if let Some(clock) = clock {
18707 state.global_time.lock().unwrap().update_global_time(
18708 id,
18709 clock.as_nanos(),
18710 clock.inherited_nanos(),
18711 );
18712 }
18713 }
18714
18715 fn reused_id_state(earlier_ran: bool) -> (GlobalState, DetTid, DetPid, DetTid, DetTime) {
18720 let config = Config {
18721 sequentialize_threads: true,
18722 runs_post_fork: RunsPostFork::Child,
18723 ..Config::default()
18724 };
18725 let (config, state, parent, parent_pid) = cancellation_test_state_with_config(config);
18726 let reused = DetTid::from_raw(parent.as_raw() + 1);
18727 install_test_registration(&state, parent, Ivar::new());
18728 let mut parent_clock = DetTime::new(&config);
18729 let epoch = parent_clock.as_nanos();
18730 parent_clock.advance_to(epoch + LogicalTime::from_nanos(1_001));
18731 let mut first_holder = parent_clock.clone_for_child();
18732 first_holder.advance_to(first_holder.as_nanos() + LogicalTime::from_nanos(500));
18733 leave_earlier_thread_behind(&state, parent, reused, earlier_ran.then_some(&first_holder));
18734 parent_clock.advance_to(epoch + LogicalTime::from_nanos(600_000));
18735 (state, parent, parent_pid, reused, parent_clock)
18736 }
18737
18738 fn fork_registration(child: DetTid, parent_pid: DetPid) -> GlobalRequest {
18739 GlobalRequest::CreateChildThread(
18740 child,
18741 parent_pid,
18742 0,
18743 Some(CloneFlags::empty()),
18744 libc::SIGCHLD,
18745 None,
18746 Some(DEFAULT_PRIORITY),
18747 )
18748 }
18749
18750 #[tokio::test]
18756 async fn a_child_on_a_reused_id_starts_its_own_clock() {
18757 let (state, parent, parent_pid, child, parent_clock) = reused_id_state(true);
18758 let parent_mm = MmId::initial(parent_pid);
18759 let child_mm = MmId::for_clone(parent_mm, child, false);
18760 let child_clock = parent_clock.clone_for_child();
18761 let mut registration = Box::pin(state.receive_rpc(
18762 Tid::from_raw(parent.as_raw()),
18763 (
18764 parent_clock,
18765 parent_mm,
18766 fork_registration(child, parent_pid),
18767 ),
18768 ));
18769 assert!(futures::poll!(&mut registration).is_pending());
18770 let before_child = state.global_time.lock().unwrap().as_nanos();
18771
18772 let mut startup = Box::pin(state.receive_rpc(
18773 Tid::from_raw(child.as_raw()),
18774 (
18775 child_clock.clone(),
18776 child_mm,
18777 GlobalRequest::StartNewThread(child, child, None, None),
18778 ),
18779 ));
18780 assert!(futures::poll!(&mut startup).is_pending());
18781 assert!(futures::poll!(&mut startup).is_pending());
18782 assert_eq!(
18783 state.global_time.lock().unwrap().threads_time(child),
18784 child_clock.as_nanos()
18785 );
18786 assert_eq!(state.global_time.lock().unwrap().as_nanos(), before_child);
18787
18788 let first = crate::scheduler::do_a_turn_blocking(
18789 state.sched.clone(),
18790 state.global_time.clone(),
18791 &Err(crate::scheduler::SkipTurn),
18792 )
18793 .await
18794 .expect("the new child must receive its first turn");
18795 assert_eq!(first.tid, child);
18796 assert_eq!(
18797 startup.await,
18798 (None, granted(GlobalResponse::StartNewThread(None)))
18799 );
18800 }
18801
18802 #[tokio::test]
18807 async fn an_early_start_on_a_reused_id_waits_for_its_registration() {
18808 let (state, parent, parent_pid, child, parent_clock) = reused_id_state(true);
18809 let parent_mm = MmId::initial(parent_pid);
18810 let child_mm = MmId::for_clone(parent_mm, child, false);
18811 let child_clock = parent_clock.clone_for_child();
18812 let earlier = state.global_time.lock().unwrap().threads_time(child);
18813
18814 let mut startup = Box::pin(state.receive_rpc(
18815 Tid::from_raw(child.as_raw()),
18816 (
18817 child_clock.clone(),
18818 child_mm,
18819 GlobalRequest::StartNewThread(child, child, None, None),
18820 ),
18821 ));
18822 assert!(futures::poll!(&mut startup).is_pending());
18823 assert!(futures::poll!(&mut startup).is_pending());
18824 assert_eq!(
18825 state.global_time.lock().unwrap().threads_time(child),
18826 earlier,
18827 "no clock is published before the registration"
18828 );
18829
18830 let mut registration = Box::pin(state.receive_rpc(
18831 Tid::from_raw(parent.as_raw()),
18832 (
18833 parent_clock,
18834 parent_mm,
18835 fork_registration(child, parent_pid),
18836 ),
18837 ));
18838 assert!(futures::poll!(&mut registration).is_pending());
18839 assert!(!state.global_time.lock().unwrap().contains_thread(child));
18840 let before_child = state.global_time.lock().unwrap().as_nanos();
18841 assert!(futures::poll!(&mut startup).is_pending());
18842 assert!(futures::poll!(&mut startup).is_pending());
18843 assert_eq!(
18844 state.global_time.lock().unwrap().threads_time(child),
18845 child_clock.as_nanos()
18846 );
18847 assert_eq!(state.global_time.lock().unwrap().as_nanos(), before_child);
18848
18849 let first = crate::scheduler::do_a_turn_blocking(
18850 state.sched.clone(),
18851 state.global_time.clone(),
18852 &Err(crate::scheduler::SkipTurn),
18853 )
18854 .await
18855 .expect("the new child must receive its first turn");
18856 assert_eq!(first.tid, child);
18857 assert_eq!(
18858 startup.await,
18859 (None, granted(GlobalResponse::StartNewThread(None)))
18860 );
18861 }
18862
18863 #[tokio::test]
18868 async fn an_early_start_on_an_id_whose_holder_never_ran_keeps_its_clock() {
18869 let (state, parent, parent_pid, child, parent_clock) = reused_id_state(false);
18870 let parent_mm = MmId::initial(parent_pid);
18871 let child_mm = MmId::for_clone(parent_mm, child, false);
18872 let child_clock = parent_clock.clone_for_child();
18873 let mut startup = Box::pin(state.receive_rpc(
18874 Tid::from_raw(child.as_raw()),
18875 (
18876 child_clock.clone(),
18877 child_mm,
18878 GlobalRequest::StartNewThread(child, child, None, None),
18879 ),
18880 ));
18881 assert!(futures::poll!(&mut startup).is_pending());
18882 assert!(futures::poll!(&mut startup).is_pending());
18883 assert!(!state.global_time.lock().unwrap().contains_thread(child));
18884
18885 let mut registration = Box::pin(state.receive_rpc(
18886 Tid::from_raw(parent.as_raw()),
18887 (
18888 parent_clock,
18889 parent_mm,
18890 fork_registration(child, parent_pid),
18891 ),
18892 ));
18893 assert!(futures::poll!(&mut registration).is_pending());
18894 assert!(futures::poll!(&mut startup).is_pending());
18895 assert!(futures::poll!(&mut startup).is_pending());
18896 assert_eq!(
18897 state.global_time.lock().unwrap().threads_time(child),
18898 child_clock.as_nanos()
18899 );
18900 let first = crate::scheduler::do_a_turn_blocking(
18901 state.sched.clone(),
18902 state.global_time.clone(),
18903 &Err(crate::scheduler::SkipTurn),
18904 )
18905 .await
18906 .expect("the new child must receive its first turn");
18907 assert_eq!(first.tid, child);
18908 assert_eq!(
18909 startup.await,
18910 (None, granted(GlobalResponse::StartNewThread(None)))
18911 );
18912 }
18913
18914 #[tokio::test]
18917 async fn a_child_on_the_id_of_a_child_that_ran_and_exited_starts_its_own_clock() {
18918 let config = Config {
18919 sequentialize_threads: true,
18920 runs_post_fork: RunsPostFork::Child,
18921 ..Config::default()
18922 };
18923 let (config, state, parent, parent_pid) = cancellation_test_state_with_config(config);
18924 let child = DetTid::from_raw(parent.as_raw() + 1);
18925 let parent_mm = MmId::initial(parent_pid);
18926 let child_mm = MmId::for_clone(parent_mm, child, false);
18927 install_test_registration(&state, parent, Ivar::new());
18928 let mut parent_clock = DetTime::new(&config);
18929 let epoch = parent_clock.as_nanos();
18930 parent_clock.advance_to(epoch + LogicalTime::from_nanos(1_001));
18931
18932 let mut first_registration = Box::pin(state.receive_rpc(
18933 Tid::from_raw(parent.as_raw()),
18934 (
18935 parent_clock.clone(),
18936 parent_mm,
18937 fork_registration(child, parent_pid),
18938 ),
18939 ));
18940 assert!(futures::poll!(&mut first_registration).is_pending());
18941 let mut first_holder = parent_clock.clone_for_child();
18942 let mut first_start = Box::pin(state.receive_rpc(
18943 Tid::from_raw(child.as_raw()),
18944 (
18945 first_holder.clone(),
18946 child_mm,
18947 GlobalRequest::StartNewThread(child, child, None, None),
18948 ),
18949 ));
18950 assert!(futures::poll!(&mut first_start).is_pending());
18951 assert!(futures::poll!(&mut first_start).is_pending());
18952 let first_turn = crate::scheduler::do_a_turn_blocking(
18953 state.sched.clone(),
18954 state.global_time.clone(),
18955 &Err(crate::scheduler::SkipTurn),
18956 )
18957 .await
18958 .expect("the first child must receive its first turn");
18959 assert_eq!(first_turn.tid, child);
18960 assert_eq!(
18961 first_start.await,
18962 (None, granted(GlobalResponse::StartNewThread(None)))
18963 );
18964 first_holder.advance_to(first_holder.as_nanos() + LogicalTime::from_nanos(500));
18965 let deregistered = state
18966 .receive_rpc(
18967 Tid::from_raw(child.as_raw()),
18968 (
18969 first_holder,
18970 child_mm,
18971 GlobalRequest::DeregisterThread(ThreadDeregistration {
18972 dettid: child,
18973 detpid: child,
18974 mm: child_mm,
18975 thread_start_entered: true,
18976 vfork_origin: None,
18977 timeslice_stats: TimesliceStats::default(),
18978 syscall_count: 0,
18979 syscall_histogram: Default::default(),
18980 chaos_epochs: Vec::new(),
18981 passed_descriptors_out: false,
18982 killed_by_group_signal: false,
18983 }),
18984 ),
18985 )
18986 .await;
18987 assert_eq!(deregistered, (None, GlobalResponse::DeregisterThread(())));
18988 let parent_turn = crate::scheduler::do_a_turn_blocking(
18989 state.sched.clone(),
18990 state.global_time.clone(),
18991 &Ok(first_turn),
18992 )
18993 .await
18994 .expect("the parent must continue after the clone");
18995 assert_eq!(parent_turn.tid, parent);
18996 assert_eq!(
18997 first_registration.await,
18998 (None, granted(GlobalResponse::CreateChildThread(None)))
18999 );
19000
19001 parent_clock.advance_to(epoch + LogicalTime::from_nanos(600_000));
19003 let second_holder = parent_clock.clone_for_child();
19004 let mut registration = Box::pin(state.receive_rpc(
19005 Tid::from_raw(parent.as_raw()),
19006 (
19007 parent_clock,
19008 parent_mm,
19009 fork_registration(child, parent_pid),
19010 ),
19011 ));
19012 assert!(futures::poll!(&mut registration).is_pending());
19013 let before_child = state.global_time.lock().unwrap().as_nanos();
19014 let mut startup = Box::pin(state.receive_rpc(
19015 Tid::from_raw(child.as_raw()),
19016 (
19017 second_holder.clone(),
19018 child_mm,
19019 GlobalRequest::StartNewThread(child, child, None, None),
19020 ),
19021 ));
19022 assert!(futures::poll!(&mut startup).is_pending());
19023 assert!(futures::poll!(&mut startup).is_pending());
19024 assert_eq!(
19025 state.global_time.lock().unwrap().threads_time(child),
19026 second_holder.as_nanos()
19027 );
19028 assert_eq!(state.global_time.lock().unwrap().as_nanos(), before_child);
19029 let second_turn = crate::scheduler::do_a_turn_blocking(
19030 state.sched.clone(),
19031 state.global_time.clone(),
19032 &Ok(parent_turn),
19033 )
19034 .await
19035 .expect("the new child must receive its first turn");
19036 assert_eq!(second_turn.tid, child);
19037 assert_eq!(
19038 startup.await,
19039 (None, granted(GlobalResponse::StartNewThread(None)))
19040 );
19041 }
19042
19043 #[tokio::test]
19047 async fn a_vfork_child_on_a_reused_id_starts_its_own_clock() {
19048 let (state, parent, parent_pid, child, parent_clock) = reused_id_state(true);
19049 let mm = MmId::initial(parent_pid);
19050 let child_clock = parent_clock.clone_for_child();
19051 let mut resources = Resources::new(parent);
19054 resources.insert(
19055 ResourceID::BlockingVfork(ExternalOpId::new(parent, 1)),
19056 Permission::RW,
19057 );
19058 let mut blocking = Box::pin(state.receive_rpc(
19059 Tid::from_raw(parent.as_raw()),
19060 (
19061 parent_clock,
19062 mm,
19063 GlobalRequest::RequestResources(resources, parent_pid),
19064 ),
19065 ));
19066 assert!(futures::poll!(&mut blocking).is_pending());
19067 let background = crate::scheduler::do_a_turn_blocking(
19068 state.sched.clone(),
19069 state.global_time.clone(),
19070 &Err(crate::scheduler::SkipTurn),
19071 )
19072 .await;
19073 assert!(background.is_err());
19074 assert_eq!(
19075 blocking.await,
19076 (
19077 None,
19078 granted(GlobalResponse::RequestResources(ResumeStatus::Normal))
19079 )
19080 );
19081 let before_child = state.global_time.lock().unwrap().as_nanos();
19082
19083 let created = state
19084 .receive_rpc(
19085 Tid::from_raw(child.as_raw()),
19086 (
19087 child_clock.clone(),
19088 mm,
19089 GlobalRequest::CreateVforkChildThread(
19090 parent,
19091 parent_pid,
19092 child,
19093 0,
19094 CloneFlags::CLONE_VFORK | CloneFlags::CLONE_VM,
19095 libc::SIGCHLD,
19096 Some(DEFAULT_PRIORITY - 1),
19097 ),
19098 ),
19099 )
19100 .await;
19101 assert_eq!(created, (None, GlobalResponse::CreateChildThread(None)));
19102 assert_eq!(
19103 state.global_time.lock().unwrap().threads_time(child),
19104 child_clock.as_nanos()
19105 );
19106 assert_eq!(state.global_time.lock().unwrap().as_nanos(), before_child);
19107 }
19108
19109 #[tokio::test]
19110 async fn first_nonstartup_rpc_counts_only_new_child_work() {
19111 let config = Config {
19112 sequentialize_threads: false,
19113 ..Config::default()
19114 };
19115 let state = GlobalState::initialize(&config, false);
19116 let parent = DetTid::from_raw(17);
19117 let child = DetTid::from_raw(18);
19118 let mut parent_clock = DetTime::new(&config);
19119 let epoch = parent_clock.as_nanos();
19120 parent_clock.advance_to(epoch + LogicalTime::from_nanos(101));
19121 let mut child_clock = parent_clock.clone_for_child();
19122 let _ = state
19123 .receive_rpc(
19124 Tid::from_raw(parent.as_raw()),
19125 (
19126 parent_clock,
19127 MmId::initial(parent),
19128 GlobalRequest::GlobalTimeLowerBound,
19129 ),
19130 )
19131 .await;
19132 child_clock.advance_to(child_clock.as_nanos() + LogicalTime::from_nanos(1));
19133 let observed = state
19134 .receive_rpc(
19135 Tid::from_raw(child.as_raw()),
19136 (
19137 child_clock,
19138 MmId::initial(child),
19139 GlobalRequest::GlobalTimeLowerBound,
19140 ),
19141 )
19142 .await;
19143 assert_eq!(
19144 observed,
19145 (
19146 None,
19147 GlobalResponse::GlobalTimeLowerBound(epoch + LogicalTime::from_nanos(102))
19148 )
19149 );
19150 }
19151
19152 #[tokio::test]
19153 async fn exec_reconnect_retains_inherited_work_accounting_across_local_reload() {
19154 let (config, state, leader, detpid) = cancellation_test_state();
19155 let ancestor = DetTid::from_raw(leader.as_raw() - 1);
19156 let worker = DetTid::from_raw(leader.as_raw() + 1);
19157 let old_mm = MmId::initial(detpid);
19158 install_test_registration(&state, leader, Ivar::new());
19159 state
19160 .sched
19161 .lock()
19162 .unwrap()
19163 .thread_tree
19164 .add_child(leader, worker, false);
19165 install_test_registration(&state, worker, Ivar::new());
19166
19167 let mut ancestor_clock = DetTime::new(&config);
19168 let epoch = ancestor_clock.as_nanos();
19169 ancestor_clock.advance_to(epoch + LogicalTime::from_nanos(1_000));
19170 let mut leader_clock = ancestor_clock.clone_for_child();
19171 leader_clock.advance_to(epoch + LogicalTime::from_nanos(1_100));
19172 let mut worker_clock = leader_clock.clone_for_child();
19173 worker_clock.advance_to(epoch + LogicalTime::from_nanos(1_350));
19174 for (tid, clock) in [
19175 (ancestor, ancestor_clock),
19176 (leader, leader_clock),
19177 (worker, worker_clock.clone()),
19178 ] {
19179 let _ = state
19180 .receive_rpc(
19181 Tid::from_raw(tid.as_raw()),
19182 (clock, old_mm, GlobalRequest::GlobalTimeLowerBound),
19183 )
19184 .await;
19185 }
19186 let total = state.global_time.lock().unwrap().as_nanos();
19187 assert_eq!(total, epoch + LogicalTime::from_nanos(1_350));
19188 let _ = state
19192 .receive_rpc(
19193 Tid::from_raw(worker.as_raw()),
19194 (
19195 worker_clock.clone(),
19196 old_mm,
19197 GlobalRequest::PrepareExec(detpid, old_mm, Default::default()),
19198 ),
19199 )
19200 .await;
19201 let cancelled = state
19202 .receive_rpc(
19203 Tid::from_raw(worker.as_raw()),
19204 (
19205 worker_clock.clone(),
19206 old_mm,
19207 GlobalRequest::CancelExec(detpid),
19208 ),
19209 )
19210 .await;
19211 assert_eq!(cancelled, (None, GlobalResponse::CancelExec(())));
19212 assert!(state.pending_exec_states.lock().unwrap().is_empty());
19213 assert_eq!(state.global_time.lock().unwrap().as_nanos(), total);
19214 assert_eq!(
19215 state.global_time.lock().unwrap().threads_time(worker),
19216 worker_clock.as_nanos()
19217 );
19218
19219 let prepared = state
19220 .receive_rpc(
19221 Tid::from_raw(worker.as_raw()),
19222 (
19223 worker_clock.clone(),
19224 old_mm,
19225 GlobalRequest::PrepareExec(detpid, old_mm, Default::default()),
19226 ),
19227 )
19228 .await;
19229 assert_eq!(prepared, (None, GlobalResponse::PrepareExec(())));
19230
19231 let mut fresh = DetTime::new(&config);
19232 let recreated = state
19233 .receive_rpc(
19234 Tid::from_raw(leader.as_raw()),
19235 (
19236 fresh.clone(),
19237 MmId::initial(leader),
19238 GlobalRequest::CreateChildThread(
19239 leader,
19240 detpid,
19241 0,
19242 None,
19243 libc::SIGCHLD,
19244 None,
19245 Some(DEFAULT_PRIORITY),
19246 ),
19247 ),
19248 )
19249 .await;
19250 assert_eq!(
19251 recreated,
19252 (
19253 Some(worker_clock.as_nanos()),
19254 GlobalResponse::CreateChildThread(Some(old_mm.for_exec(detpid)))
19255 )
19256 );
19257 let stale = state
19260 .receive_rpc(
19261 Tid::from_raw(leader.as_raw()),
19262 (
19263 worker_clock.clone(),
19264 old_mm,
19265 GlobalRequest::RequestResources(Resources::new(leader), detpid),
19266 ),
19267 )
19268 .await;
19269 assert_eq!(stale, (None, GlobalResponse::ThreadExited));
19270 assert_eq!(state.global_time.lock().unwrap().as_nanos(), total);
19271
19272 fresh.advance_to(recreated.0.unwrap());
19273 assert_eq!(fresh.inherited_nanos(), LogicalTime::ZERO);
19274 let mut startup = Box::pin(state.receive_rpc(
19275 Tid::from_raw(leader.as_raw()),
19276 (
19277 fresh.clone(),
19278 old_mm.for_exec(detpid),
19279 GlobalRequest::StartNewThread(leader, detpid, None, None),
19280 ),
19281 ));
19282 assert!(futures::poll!(&mut startup).is_pending());
19283 assert!(futures::poll!(&mut startup).is_pending());
19284 let first = crate::scheduler::do_a_turn_blocking(
19285 state.sched.clone(),
19286 state.global_time.clone(),
19287 &Err(crate::scheduler::SkipTurn),
19288 )
19289 .await
19290 .expect("replacement leader must run");
19291 assert_eq!(first.tid, leader);
19292 assert_eq!(
19293 startup.await,
19294 (None, granted(GlobalResponse::StartNewThread(None)))
19295 );
19296 assert_eq!(state.global_time.lock().unwrap().as_nanos(), total);
19297
19298 fresh.advance_to(fresh.as_nanos() + LogicalTime::from_nanos(1));
19299 let observed = state
19300 .receive_rpc(
19301 Tid::from_raw(leader.as_raw()),
19302 (
19303 fresh.clone(),
19304 old_mm.for_exec(detpid),
19305 GlobalRequest::GlobalTimeLowerBound,
19306 ),
19307 )
19308 .await;
19309 assert_eq!(
19310 observed,
19311 (
19312 None,
19313 GlobalResponse::GlobalTimeLowerBound(total + LogicalTime::from_nanos(1))
19314 )
19315 );
19316 let global = state.global_time.lock().unwrap();
19317 assert_eq!(global.threads_time(leader), fresh.as_nanos());
19318 assert!(!global.contains_thread(worker));
19319 }
19320
19321 #[test]
19322 fn live_registration_without_next_turn_is_not_terminal() {
19323 let (_, state, dettid, detpid) = cancellation_test_state();
19324 install_test_registration(&state, dettid, Ivar::new());
19325 state.sched.lock().unwrap().next_turns.remove(&dettid);
19326
19327 assert!(
19328 !state
19329 .sched
19330 .lock()
19331 .unwrap()
19332 .thread_is_logically_killed(dettid),
19333 "transient next-turn absence must not imply logical death"
19334 );
19335
19336 state
19337 .sched
19338 .lock()
19339 .unwrap()
19340 .logically_kill_thread(&dettid, &detpid, MmId::initial(detpid));
19341 assert!(
19342 state
19343 .sched
19344 .lock()
19345 .unwrap()
19346 .thread_is_logically_killed(dettid),
19347 "explicit logical death must install a permanent TID tombstone"
19348 );
19349 }
19350
19351 #[tokio::test]
19352 async fn exec_reconnect_retires_siblings_and_reuses_live_scheduler_and_clock_state() {
19353 let (config, state, dettid, detpid) = cancellation_test_state();
19354 let old_mm = MmId::initial(detpid).for_exec(detpid);
19355 install_test_registration(&state, dettid, Ivar::new());
19356 let sibling = DetTid::from_raw(dettid.as_raw() + 1);
19357 let sibling_request = Ivar::new();
19358 state
19359 .sched
19360 .lock()
19361 .unwrap()
19362 .thread_tree
19363 .add_child(dettid, sibling, false);
19364 install_test_registration(&state, sibling, sibling_request.clone());
19365 {
19366 let mut scheduler = state.sched.lock().unwrap();
19367 scheduler.next_turns.get_mut(&dettid).unwrap().resp =
19368 Ivar::full(SchedResponse::Go(None));
19369 scheduler
19370 .next_turns
19371 .get_mut(&sibling)
19372 .unwrap()
19373 .child_tid_addr = 0x1234;
19374 }
19375 let mut existing_time = DetTime::new(&config);
19376 existing_time.add_syscall();
19377 existing_time.add_syscall();
19378 state.global_time.lock().unwrap().update_global_time(
19379 dettid,
19380 existing_time.as_nanos(),
19381 LogicalTime::ZERO,
19382 );
19383 let (global_before, thread_before) = {
19384 let global_time = state.global_time.lock().unwrap();
19385 (global_time.as_nanos(), global_time.threads_time(dettid))
19386 };
19387 let fresh_local_time = DetTime::new(&config);
19388 let physical_pid = std::process::id() as i32;
19389 let physical_tid = unsafe { libc::syscall(libc::SYS_gettid) as i32 };
19390 let physical_ids = Some((physical_pid, physical_tid));
19391 state.pending_exec_states.lock().unwrap().insert(
19392 detpid,
19393 PendingExecState {
19394 caller: dettid,
19395 process: detpid,
19396 mm: old_mm,
19397 fd_blocking: Default::default(),
19398 },
19399 );
19400 state
19401 .sched
19402 .lock()
19403 .unwrap()
19404 .install_test_exec_incarnation(dettid, old_mm);
19405 let in_flight_exec_response = state
19406 .receive_rpc(
19407 reverie::Tid::from_raw(dettid.as_raw()),
19408 (
19409 existing_time.clone(),
19410 old_mm.for_exec(detpid),
19411 GlobalRequest::ReportUnsupportedSyscall("exec-in-flight".to_owned()),
19412 ),
19413 )
19414 .await;
19415 assert_eq!(
19416 in_flight_exec_response.1,
19417 GlobalResponse::ReportUnsupportedSyscall(())
19418 );
19419
19420 let create_response = state
19421 .receive_rpc(
19422 reverie::Tid::from_raw(dettid.as_raw()),
19423 (
19424 fresh_local_time.clone(),
19425 MmId::initial(dettid),
19426 GlobalRequest::CreateChildThread(
19427 dettid,
19428 detpid,
19429 0,
19430 None,
19431 libc::SIGCHLD,
19432 physical_ids,
19433 Some(DEFAULT_PRIORITY),
19434 ),
19435 ),
19436 )
19437 .await;
19438 assert_eq!(
19439 create_response,
19440 (
19441 Some(thread_before),
19442 GlobalResponse::CreateChildThread(Some(old_mm.for_exec(detpid)))
19443 )
19444 );
19445 assert_eq!(
19446 state.sched.lock().unwrap().physical_thread_identity(dettid),
19447 Some((old_mm.for_exec(detpid), physical_pid, physical_tid)),
19448 "post-exec host identity must be installed by CreateChildThread before admission"
19449 );
19450
19451 let start_response = state
19452 .receive_rpc(
19453 reverie::Tid::from_raw(dettid.as_raw()),
19454 (
19455 fresh_local_time,
19456 old_mm.for_exec(detpid),
19457 GlobalRequest::StartNewThread(dettid, detpid, physical_ids, None),
19458 ),
19459 )
19460 .await;
19461 assert_eq!(
19462 start_response,
19463 (Some(thread_before), GlobalResponse::StartNewThread(None))
19464 );
19465
19466 let scheduler = state.sched.lock().unwrap();
19467 assert!(!scheduler.thread_is_logically_killed(dettid));
19468 assert!(scheduler.thread_is_logically_killed(sibling));
19469 assert_eq!(scheduler.next_turns.len(), 1);
19470 assert!(matches!(sibling_request.try_read(), Some(Err(_))));
19471 assert!(
19472 scheduler.child_tid_was_cleared(FutexID::private(old_mm, 0x1234), sibling.as_raw())
19473 );
19474 drop(scheduler);
19475 let global_time = state.global_time.lock().unwrap();
19476 assert_eq!(global_time.as_nanos(), global_before);
19477 assert_eq!(global_time.threads_time(dettid), thread_before);
19478 }
19479
19480 fn park_exec_robust_waiter(state: &GlobalState, futex: FutexID) -> DetTid {
19483 park_exec_robust_waiter_as(state, futex, 40)
19484 }
19485
19486 fn park_exec_robust_waiter_as(state: &GlobalState, futex: FutexID, raw: i32) -> DetTid {
19488 let waiter = DetTid::from_raw(raw);
19489 state
19490 .sched
19491 .lock()
19492 .unwrap()
19493 .thread_tree
19494 .add_child(waiter, waiter, true);
19495 install_test_registration(state, waiter, Ivar::new());
19496 let mut scheduler = state.sched.lock().unwrap();
19497 scheduler.sleep_futex_waiter(&waiter, futex, None, u32::MAX, None);
19498 assert!(scheduler.run_queue.remove_tid(waiter));
19499 waiter
19500 }
19501
19502 #[tokio::test]
19508 async fn exec_reconnect_wakes_the_robust_waiters_its_exec_marked() {
19509 let (config, state, dettid, detpid) = cancellation_test_state();
19510 let old_mm = MmId::initial(detpid).for_exec(detpid);
19511 install_test_registration(&state, dettid, Ivar::new());
19512 let mut existing_time = DetTime::new(&config);
19513 existing_time.add_syscall();
19514 state.global_time.lock().unwrap().update_global_time(
19515 dettid,
19516 existing_time.as_nanos(),
19517 LogicalTime::ZERO,
19518 );
19519 let futex = FutexID::private(old_mm, 0x4000);
19520 let waiter = park_exec_robust_waiter(&state, futex);
19521 state.pending_exec_states.lock().unwrap().insert(
19522 detpid,
19523 PendingExecState {
19524 caller: dettid,
19525 process: detpid,
19526 mm: old_mm,
19527 fd_blocking: ExecFdState {
19528 robust_wakes: vec![(dettid, futex)],
19529 ..Default::default()
19530 },
19531 },
19532 );
19533 state
19534 .sched
19535 .lock()
19536 .unwrap()
19537 .install_test_exec_incarnation(dettid, old_mm);
19538 assert!(!state.sched.lock().unwrap().admission_pending(waiter));
19539
19540 let create_response = state
19541 .receive_rpc(
19542 reverie::Tid::from_raw(dettid.as_raw()),
19543 (
19544 DetTime::new(&config),
19545 MmId::initial(dettid),
19546 GlobalRequest::CreateChildThread(
19547 dettid,
19548 detpid,
19549 0,
19550 None,
19551 libc::SIGCHLD,
19552 None,
19553 Some(DEFAULT_PRIORITY),
19554 ),
19555 ),
19556 )
19557 .await;
19558 assert_eq!(
19559 create_response.1,
19560 GlobalResponse::CreateChildThread(Some(old_mm.for_exec(detpid)))
19561 );
19562 assert!(state.sched.lock().unwrap().admission_pending(waiter));
19563 assert!(
19564 state
19565 .post_exec_fd_blocking
19566 .lock()
19567 .unwrap()
19568 .get(&dettid)
19569 .is_none_or(|carried| carried.robust_wakes.is_empty()),
19570 "the wakes are delivered once, at the reconnect"
19571 );
19572 }
19573
19574 #[tokio::test]
19580 async fn a_failed_exec_drops_its_robust_wakes() {
19581 let (config, state, leader, detpid) = cancellation_test_state();
19582 install_test_registration(&state, leader, Ivar::new());
19583 let futex = FutexID::private(MmId::initial(detpid), 0x4000);
19584 let waiter = park_exec_robust_waiter(&state, futex);
19585 let clock = DetTime::new(&config);
19586 let prepared = state
19587 .receive_rpc(
19588 reverie::Tid::from_raw(leader.as_raw()),
19589 (
19590 clock.clone(),
19591 MmId::initial(leader),
19592 GlobalRequest::PrepareExec(
19593 detpid,
19594 MmId::initial(detpid),
19595 Box::new(ExecFdState {
19596 robust_wakes: vec![(leader, futex)],
19597 ..Default::default()
19598 }),
19599 ),
19600 ),
19601 )
19602 .await;
19603 assert_eq!(prepared.1, GlobalResponse::PrepareExec(()));
19604 let cancelled = state
19605 .receive_rpc(
19606 reverie::Tid::from_raw(leader.as_raw()),
19607 (
19608 clock,
19609 MmId::initial(leader),
19610 GlobalRequest::CancelExec(detpid),
19611 ),
19612 )
19613 .await;
19614 assert_eq!(cancelled.1, GlobalResponse::CancelExec(()));
19615 assert!(state.pending_exec_states.lock().unwrap().is_empty());
19616 assert!(!state.sched.lock().unwrap().admission_pending(waiter));
19617 }
19618
19619 #[tokio::test]
19629 async fn a_terminal_exec_wakes_its_robust_waiters() {
19630 let (config, state, leader, detpid) = cancellation_test_state();
19631 install_test_registration(&state, leader, Ivar::new());
19632 let (tid_tx, tid_rx) = std::sync::mpsc::channel();
19633 let (exit_tx, exit_rx) = std::sync::mpsc::channel::<()>();
19634 let host_thread = std::thread::spawn(move || {
19635 tid_tx
19636 .send(unsafe { libc::syscall(libc::SYS_gettid) as i32 })
19637 .unwrap();
19638 let _ = exit_rx.recv();
19639 });
19640 state
19641 .sched
19642 .lock()
19643 .unwrap()
19644 .register_physical_thread(
19645 leader,
19646 MmId::initial(detpid),
19647 std::process::id() as i32,
19648 tid_rx.recv().unwrap(),
19649 )
19650 .unwrap();
19651 let futex = FutexID::private(MmId::initial(detpid), 0x4000);
19652 let waiter = park_exec_robust_waiter(&state, futex);
19653 let second = park_exec_robust_waiter_as(&state, futex, 41);
19657 let prepared = state
19658 .receive_rpc(
19659 reverie::Tid::from_raw(leader.as_raw()),
19660 (
19661 DetTime::new(&config),
19662 MmId::initial(leader),
19663 GlobalRequest::PrepareExec(
19664 detpid,
19665 MmId::initial(detpid),
19666 Box::new(ExecFdState {
19667 robust_wakes: vec![(leader, futex)],
19668 ..Default::default()
19669 }),
19670 ),
19671 ),
19672 )
19673 .await;
19674 assert_eq!(prepared.1, GlobalResponse::PrepareExec(()));
19675 assert!(!state.sched.lock().unwrap().admission_pending(waiter));
19676
19677 state
19678 .recv_deregister_thread(
19679 reverie::Tid::from_raw(leader.as_raw()),
19680 ThreadDeregistration {
19681 thread_start_entered: true,
19682 vfork_origin: None,
19683 dettid: leader,
19684 detpid,
19685 mm: MmId::initial(detpid),
19686 timeslice_stats: TimesliceStats::default(),
19687 syscall_count: 0,
19688 syscall_histogram: Default::default(),
19689 chaos_epochs: Vec::new(),
19690 killed_by_group_signal: false,
19691 passed_descriptors_out: false,
19692 },
19693 )
19694 .await;
19695 assert!(state.pending_exec_states.lock().unwrap().is_empty());
19696 {
19697 let scheduler = state.sched.lock().unwrap();
19698 assert!(scheduler.admission_pending(waiter));
19699 assert!(!scheduler.admission_pending(second));
19700 assert!(scheduler.holds_turns_until_gone(leader));
19701 }
19702 exit_tx.send(()).unwrap();
19703 host_thread.join().unwrap();
19704 }
19705
19706 #[tokio::test]
19707 async fn nonleader_exec_rebinds_caller_to_leader_and_preserves_its_clock() {
19708 let (config, state, leader, detpid) = cancellation_test_state();
19709 let worker = DetTid::from_raw(leader.as_raw() + 1);
19710 let sibling = DetTid::from_raw(leader.as_raw() + 2);
19711 let old_mm = MmId::initial(detpid).for_exec(detpid);
19712 let leader_request = Ivar::new();
19713 let worker_request = Ivar::new();
19714 let sibling_request = Ivar::new();
19715 install_test_registration(&state, leader, leader_request.clone());
19716 {
19717 let mut scheduler = state.sched.lock().unwrap();
19718 scheduler.thread_tree.add_child(leader, worker, false);
19719 scheduler.thread_tree.add_child(leader, sibling, false);
19720 }
19721 install_test_registration(&state, worker, worker_request.clone());
19722 install_test_registration(&state, sibling, sibling_request.clone());
19723 {
19724 let mut scheduler = state.sched.lock().unwrap();
19725 scheduler
19726 .next_turns
19727 .get_mut(&sibling)
19728 .unwrap()
19729 .child_tid_addr = 0x5678;
19730 scheduler
19731 .timeslices
19732 .insert(leader, Some(LogicalTime::from_nanos(99)));
19733 scheduler.install_test_vfork_barrier(leader, sibling);
19734 }
19735
19736 let mut leader_clock = DetTime::new(&config);
19737 leader_clock.add_syscall();
19738 let mut worker_clock = DetTime::new(&config);
19739 worker_clock.add_syscall();
19740 worker_clock.add_syscall();
19741 worker_clock.add_syscall();
19742 {
19743 let mut global_time = state.global_time.lock().unwrap();
19744 global_time.update_global_time(leader, leader_clock.as_nanos(), LogicalTime::ZERO);
19745 global_time.update_global_time(worker, worker_clock.as_nanos(), LogicalTime::ZERO);
19746 }
19747 let total_before = state.global_time.lock().unwrap().as_nanos();
19748 let fd_blocking = ExecFdState {
19749 blocking: [42].into_iter().collect(),
19750 inherited_reads: std::collections::BTreeMap::from([(
19751 43,
19752 crate::fd::InheritedExternalRead {
19753 origin: crate::types::OpenFileId::new(leader, 0),
19754 dev: 1,
19755 inode: 2,
19756 rdev: 3,
19757 file_type: libc::S_IFCHR,
19758 access_mode: libc::O_RDONLY,
19759 resource: Some(ResourceID::Device(crate::resources::Device::ContainerStdin)),
19760 },
19761 )]),
19762 initialize_inherited_stdio: false,
19763 initial_input: None,
19764 robust_wakes: Vec::new(),
19765 open_files: None,
19766 maps: None,
19767 procfs_records: None,
19768 channels: Default::default(),
19769 open_file_namespace: None,
19770 random_device_sequence: 0,
19771 exe: None,
19772 };
19773 state.pending_exec_states.lock().unwrap().insert(
19774 detpid,
19775 PendingExecState {
19776 caller: worker,
19777 process: detpid,
19778 mm: old_mm,
19779 fd_blocking: fd_blocking.clone(),
19780 },
19781 );
19782 let fresh_local_time = DetTime::new(&config);
19783
19784 let create_response = state
19785 .receive_rpc(
19786 reverie::Tid::from_raw(leader.as_raw()),
19787 (
19788 fresh_local_time.clone(),
19789 MmId::initial(leader),
19790 GlobalRequest::CreateChildThread(
19791 leader,
19792 detpid,
19793 0,
19794 None,
19795 libc::SIGCHLD,
19796 None,
19797 Some(DEFAULT_PRIORITY),
19798 ),
19799 ),
19800 )
19801 .await;
19802 assert_eq!(
19803 create_response,
19804 (
19805 Some(worker_clock.as_nanos()),
19806 GlobalResponse::CreateChildThread(Some(old_mm.for_exec(detpid)))
19807 )
19808 );
19809 assert!(state.pending_exec_states.lock().unwrap().is_empty());
19810 assert_eq!(
19811 state.post_exec_fd_blocking.lock().unwrap().get(&leader),
19812 Some(&fd_blocking)
19813 );
19814
19815 let late_old_request = state
19816 .receive_rpc(
19817 reverie::Tid::from_raw(leader.as_raw()),
19818 (
19819 leader_clock.clone(),
19820 old_mm,
19821 GlobalRequest::RequestResources(Resources::new(leader), detpid),
19822 ),
19823 )
19824 .await;
19825 assert_eq!(late_old_request, (None, GlobalResponse::ThreadExited));
19826 let admitted_before_fence = state
19827 .recv_request_resources(
19828 reverie::Tid::from_raw(leader.as_raw()),
19829 detpid,
19830 Resources::new(leader),
19831 Some(old_mm),
19832 )
19833 .await;
19834 assert_eq!(
19835 admitted_before_fence,
19836 (SchedulerRpcResult::ThreadExited, None)
19837 );
19838 let late_old_deregister = state
19839 .receive_rpc(
19840 reverie::Tid::from_raw(leader.as_raw()),
19841 (
19842 leader_clock.clone(),
19843 old_mm,
19844 GlobalRequest::DeregisterThread(ThreadDeregistration {
19845 thread_start_entered: true,
19846 vfork_origin: None,
19847 dettid: leader,
19848 detpid,
19849 mm: old_mm,
19850 timeslice_stats: TimesliceStats::default(),
19851 syscall_count: 0,
19852 syscall_histogram: Default::default(),
19853 chaos_epochs: Vec::new(),
19854 killed_by_group_signal: false,
19855 passed_descriptors_out: false,
19856 }),
19857 ),
19858 )
19859 .await;
19860 assert_eq!(
19861 late_old_deregister,
19862 (None, GlobalResponse::DeregisterThread(()))
19863 );
19864 let duplicate_create = state
19865 .receive_rpc(
19866 reverie::Tid::from_raw(leader.as_raw()),
19867 (
19868 fresh_local_time.clone(),
19869 MmId::initial(leader),
19870 GlobalRequest::CreateChildThread(
19871 leader,
19872 detpid,
19873 0,
19874 None,
19875 libc::SIGCHLD,
19876 None,
19877 Some(DEFAULT_PRIORITY),
19878 ),
19879 ),
19880 )
19881 .await;
19882 assert_eq!(duplicate_create, (None, GlobalResponse::ThreadExited));
19883
19884 {
19885 let scheduler = state.sched.lock().unwrap();
19886 assert!(!scheduler.thread_is_logically_killed(leader));
19887 assert!(scheduler.thread_is_logically_killed(worker));
19888 assert!(scheduler.thread_is_logically_killed(sibling));
19889 assert_eq!(scheduler.next_turns.len(), 1);
19890 assert!(scheduler.next_turns.contains_key(&leader));
19891 assert!(!scheduler.timeslices.contains_key(&leader));
19892 assert!(!scheduler.vfork_barrier_mentions(leader));
19893 assert!(!scheduler.vfork_barrier_mentions(sibling));
19894 assert!(matches!(leader_request.try_read(), Some(Err(_))));
19895 assert!(matches!(worker_request.try_read(), Some(Err(_))));
19896 assert!(matches!(sibling_request.try_read(), Some(Err(_))));
19897 assert!(
19898 scheduler
19899 .child_tid_was_cleared(FutexID::private(old_mm, 0x5678), sibling.as_raw(),)
19900 );
19901 }
19902
19903 let turn_sched = state.sched.clone();
19908 let turn_time = state.global_time.clone();
19909 let turn = tokio::spawn(async move {
19910 let last: Result<Resources, crate::scheduler::SkipTurn> =
19911 Err(crate::scheduler::SkipTurn);
19912 crate::scheduler::do_a_turn_blocking(turn_sched, turn_time, &last).await
19913 });
19914 let start_response = state
19915 .receive_rpc(
19916 reverie::Tid::from_raw(leader.as_raw()),
19917 (
19918 fresh_local_time,
19919 old_mm.for_exec(detpid),
19920 GlobalRequest::StartNewThread(leader, detpid, None, None),
19921 ),
19922 )
19923 .await;
19924 assert!(
19925 turn.await
19926 .expect("replacement scheduler turn panicked")
19927 .is_ok(),
19928 "replacement leader did not survive the first step2 drain"
19929 );
19930 assert_eq!(
19931 start_response,
19932 (
19933 Some(worker_clock.as_nanos()),
19934 granted(GlobalResponse::StartNewThread(None))
19935 )
19936 );
19937 {
19938 let scheduler = state.sched.lock().unwrap();
19939 assert_eq!(
19940 scheduler
19941 .run_queue
19942 .tids()
19943 .filter(|dettid| **dettid == leader)
19944 .count(),
19945 1
19946 );
19947 assert!(!scheduler.run_queue.contains_tid(worker));
19948 assert!(!scheduler.run_queue.contains_tid(sibling));
19949 }
19950 {
19951 let global_time = state.global_time.lock().unwrap();
19952 assert_eq!(global_time.as_nanos(), total_before);
19953 assert_eq!(global_time.threads_time(leader), worker_clock.as_nanos());
19954 assert!(!global_time.contains_thread(worker));
19955 }
19956
19957 let mark_response = state
19958 .receive_rpc(
19959 reverie::Tid::from_raw(leader.as_raw()),
19960 (
19961 worker_clock,
19962 old_mm.for_exec(detpid),
19963 GlobalRequest::MarkPastFirstExecve(detpid, None, None, None),
19964 ),
19965 )
19966 .await;
19967 assert_eq!(
19968 mark_response.1,
19969 GlobalResponse::MarkPastFirstExecve(Box::new(fd_blocking))
19970 );
19971 assert!(state.post_exec_fd_blocking.lock().unwrap().is_empty());
19972 }
19973
19974 async fn start_external_image(
19975 tool: &Detcore,
19976 guest: &mut ExternalRegistrationGuest<'_>,
19977 expected_grants: &[ResourceID],
19978 ) {
19979 use reverie::Tool;
19980
19981 let state = guest.global;
19982 let tid = guest.thread.dettid;
19983 tokio::time::timeout(Duration::from_secs(2), async {
19984 let mut start = std::pin::pin!(tool.handle_thread_start(guest));
19985 assert!(futures::poll!(start.as_mut()).is_pending());
19986 let grants = async {
19990 let mut last = Err(crate::scheduler::SkipTurn);
19991 for expected in expected_grants {
19992 let granted = crate::scheduler::do_a_turn_blocking(
19993 state.sched.clone(),
19994 state.global_time.clone(),
19995 &last,
19996 )
19997 .await
19998 .expect("image admission must receive its scheduler grant");
19999 assert_eq!(granted.tid, tid);
20000 assert!(granted.resources.contains_key(expected));
20001 last = Ok(granted);
20002 }
20003 };
20004 let (started, ()) = tokio::join!(start, grants);
20005 started.unwrap();
20006 })
20007 .await
20008 .expect("image admission must finish without guest instructions");
20009 }
20010
20011 struct BoundaryAdmissionGuest<'a> {
20015 inner: ExternalRegistrationGuest<'a>,
20016 clock_reads: usize,
20017 }
20018
20019 impl<'a> std::ops::Deref for BoundaryAdmissionGuest<'a> {
20020 type Target = ExternalRegistrationGuest<'a>;
20021 fn deref(&self) -> &Self::Target {
20022 &self.inner
20023 }
20024 }
20025 impl std::ops::DerefMut for BoundaryAdmissionGuest<'_> {
20026 fn deref_mut(&mut self) -> &mut Self::Target {
20027 &mut self.inner
20028 }
20029 }
20030
20031 #[reverie::tool]
20032 impl GlobalRPC<GlobalState> for BoundaryAdmissionGuest<'_> {
20033 async fn send_rpc(
20034 &self,
20035 message: <GlobalState as GlobalTool>::Request,
20036 ) -> <GlobalState as GlobalTool>::Response {
20037 self.inner.send_rpc(message).await
20038 }
20039 fn config(&self) -> &Config {
20040 self.inner.config
20041 }
20042 }
20043
20044 #[reverie::tool]
20045 impl Guest<Detcore> for BoundaryAdmissionGuest<'_> {
20046 type Memory = reverie::syscalls::LocalMemory;
20047 type Stack = ExternalRegistrationStack;
20048 fn tid(&self) -> reverie::Pid {
20049 self.inner.tid()
20050 }
20051 fn pid(&self) -> reverie::Pid {
20052 self.inner.pid()
20053 }
20054 fn ppid(&self) -> Option<reverie::Pid> {
20055 self.inner.ppid()
20056 }
20057 fn memory(&self) -> Self::Memory {
20058 self.inner.memory()
20059 }
20060 fn thread_state_mut(&mut self) -> &mut crate::ThreadState<()> {
20061 &mut self.inner.thread
20062 }
20063 fn thread_state(&self) -> &crate::ThreadState<()> {
20064 &self.inner.thread
20065 }
20066 async fn regs(&mut self) -> libc::user_regs_struct {
20067 self.inner.regs().await
20068 }
20069 async fn stack(&mut self) -> Self::Stack {
20070 self.inner.stack().await
20071 }
20072 async fn daemonize(&mut self) {
20073 self.inner.daemonize().await
20074 }
20075 async fn inject<S: reverie::syscalls::SyscallInfo>(
20076 &mut self,
20077 syscall: S,
20078 ) -> Result<i64, reverie::syscalls::Errno> {
20079 self.inner.inject(syscall).await
20080 }
20081 async fn tail_inject<S: reverie::syscalls::SyscallInfo>(
20082 &mut self,
20083 syscall: S,
20084 ) -> reverie::Never {
20085 self.inner.tail_inject(syscall).await
20086 }
20087 fn set_timer(&mut self, schedule: reverie::TimerSchedule) -> Result<(), reverie::Error> {
20088 self.inner.set_timer(schedule)
20089 }
20090 fn set_timer_precise(
20091 &mut self,
20092 schedule: reverie::TimerSchedule,
20093 ) -> Result<(), reverie::Error> {
20094 self.inner.set_timer_precise(schedule)
20095 }
20096 fn read_clock(&mut self) -> Result<u64, reverie::Error> {
20097 self.clock_reads += 1;
20098 Ok(0)
20099 }
20100 }
20101
20102 #[tokio::test]
20103 async fn boundary_actual_root_admission_keeps_registration_before_start_and_one_reset() {
20104 use detcore_model::schedule::boundary::Boundary;
20105 use detcore_model::schedule::boundary::Cause;
20106 use detcore_model::schedule::boundary::Entry;
20107 use detcore_model::schedule::boundary::Record;
20108 use detcore_model::schedule::boundary::Schedule;
20109 use reverie::Tool;
20110
20111 use crate::preemptions::boundary_recording::BoundaryRecording;
20112 use crate::preemptions::boundary_recording::Recording;
20113
20114 for timers in [true, false] {
20115 let mut trajectories = Vec::new();
20116 for mode in 0..3 {
20117 let temp = tempfile::tempdir().unwrap();
20118 let root = super::ROOT_DETPID;
20119 let mut expected =
20120 crate::slice_boundary::grant::tests::budget(root, MmId::initial(root), 0)
20121 .expected();
20122 expected.checkin = 3;
20123 expected.entry = Entry::Cpuid;
20124 expected.cause = Cause::SoftwareCheckin;
20125 if !timers {
20126 expected.entry = Entry::Syscall {
20127 syscall: reverie::syscalls::Sysno::sched_yield,
20128 occurrence: 1,
20129 };
20130 expected.cause = Cause::ExplicitSchedYield;
20131 }
20132 let input = temp.path().join("input");
20133 Recording::Boundaries(
20134 BoundaryRecording::new(
20135 PreemptionRecord::from_sched_events(vec![]),
20136 Schedule::new(vec![Record::TimesliceEnd(Boundary {
20137 identity: expected,
20138 count: 1,
20139 start_rip: None,
20140 end_rip: None,
20141 end_time: None,
20142 })])
20143 .unwrap(),
20144 )
20145 .unwrap(),
20146 )
20147 .write(&input)
20148 .unwrap();
20149 let config = Config {
20150 sequentialize_threads: true,
20151 no_rcb_time: true,
20152 max_timeslice: if timers {
20153 Config::default().max_timeslice
20154 } else {
20155 None
20156 },
20157 record_preemptions: mode == 1,
20158 strict_schedule_boundaries: mode != 0,
20159 record_preemptions_to: (mode == 1).then(|| temp.path().join("output")),
20160 replay_schedule_from: (mode == 2).then_some(input),
20161 ..Config::default()
20162 }
20163 .with_backend(|backend| backend.delivers_preemption_timer_events = false);
20164 let state = GlobalState::initialize(&config, false);
20165 let tool = Detcore::new(Tid::from_raw(root.as_raw()), &config);
20166 let mut guest = BoundaryAdmissionGuest {
20167 inner: ExternalRegistrationGuest {
20168 global: &state,
20169 config: &config,
20170 thread: tool.init_thread_state(Tid::from_raw(root.as_raw()), None),
20171 requests: Mutex::new(Vec::new()),
20172 post_exec: false,
20173 ppid: None,
20174 private_memory: None,
20175 },
20176 clock_reads: 0,
20177 };
20178 guest.thread.detpid = Some(root);
20179 assert!(guest.thread.slice_boundary.is_none());
20180 tokio::time::timeout(Duration::from_secs(2), async {
20181 let mut start = std::pin::pin!(tool.handle_thread_start(&mut guest));
20182 assert!(futures::poll!(start.as_mut()).is_pending());
20183 let grants = async {
20184 let first = crate::scheduler::do_a_turn_blocking(
20185 state.sched.clone(),
20186 state.global_time.clone(),
20187 &Err(crate::scheduler::SkipTurn),
20188 )
20189 .await
20190 .unwrap();
20191 assert_eq!(first.resources.len(), 1);
20192 assert!(first.resources.contains_key(&ResourceID::ParentContinue {
20193 parent: root,
20194 child: root,
20195 }));
20196 let next = state.sched.lock().unwrap().next_turns[&root].req.clone();
20199 let pending = next.get().await.unwrap();
20200 assert!(
20201 pending
20202 .resources
20203 .contains_key(&ResourceID::MemAddrSpace(root))
20204 );
20205 if mode == 2 {
20206 let mut different = expected;
20207 different.committed_rcbs = 1;
20208 let schedule = Schedule::new(vec![Record::TimesliceEnd(Boundary {
20209 identity: different,
20210 count: 1,
20211 start_rip: None,
20212 end_rip: None,
20213 end_time: None,
20214 })])
20215 .unwrap();
20216 let different =
20217 crate::slice_boundary::grant::Budget::at(&schedule, 0).unwrap();
20218 assert!(
20221 state
20222 .sched
20223 .lock()
20224 .unwrap()
20225 .boundary_budgets
20226 .queue(MmId::initial(root), different)
20227 .is_err()
20228 );
20229 }
20230 let second = crate::scheduler::do_a_turn_blocking(
20231 state.sched.clone(),
20232 state.global_time.clone(),
20233 &Ok(first),
20234 )
20235 .await
20236 .unwrap();
20237 assert_eq!(second.resources.len(), 1);
20238 assert!(
20239 second
20240 .resources
20241 .contains_key(&ResourceID::MemAddrSpace(root))
20242 );
20243 };
20244 let (started, ()) = tokio::join!(start, grants);
20245 started.unwrap();
20246 })
20247 .await
20248 .expect("the two original admission grants must finish");
20249 assert_eq!(guest.thread.stats.timeslice_count, u64::from(timers));
20250 let requests = guest.requests.lock().unwrap();
20251 let registration = requests
20252 .iter()
20253 .position(|r| matches!(r, GlobalRequest::CreateChildThread(..)))
20254 .unwrap();
20255 let start = requests
20256 .iter()
20257 .position(|r| matches!(r, GlobalRequest::StartNewThread(..)))
20258 .unwrap();
20259 assert!(registration < start);
20260 if mode != 0 {
20261 let observed =
20262 serde_json::to_value(guest.thread.slice_boundary.as_ref().unwrap())
20263 .unwrap();
20264 assert_eq!(observed["start_reset_done"], true);
20265 assert_eq!(observed["checkin"], 2);
20266 assert_eq!(observed["completed_slices"], 0);
20267 assert_eq!(observed["current"][0], "ThreadStart");
20268 assert_eq!(observed["current"][1], "Posthook");
20269 assert_eq!(observed["pending_start_budget"], serde_json::Value::Null);
20270 assert_eq!(observed["expected_budget"].is_null(), mode != 2);
20271 }
20272 let sched = state.sched.lock().unwrap();
20273 trajectories.push((
20274 sched.turn,
20275 sched.committed_time,
20276 guest.thread.thread_logical_time.as_nanos(),
20277 guest.thread.stats.timeslice_count,
20278 guest.thread.committed_clock_value,
20279 guest.thread.scheduler_grants,
20280 guest.clock_reads,
20281 ));
20282 }
20283 assert_eq!(trajectories[0], trajectories[1]);
20284 assert_eq!(trajectories[0], trajectories[2]);
20285 }
20286 }
20287
20288 #[test]
20289 fn boundary_self_registration_is_not_a_parent_child_or_ordinary_grant() {
20290 let root = super::ROOT_DETPID;
20291 let request =
20292 GlobalRequest::CreateChildThread(root, root, 0, None, libc::SIGCHLD, None, None);
20293 assert!(super::registers_own_image(root, &request));
20294 assert!(!super::registers_own_image(DetTid::from_raw(17), &request));
20295 assert!(super::registers_own_image(
20296 root,
20297 &GlobalRequest::WithForwardedCount(4, Box::new(request))
20298 ));
20299 assert!(!super::registers_own_image(
20300 root,
20301 &GlobalRequest::StartNewThread(root, root, None, None)
20302 ));
20303 assert!(!super::registers_own_image(
20304 root,
20305 &GlobalRequest::ResumeExec(root)
20306 ));
20307 assert!(!super::registers_own_image(
20308 root,
20309 &GlobalRequest::GlobalTimeLowerBound
20310 ));
20311 let vfork = GlobalRequest::CreateVforkChildThread(
20312 root,
20313 root,
20314 DetTid::from_raw(17),
20315 0,
20316 CloneFlags::CLONE_VFORK | CloneFlags::CLONE_VM,
20317 libc::SIGCHLD,
20318 None,
20319 );
20320 assert!(!super::registers_own_image(root, &vfork));
20321 assert!(super::registers_own_image(DetTid::from_raw(17), &vfork));
20322
20323 let mut thread = crate::ThreadState::new(root, &Config::default(), ());
20324 let owner = crate::slice_boundary::lifecycle::Registry::default()
20325 .fresh(root, thread.mm_id)
20326 .unwrap();
20327 assert!(crate::slice_boundary::adopt_owner(&mut thread, owner, false).is_err());
20328 assert!(thread.slice_boundary.is_none());
20329 }
20330
20331 #[tokio::test]
20335 async fn a_reloaded_descendant_reconnects_before_publishing_its_new_memory_owner() {
20336 use reverie::Tool;
20337
20338 let parent = super::ROOT_DETPID;
20339 let child = DetTid::from_raw(17);
20340 for (case, old_mm) in [
20341 ("first exec", MmId::initial(child)),
20342 ("later exec", MmId::initial(child).for_exec(child)),
20343 ] {
20344 let config = Config {
20345 sequentialize_threads: true,
20346 max_timeslice: None,
20347 no_rcb_time: true,
20348 ..Config::default()
20349 }
20350 .with_backend(|backend| backend.needs_killed_thread_rpc_cancellation = true);
20351 let state = GlobalState::initialize(&config, false);
20352 let facts = |epoch| {
20353 reverie::RuntimePrivateMemory::Ready(reverie::RuntimePrivateMemorySnapshot {
20354 owner: [1, 2, 3, epoch],
20355 version: 1,
20356 regions: vec![reverie::RuntimePrivateMemoryRegion {
20357 start: 0x1000,
20358 end: 0x2000,
20359 }],
20360 })
20361 };
20362 {
20365 let mut sched = state.sched.lock().unwrap();
20366 sched.thread_tree.add_child(parent, parent, true);
20367 sched.thread_tree.add_child(parent, child, true);
20368 assert_eq!(sched.thread_tree.process_of(child), Some(child), "{case}");
20369 assert_eq!(
20370 sched.thread_tree.parent_process(&child),
20371 Some(parent),
20372 "{case}"
20373 );
20374 sched.record_thread_mm(child, old_mm);
20375 if old_mm != MmId::initial(child) {
20376 sched.install_test_exec_incarnation(child, old_mm);
20377 }
20378 }
20379 install_test_registration(&state, child, Ivar::new());
20380 let tool = Detcore::new(reverie::Pid::from_raw(child.as_raw()), &config);
20381 let mut old = ExternalRegistrationGuest {
20382 global: &state,
20383 config: &config,
20384 thread: tool.init_thread_state(Tid::from_raw(child.as_raw()), None),
20385 requests: Mutex::new(Vec::new()),
20386 post_exec: false,
20387 ppid: Some(reverie::Pid::from_raw(parent.as_raw())),
20388 private_memory: Some(facts(1)),
20389 };
20390 old.thread.detpid = Some(child);
20391 old.thread.mm_id = old_mm;
20392 old.thread.past_global_first_execve = true;
20393 start_external_image(&tool, &mut old, &[ResourceID::MemAddrSpace(child)]).await;
20394 super::send_and_update_time(&mut old, GlobalRequest::GlobalTimeLowerBound).await;
20395 assert_eq!(
20396 state.sched.lock().unwrap().runtime_private_memory(old_mm),
20397 Some(facts(1)),
20398 "{case}"
20399 );
20400 super::prepare_exec(&mut old, old_mm, Default::default(), Vec::new(), None).await;
20401 assert!(
20402 state
20403 .pending_exec_states
20404 .lock()
20405 .unwrap()
20406 .contains_key(&child),
20407 "{case}"
20408 );
20409 assert_eq!(
20412 state.sched.lock().unwrap().runtime_private_memory(old_mm),
20413 Some(facts(1)),
20414 "{case}: prepared old image"
20415 );
20416 let registered_children = state
20417 .sched
20418 .lock()
20419 .unwrap()
20420 .thread_tree
20421 .get_children(&parent)
20422 .clone();
20423 drop(old);
20424
20425 let mut successor = ExternalRegistrationGuest {
20426 global: &state,
20427 config: &config,
20428 thread: tool.init_thread_state(Tid::from_raw(child.as_raw()), None),
20429 requests: Mutex::new(Vec::new()),
20430 post_exec: false,
20431 ppid: Some(reverie::Pid::from_raw(parent.as_raw())),
20432 private_memory: Some(facts(2)),
20433 };
20434 successor.thread.detpid = Some(child);
20435 assert!(!successor.is_root_process(), "{case}");
20436 assert!(!successor.thread.past_global_first_execve, "{case}");
20437 start_external_image(&tool, &mut successor, &[ResourceID::MemAddrSpace(child)]).await;
20438 let new_mm = old_mm.for_exec(child);
20439 assert_eq!(successor.thread.mm_id, new_mm, "{case}");
20440 assert!(
20441 state.pending_exec_states.lock().unwrap().is_empty(),
20442 "{case}"
20443 );
20444 super::send_and_update_time(&mut successor, GlobalRequest::GlobalTimeLowerBound).await;
20445 let sched = state.sched.lock().unwrap();
20446 assert_eq!(sched.thread_mm(child), Some(new_mm), "{case}");
20447 assert_eq!(
20448 sched.thread_tree.get_children(&parent),
20449 ®istered_children,
20450 "{case}: exec is not a new child birth"
20451 );
20452 assert_eq!(sched.thread_tree.process_of(child), Some(child), "{case}");
20453 assert_eq!(
20454 sched.thread_tree.parent_process(&child),
20455 Some(parent),
20456 "{case}"
20457 );
20458 assert_eq!(
20459 sched.runtime_private_memory(new_mm),
20460 Some(facts(2)),
20461 "{case}"
20462 );
20463 let requests = successor.requests.lock().unwrap();
20464 assert_eq!(
20465 requests
20466 .iter()
20467 .filter(|request| matches!(request, GlobalRequest::CreateChildThread(..)))
20468 .count(),
20469 1,
20470 "{case}"
20471 );
20472 }
20473 }
20474
20475 #[tokio::test]
20476 async fn reloaded_post_exec_restores_prepared_input_aliases_once() {
20477 use nix::fcntl::OFlag;
20478 use reverie::Tool;
20479
20480 let config = Config {
20481 sequentialize_threads: true,
20482 max_timeslice: None,
20483 ..Config::default()
20484 }
20485 .with_backend(|backend| {
20486 backend.needs_killed_thread_rpc_cancellation = true;
20487 backend.tool_shares_guest_descriptor_table = true;
20488 });
20489 let mut state = GlobalState::initialize(&config, false);
20490 let leader = super::ROOT_DETPID;
20491 let tool = Detcore::new(reverie::Pid::from_raw(leader.as_raw()), &config);
20492
20493 let reader: OwnedFd = std::fs::File::open("/dev/null").unwrap().into();
20496 let fd = reader.as_raw_fd();
20497 let input = reverie::InheritedInput::pipe(fd).unwrap();
20498 let identity = input.identity().clone();
20499 state.install_inherited_input(input.lease().unwrap());
20500 let (source, _writer) = input.into_reader_and_writer();
20501 assert_eq!(unsafe { libc::dup2(source.as_raw_fd(), fd) }, fd);
20503 drop(source);
20504 let alias = reader.try_clone().unwrap();
20505 let cloexec = reader.try_clone().unwrap();
20506 let alias_fd = alias.as_raw_fd();
20507 let cloexec_fd = cloexec.as_raw_fd();
20508 assert_eq!(unsafe { libc::fcntl(alias_fd, libc::F_SETFD, 0) }, 0);
20510 let flags = |fd| {
20511 let result = unsafe {
20513 (
20514 libc::fcntl(fd, libc::F_GETFL),
20515 libc::fcntl(fd, libc::F_GETFD),
20516 )
20517 };
20518 assert!(result.0 >= 0 && result.1 >= 0);
20519 result
20520 };
20521 let kernel_before = [flags(fd), flags(alias_fd)];
20522 assert_eq!(
20523 kernel_before[0].0 & (libc::O_ACCMODE | libc::O_NONBLOCK),
20524 libc::O_RDONLY
20525 );
20526 assert_eq!(flags(cloexec_fd).1 & libc::FD_CLOEXEC, libc::FD_CLOEXEC);
20527
20528 let mut old = ExternalRegistrationGuest {
20529 global: &state,
20530 config: &config,
20531 thread: tool.init_thread_state(Tid::from_raw(leader.as_raw()), None),
20532 requests: Mutex::new(Vec::new()),
20533 post_exec: true,
20534 ppid: None,
20535 private_memory: None,
20536 };
20537 old.thread.detpid = Some(leader);
20538 start_external_image(
20539 &tool,
20540 &mut old,
20541 &[
20542 ResourceID::ParentContinue {
20543 parent: leader,
20544 child: leader,
20545 },
20546 ResourceID::MemAddrSpace(leader),
20547 ],
20548 )
20549 .await;
20550 tool.handle_post_exec(&mut old).await.unwrap();
20551 assert!(state.inherited_input.lock().unwrap().is_none());
20552 old.thread.dup_fd(fd, alias_fd, OFlag::empty()).unwrap();
20553 old.thread.dup_fd(fd, cloexec_fd, OFlag::O_CLOEXEC).unwrap();
20554 let origin = old.thread.file_metadata.lock().unwrap().file_handles[&fd]
20555 .inherited_external_read()
20556 .expect("the actual registered launch seed must establish read provenance");
20557 assert_eq!(
20558 (origin.dev, origin.inode, origin.rdev),
20559 (identity.dev(), identity.inode(), identity.rdev())
20560 );
20561 let old_mm = old.thread.mm_id;
20562 super::prepare_exec(&mut old, old_mm, Default::default(), Vec::new(), None).await;
20563 {
20564 let pending = state.pending_exec_states.lock().unwrap();
20565 let prepared = &pending[&leader].fd_blocking.inherited_reads;
20566 assert_eq!(
20567 prepared.keys().copied().collect::<BTreeSet<_>>(),
20568 BTreeSet::from([fd, alias_fd])
20569 );
20570 assert!(prepared.values().all(|entry| entry == &origin));
20571 assert!(!prepared.contains_key(&cloexec_fd));
20572 }
20573 drop(old);
20576 drop(cloexec);
20577 let mut successor = ExternalRegistrationGuest {
20578 global: &state,
20579 config: &config,
20580 thread: tool.init_thread_state(Tid::from_raw(leader.as_raw()), None),
20581 requests: Mutex::new(Vec::new()),
20582 post_exec: true,
20583 ppid: None,
20584 private_memory: None,
20585 };
20586 successor.thread.detpid = Some(leader);
20587 start_external_image(&tool, &mut successor, &[ResourceID::MemAddrSpace(leader)]).await;
20588 assert_eq!(successor.thread.mm_id, old_mm.for_exec(leader));
20589 assert!(state.pending_exec_states.lock().unwrap().is_empty());
20590 assert_eq!(
20591 state.post_exec_fd_blocking.lock().unwrap()[&leader]
20592 .inherited_reads
20593 .len(),
20594 2
20595 );
20596 assert!(
20597 !successor
20598 .thread
20599 .file_metadata
20600 .lock()
20601 .unwrap()
20602 .file_handles
20603 .contains_key(&fd)
20604 );
20605
20606 tool.handle_post_exec(&mut successor).await.unwrap();
20609 assert!(state.post_exec_fd_blocking.lock().unwrap().is_empty());
20610 {
20611 let metadata = successor.thread.file_metadata.lock().unwrap();
20612 for slot in [fd, alias_fd] {
20613 let entry = metadata
20614 .file_handles
20615 .get(&slot)
20616 .expect("post-exec must restore every prepared surviving input alias");
20617 assert_eq!(
20618 entry.inherited_external_read(),
20619 Some(origin.clone()),
20620 "post-exec must restore transferred origin, not plain pipe discovery"
20621 );
20622 assert_eq!(entry.ty(), crate::fd::FdType::Pipe);
20623 assert!(!entry.is_nonblocking());
20624 assert!(!entry.physically_nonblocking());
20625 assert!(!entry.is_cloexec());
20626 assert_eq!(entry.resource(), origin.resource);
20627 }
20628 assert_eq!(
20629 metadata.file_handles[&fd].open_file_id(),
20630 metadata.file_handles[&alias_fd].open_file_id()
20631 );
20632 assert_eq!(metadata.file_handles[&fd].open_file_alias_count(), 2);
20633 assert!(!metadata.file_handles.contains_key(&cloexec_fd));
20634 }
20635 assert_eq!([flags(fd), flags(alias_fd)], kernel_before);
20636 let snapshot =
20637 || serde_json::to_value(&*successor.thread.file_metadata.lock().unwrap()).unwrap();
20638 let before_rpc = snapshot();
20639 let response = successor
20640 .send_rpc((
20641 successor.thread.thread_logical_time.clone(),
20642 successor.thread.mm_id,
20643 GlobalRequest::GlobalTimeLowerBound,
20644 ))
20645 .await;
20646 assert!(matches!(
20647 response.1,
20648 GlobalResponse::GlobalTimeLowerBound(_)
20649 ));
20650 assert_eq!(
20651 snapshot(),
20652 before_rpc,
20653 "ordinary admitted RPC must leave restored metadata unchanged"
20654 );
20655 assert!(state.post_exec_fd_blocking.lock().unwrap().is_empty());
20656 assert!(state.pending_exec_states.lock().unwrap().is_empty());
20657 let requests = successor.requests.lock().unwrap();
20658 assert_eq!(
20659 requests
20660 .iter()
20661 .filter(|r| matches!(r, GlobalRequest::MarkPastFirstExecve(..)))
20662 .count(),
20663 1
20664 );
20665 assert!(matches!(
20666 requests.first(),
20667 Some(GlobalRequest::CreateChildThread(..))
20668 ));
20669 assert!(matches!(
20670 requests.get(1),
20671 Some(GlobalRequest::StartNewThread(..))
20672 ));
20673 assert!(matches!(
20674 requests.last(),
20675 Some(GlobalRequest::GlobalTimeLowerBound)
20676 ));
20677 }
20678
20679 #[tokio::test]
20680 async fn post_exec_deletes_armed_disarmed_and_non_notifying_timers() {
20681 use reverie::Tool;
20682
20683 let config = Config {
20684 sequentialize_threads: true,
20685 max_timeslice: None,
20688 ..Config::default()
20689 }
20690 .with_backend(|backend| {
20691 backend.needs_killed_thread_rpc_cancellation = true;
20692 });
20693 let state = GlobalState::initialize(&config, false);
20694 let leader = DetTid::from_raw(17);
20695 let detpid = DetPid::from_raw(17);
20696 state
20697 .sched
20698 .lock()
20699 .unwrap()
20700 .thread_tree
20701 .add_child(leader, leader, true);
20702 let next_request = Ivar::new();
20703 install_test_registration(&state, leader, next_request.clone());
20704 let tool = Detcore::new(reverie::Pid::from_raw(leader.as_raw()), &config);
20705 let mut thread = tool.init_thread_state(Tid::from_raw(leader.as_raw()), None);
20706 thread.detpid = Some(detpid);
20707 thread.stats.count_syscall(Sysno::execve);
20712 thread.thread_logical_time.add_syscall_with_cost(137);
20713 let mut guest = ExternalRegistrationGuest {
20714 global: &state,
20715 config: &config,
20716 thread,
20717 requests: Mutex::new(Vec::new()),
20718 post_exec: true,
20719 ppid: None,
20720 private_memory: None,
20721 };
20722 let t = LogicalTime::from_nanos;
20723 let [periodic, disarmed, silent] = {
20724 let mut timers = guest.thread.posix_timers.lock().unwrap();
20725 let periodic = timers.create(Some(libc::SIGUSR2), None, None, false);
20726 let disarmed = timers.create(Some(libc::SIGALRM), None, None, false);
20727 let silent = timers.create(None, None, None, false);
20728 timers.settime(periodic, 50, Some(t(100)), t(0));
20729 timers.settime(silent, 0, Some(t(200)), t(0));
20730 [periodic, disarmed, silent]
20731 };
20732
20733 for _ in 0..2 {
20737 let old_mm = guest.thread.mm_id;
20738 super::prepare_exec(&mut guest, old_mm, Default::default(), Vec::new(), None).await;
20739 guest.thread.mm_id = old_mm.for_exec(detpid);
20740 let before = guest.thread.thread_logical_time.as_nanos();
20741 tokio::time::timeout(Duration::from_secs(2), tool.handle_post_exec(&mut guest))
20742 .await
20743 .expect("post-exec must finish within the caller's scheduler turn")
20744 .unwrap();
20745 assert_eq!(guest.thread.thread_logical_time.as_nanos(), before);
20746 assert!(
20747 next_request.try_read().is_none(),
20748 "post-exec unexpectedly yielded"
20749 );
20750 let mut timers = guest.thread.posix_timers.lock().unwrap();
20751 for id in [periodic, disarmed, silent] {
20752 assert!(!timers.contains(id), "post-exec retained POSIX timer {id}");
20753 assert_eq!(timers.gettime(id, t(10)), None);
20754 assert_eq!(timers.settime(id, 0, Some(t(300)), t(10)), None);
20755 assert_eq!(timers.signal(id), None);
20756 assert!(!timers.remove(id));
20757 }
20758 }
20759 let mut timers = guest.thread.posix_timers.lock().unwrap();
20760 let new = timers.create(Some(libc::SIGUSR1), None, None, false);
20761 assert_eq!(new, 3);
20762 assert_eq!(timers.settime(new, 0, Some(t(300)), t(10)), Some((0, 0)));
20763 assert_eq!(timers.gettime(new, t(20)), Some((280, 0)));
20764 }
20765
20766 fn exec_record_test_state(
20769 sequentialize_threads: bool,
20770 launcher: &std::path::Path,
20771 ) -> (Config, GlobalState, DetTid, DetPid) {
20772 let config = Config {
20773 sequentialize_threads,
20774 launch_image: Some(crate::LaunchImage {
20775 exe: None,
20776 launcher: Some(launcher.to_path_buf()),
20777 }),
20778 ..Config::default()
20779 };
20780 let state = GlobalState::initialize(&config, false);
20781 let dettid = DetTid::from_raw(17);
20782 state
20783 .sched
20784 .lock()
20785 .unwrap()
20786 .thread_tree
20787 .add_child(dettid, dettid, true);
20788 (config, state, dettid, DetPid::from_raw(17))
20789 }
20790
20791 #[tokio::test]
20797 #[allow(clippy::disallowed_methods)] async fn an_exec_record_stands_only_where_the_kernel_names_it_or_the_launcher() {
20799 use std::os::unix::fs::MetadataExt;
20800 let directory = tempfile::tempdir().unwrap();
20801 let directory = std::fs::canonicalize(directory.path()).unwrap();
20802 let (image, launcher, other) = (
20803 directory.join("image"),
20804 directory.join("launcher"),
20805 directory.join("other"),
20806 );
20807 for file in [&image, &launcher, &other] {
20808 std::fs::write(file, file.as_os_str().as_encoded_bytes()).unwrap();
20809 }
20810 let identity = |path: &std::path::Path| {
20811 let meta = std::fs::metadata(path).unwrap();
20812 (meta.dev(), meta.ino())
20813 };
20814 let record = crate::exec_record::ExecRecord::at(std::path::Path::new("/"), &image).unwrap();
20815 for (sequentialize, kernel, kept) in [
20816 (true, identity(&image), true),
20817 (true, identity(&launcher), true),
20818 (true, identity(&other), false),
20819 (false, identity(&image), false),
20820 ] {
20821 let (config, state, leader, detpid) = exec_record_test_state(sequentialize, &launcher);
20822 install_test_registration(&state, leader, Ivar::new());
20823 let clock = DetTime::new(&config);
20824 let mm = MmId::initial(detpid);
20825 let tid = reverie::Tid::from_raw(leader.as_raw());
20826 let prepared = ExecFdState {
20827 exe: Some(record.clone()),
20828 ..Default::default()
20829 };
20830 let response = state
20831 .receive_rpc(
20832 tid,
20833 (
20834 clock.clone(),
20835 mm,
20836 GlobalRequest::PrepareExec(detpid, mm, Box::new(prepared)),
20837 ),
20838 )
20839 .await;
20840 assert_eq!(response.1, GlobalResponse::PrepareExec(()));
20841 let response = state
20842 .receive_rpc(
20843 tid,
20844 (
20845 clock,
20846 mm.for_exec(detpid),
20847 GlobalRequest::MarkPastFirstExecve(detpid, None, None, Some(kernel)),
20848 ),
20849 )
20850 .await;
20851 let GlobalResponse::MarkPastFirstExecve(overrides) = response.1 else {
20852 panic!("unexpected response {:?}", response.1);
20853 };
20854 let expected = kept.then(|| record.clone());
20855 assert_eq!(overrides.exe, expected, "{sequentialize} {kernel:?}");
20856 assert_eq!(
20857 state.exec_images.lock().unwrap().get(&detpid).cloned(),
20858 expected,
20859 "{sequentialize} {kernel:?}"
20860 );
20861 }
20862 }
20863
20864 #[tokio::test]
20870 #[allow(clippy::disallowed_methods)] async fn the_launch_image_goes_to_the_registered_roots_first_image_only() {
20872 let directory = tempfile::tempdir().unwrap();
20873 let directory = std::fs::canonicalize(directory.path()).unwrap();
20874 let (image, launcher) = (directory.join("image"), directory.join("launcher"));
20875 for file in [&image, &launcher] {
20876 std::fs::write(file, file.as_os_str().as_encoded_bytes()).unwrap();
20877 }
20878 let record = crate::exec_record::ExecRecord::at(std::path::Path::new("/"), &image).unwrap();
20879 for sequentialize in [true, false] {
20880 let config = Config {
20881 sequentialize_threads: sequentialize,
20882 launch_image: Some(crate::LaunchImage {
20883 exe: Some(image.clone()),
20884 launcher: Some(launcher.clone()),
20885 }),
20886 ..Config::default()
20887 };
20888 let state = GlobalState::initialize(&config, false);
20889 let (root, other) = (DetTid::from_raw(17), DetTid::from_raw(23));
20890 for tid in [root, other] {
20891 state
20892 .sched
20893 .lock()
20894 .unwrap()
20895 .thread_tree
20896 .add_child(tid, tid, true);
20897 install_test_registration(&state, tid, Ivar::new());
20898 }
20899 state.root_dettid.set(root).unwrap();
20900 let clock = DetTime::new(&config);
20901 let mark = |tid: DetTid| {
20902 let process = DetPid::from_raw(tid.as_raw());
20903 (
20904 reverie::Tid::from_raw(tid.as_raw()),
20905 (
20906 clock.clone(),
20907 MmId::initial(process),
20908 GlobalRequest::MarkPastFirstExecve(process, None, None, None),
20909 ),
20910 )
20911 };
20912 for (tid, expected) in [(other, None), (root, Some(&record)), (root, None)] {
20913 let (from, request) = mark(tid);
20914 let response = state.receive_rpc(from, request).await;
20915 let GlobalResponse::MarkPastFirstExecve(overrides) = response.1 else {
20916 panic!("unexpected response {:?}", response.1);
20917 };
20918 assert_eq!(overrides.exe.as_ref(), expected, "{sequentialize} {tid:?}");
20919 }
20920 }
20921 }
20922
20923 #[tokio::test]
20932 #[allow(clippy::disallowed_methods)] async fn an_images_own_record_fills_in_and_outranks_only_the_launchs_search() {
20934 let directory = tempfile::tempdir().unwrap();
20935 let directory = std::fs::canonicalize(directory.path()).unwrap();
20936 let (image, other, launcher) = (
20937 directory.join("image"),
20938 directory.join("other"),
20939 directory.join("launcher"),
20940 );
20941 for file in [&image, &other, &launcher] {
20942 std::fs::write(file, file.as_os_str().as_encoded_bytes()).unwrap();
20943 }
20944 let record_of = |path: &std::path::Path| {
20945 crate::exec_record::ExecRecord::at(std::path::Path::new("/"), path)
20946 };
20947 let (image_record, other_record, launcher_record) =
20948 (record_of(&image), record_of(&other), record_of(&launcher));
20949
20950 for (sequentialize, prepared, unprepared, expected) in [
20952 (true, None, other_record.clone(), other_record.clone()),
20953 (
20954 true,
20955 image_record.clone(),
20956 other_record.clone(),
20957 image_record.clone(),
20958 ),
20959 (true, None, launcher_record.clone(), None),
20960 (false, None, other_record.clone(), None),
20961 ] {
20962 let (config, state, leader, detpid) = exec_record_test_state(sequentialize, &launcher);
20963 install_test_registration(&state, leader, Ivar::new());
20964 let clock = DetTime::new(&config);
20965 let mm = MmId::initial(detpid);
20966 let tid = reverie::Tid::from_raw(leader.as_raw());
20967 let preparation = ExecFdState {
20968 exe: prepared.clone(),
20969 ..Default::default()
20970 };
20971 let response = state
20972 .receive_rpc(
20973 tid,
20974 (
20975 clock.clone(),
20976 mm,
20977 GlobalRequest::PrepareExec(detpid, mm, Box::new(preparation)),
20978 ),
20979 )
20980 .await;
20981 assert_eq!(response.1, GlobalResponse::PrepareExec(()));
20982 let response = state
20983 .receive_rpc(
20984 tid,
20985 (
20986 clock,
20987 mm.for_exec(detpid),
20988 GlobalRequest::MarkPastFirstExecve(detpid, None, unprepared.clone(), None),
20989 ),
20990 )
20991 .await;
20992 let GlobalResponse::MarkPastFirstExecve(overrides) = response.1 else {
20993 panic!("unexpected response {:?}", response.1);
20994 };
20995 assert_eq!(
20996 overrides.exe, expected,
20997 "{sequentialize} prepared {prepared:?} unprepared {unprepared:?}"
20998 );
20999 }
21000
21001 {
21005 let config = Config {
21006 sequentialize_threads: true,
21007 launch_image: Some(crate::LaunchImage {
21008 exe: Some(image.clone()),
21009 launcher: None,
21010 }),
21011 ..Config::default()
21012 };
21013 let state = GlobalState::initialize(&config, false);
21014 let root = DetTid::from_raw(17);
21015 state
21016 .sched
21017 .lock()
21018 .unwrap()
21019 .thread_tree
21020 .add_child(root, root, true);
21021 install_test_registration(&state, root, Ivar::new());
21022 state.root_dettid.set(root).unwrap();
21023 let process = DetPid::from_raw(root.as_raw());
21024 let tid = reverie::Tid::from_raw(root.as_raw());
21025 let clock = DetTime::new(&config);
21026 let mm = MmId::initial(process);
21027 let preparation = ExecFdState {
21028 exe: other_record.clone(),
21029 ..Default::default()
21030 };
21031 let response = state
21032 .receive_rpc(
21033 tid,
21034 (
21035 clock.clone(),
21036 mm,
21037 GlobalRequest::PrepareExec(process, mm, Box::new(preparation)),
21038 ),
21039 )
21040 .await;
21041 assert_eq!(response.1, GlobalResponse::PrepareExec(()));
21042 let response = state
21043 .receive_rpc(
21044 tid,
21045 (
21046 clock,
21047 mm.for_exec(process),
21048 GlobalRequest::MarkPastFirstExecve(process, None, None, None),
21049 ),
21050 )
21051 .await;
21052 let GlobalResponse::MarkPastFirstExecve(overrides) = response.1 else {
21053 panic!("unexpected response {:?}", response.1);
21054 };
21055 assert_eq!(
21056 overrides.exe, image_record,
21057 "no launcher: the launch's record"
21058 );
21059 }
21060
21061 for (launch_exe, unprepared, expected) in [
21064 (Some(&image), other_record.clone(), other_record.clone()),
21065 (Some(&image), None, image_record.clone()),
21066 (None, other_record.clone(), other_record.clone()),
21067 (Some(&image), launcher_record.clone(), image_record.clone()),
21068 ] {
21069 let config = Config {
21070 sequentialize_threads: true,
21071 launch_image: Some(crate::LaunchImage {
21072 exe: launch_exe.cloned(),
21073 launcher: Some(launcher.clone()),
21074 }),
21075 ..Config::default()
21076 };
21077 let state = GlobalState::initialize(&config, false);
21078 let root = DetTid::from_raw(17);
21079 state
21080 .sched
21081 .lock()
21082 .unwrap()
21083 .thread_tree
21084 .add_child(root, root, true);
21085 install_test_registration(&state, root, Ivar::new());
21086 state.root_dettid.set(root).unwrap();
21087 let process = DetPid::from_raw(root.as_raw());
21088 let response = state
21089 .receive_rpc(
21090 reverie::Tid::from_raw(root.as_raw()),
21091 (
21092 DetTime::new(&config),
21093 MmId::initial(process),
21094 GlobalRequest::MarkPastFirstExecve(process, None, unprepared.clone(), None),
21095 ),
21096 )
21097 .await;
21098 let GlobalResponse::MarkPastFirstExecve(overrides) = response.1 else {
21099 panic!("unexpected response {:?}", response.1);
21100 };
21101 assert_eq!(
21102 overrides.exe, expected,
21103 "launch {launch_exe:?} unprepared {unprepared:?}"
21104 );
21105 }
21106 }
21107
21108 #[tokio::test]
21109 async fn only_successful_exec_cancels_posix_deadlines() {
21110 let (config, state, leader, detpid) = cancellation_test_state();
21111 install_test_registration(&state, leader, Ivar::new());
21112 let clock = DetTime::new(&config);
21113 let mm = MmId::initial(detpid);
21114 let deadline = LogicalTime::from_nanos(1_000_000);
21115 {
21116 let mut sched = state.sched.lock().unwrap();
21117 sched.register_alarm(
21118 detpid,
21119 leader,
21120 LogicalTime::ZERO,
21121 deadline,
21122 LogicalTime::ZERO,
21123 Signal::SIGALRM,
21124 );
21125 sched.register_posix_timer(
21126 detpid,
21127 crate::scheduler::timed_waiters::SignalTarget::Process(leader),
21128 0,
21129 Some(deadline),
21130 deadline,
21131 Signal::SIGUSR2.into(),
21132 );
21133 }
21134 let before: Vec<_> = state
21135 .sched
21136 .lock()
21137 .unwrap()
21138 .blocked
21139 .timed_waiters
21140 .iter()
21141 .collect();
21142 for request in [
21143 GlobalRequest::PrepareExec(detpid, mm, Default::default()),
21144 GlobalRequest::CancelExec(detpid),
21145 GlobalRequest::PrepareExec(detpid, mm, Default::default()),
21146 ] {
21147 state
21148 .receive_rpc(
21149 reverie::Tid::from_raw(leader.as_raw()),
21150 (clock.clone(), mm, request),
21151 )
21152 .await;
21153 assert_eq!(
21154 state
21155 .sched
21156 .lock()
21157 .unwrap()
21158 .blocked
21159 .timed_waiters
21160 .iter()
21161 .collect::<Vec<_>>(),
21162 before
21163 );
21164 }
21165
21166 let response = state
21167 .receive_rpc(
21168 reverie::Tid::from_raw(leader.as_raw()),
21169 (
21170 clock,
21171 mm.for_exec(detpid),
21172 GlobalRequest::MarkPastFirstExecve(detpid, None, None, None),
21173 ),
21174 )
21175 .await;
21176 assert_eq!(
21177 response.1,
21178 GlobalResponse::MarkPastFirstExecve(Default::default())
21179 );
21180 let sched = state.sched.lock().unwrap();
21181 assert_eq!(
21182 sched.blocked.timed_waiters.alarm_state(detpid),
21183 Some((deadline, LogicalTime::ZERO))
21184 );
21185 assert_eq!(sched.blocked.timed_waiters.iter().count(), 1);
21186 }
21187
21188 #[tokio::test]
21189 async fn failed_exec_clears_prepared_state_without_retiring_siblings() {
21190 let (config, state, leader, detpid) = cancellation_test_state();
21191 let sibling = DetTid::from_raw(leader.as_raw() + 1);
21192 install_test_registration(&state, leader, Ivar::new());
21193 state
21194 .sched
21195 .lock()
21196 .unwrap()
21197 .thread_tree
21198 .add_child(leader, sibling, false);
21199 install_test_registration(&state, sibling, Ivar::new());
21200 let clock = DetTime::new(&config);
21201
21202 let prepared = state
21203 .receive_rpc(
21204 reverie::Tid::from_raw(leader.as_raw()),
21205 (
21206 clock.clone(),
21207 MmId::initial(leader),
21208 GlobalRequest::PrepareExec(detpid, MmId::initial(detpid), Default::default()),
21209 ),
21210 )
21211 .await;
21212 assert_eq!(prepared.1, GlobalResponse::PrepareExec(()));
21213 assert!(
21214 state
21215 .pending_exec_states
21216 .lock()
21217 .unwrap()
21218 .contains_key(&detpid)
21219 );
21220
21221 let cancelled = state
21222 .receive_rpc(
21223 reverie::Tid::from_raw(leader.as_raw()),
21224 (
21225 clock,
21226 MmId::initial(leader),
21227 GlobalRequest::CancelExec(detpid),
21228 ),
21229 )
21230 .await;
21231 assert_eq!(cancelled.1, GlobalResponse::CancelExec(()));
21232 assert!(state.pending_exec_states.lock().unwrap().is_empty());
21233 {
21234 let scheduler = state.sched.lock().unwrap();
21235 assert!(!scheduler.thread_is_logically_killed(leader));
21236 assert!(!scheduler.thread_is_logically_killed(sibling));
21237 assert_eq!(scheduler.next_turns.len(), 2);
21238 }
21239
21240 state.pending_exec_states.lock().unwrap().insert(
21241 detpid,
21242 PendingExecState {
21243 caller: leader,
21244 process: detpid,
21245 mm: MmId::initial(detpid),
21246 fd_blocking: Default::default(),
21247 },
21248 );
21249 state.post_exec_fd_blocking.lock().unwrap().insert(
21250 leader,
21251 ExecFdState::from([42].into_iter().collect::<ExecFdBlockingOverrides>()),
21252 );
21253 state
21254 .recv_deregister_thread(
21255 reverie::Tid::from_raw(leader.as_raw()),
21256 ThreadDeregistration {
21257 thread_start_entered: true,
21258 vfork_origin: None,
21259 dettid: leader,
21260 detpid,
21261 mm: MmId::initial(detpid),
21262 timeslice_stats: TimesliceStats::default(),
21263 syscall_count: 0,
21264 syscall_histogram: Default::default(),
21265 chaos_epochs: Vec::new(),
21266 killed_by_group_signal: false,
21267 passed_descriptors_out: false,
21268 },
21269 )
21270 .await;
21271 assert!(state.pending_exec_states.lock().unwrap().is_empty());
21272 assert!(state.post_exec_fd_blocking.lock().unwrap().is_empty());
21273
21274 state.pending_exec_states.lock().unwrap().insert(
21275 detpid,
21276 PendingExecState {
21277 caller: leader,
21278 process: detpid,
21279 mm: MmId::initial(detpid),
21280 fd_blocking: Default::default(),
21281 },
21282 );
21283 state
21284 .recv_deregister_thread(
21285 reverie::Tid::from_raw(leader.as_raw()),
21286 ThreadDeregistration {
21287 thread_start_entered: true,
21288 vfork_origin: None,
21289 dettid: leader,
21290 detpid,
21291 mm: MmId::initial(detpid).for_exec(detpid),
21292 timeslice_stats: TimesliceStats::default(),
21293 syscall_count: 0,
21294 syscall_histogram: Default::default(),
21295 chaos_epochs: Vec::new(),
21296 killed_by_group_signal: false,
21297 passed_descriptors_out: false,
21298 },
21299 )
21300 .await;
21301 assert!(state.pending_exec_states.lock().unwrap().is_empty());
21302
21303 state.pending_exec_states.lock().unwrap().insert(
21304 detpid,
21305 PendingExecState {
21306 caller: leader,
21307 process: detpid,
21308 mm: MmId::initial(detpid),
21309 fd_blocking: Default::default(),
21310 },
21311 );
21312 state.post_exec_fd_blocking.lock().unwrap().insert(
21313 leader,
21314 ExecFdState::from([42].into_iter().collect::<ExecFdBlockingOverrides>()),
21315 );
21316 state.complete_physical_process_exit(detpid.as_raw());
21317 assert!(state.pending_exec_states.lock().unwrap().is_empty());
21318 assert!(state.post_exec_fd_blocking.lock().unwrap().is_empty());
21319 }
21320
21321 #[test]
21322 fn completed_unsupported_syscalls_transfer_once_without_duplicates() {
21323 let mut state = GlobalState::initialize(&Config::default(), false);
21324 for name in ["restart_syscall", "io_setup", "restart_syscall"] {
21325 state
21326 .unsupported_syscalls
21327 .get_mut()
21328 .unwrap()
21329 .insert(name.to_owned());
21330 }
21331 assert_eq!(
21332 state.take_unsupported_syscalls(),
21333 ["io_setup".to_owned(), "restart_syscall".to_owned()]
21334 .into_iter()
21335 .collect()
21336 );
21337 assert!(state.take_unsupported_syscalls().is_empty());
21338 }
21339
21340 #[test]
21341 fn unsupported_syscall_report_duplicate_is_close_on_exec() {
21342 let mut descriptors = [-1; 2];
21343 assert_eq!(
21344 unsafe { libc::pipe2(descriptors.as_mut_ptr(), libc::O_CLOEXEC) },
21345 0
21346 );
21347 let _reader = unsafe { OwnedFd::from_raw_fd(descriptors[0]) };
21349 let writer = unsafe { OwnedFd::from_raw_fd(descriptors[1]) };
21350 let config = Config {
21351 unsupported_syscall_report_fd: Some(writer.as_raw_fd()),
21352 ..Config::default()
21353 };
21354
21355 let state = GlobalState::initialize(&config, false);
21356 let duplicate = state
21357 .unsupported_syscall_report_fd
21358 .as_ref()
21359 .expect("report writer should be duplicated")
21360 .lock()
21361 .unwrap();
21362 let flags = unsafe { libc::fcntl(duplicate.as_raw_fd(), libc::F_GETFD) };
21363 assert_ne!(flags, -1);
21364 assert_ne!(flags & libc::FD_CLOEXEC, 0);
21365 }
21366
21367 #[test]
21370 fn device_pool_remaps_deterministically() {
21371 use super::DevicePool;
21372
21373 let raw_root = 0x20; let raw_proc_run1 = 3_145_792; let raw_proc_run2 = 3_145_788; let mut pool1 = DevicePool::new();
21381 let root1 = pool1.determinize(raw_root);
21382 let proc1 = pool1.determinize(raw_proc_run1);
21383 let mut pool2 = DevicePool::new();
21385 let root2 = pool2.determinize(raw_root);
21386 let proc2 = pool2.determinize(raw_proc_run2);
21387
21388 assert_eq!(root1, root2);
21391 assert_eq!(proc1, proc2);
21392
21393 assert_ne!(root1, proc1);
21395 assert_eq!(root1, 1);
21396 assert_eq!(proc1, 2);
21397
21398 assert_eq!(pool1.determinize(raw_root), root1);
21400 assert_eq!(pool1.determinize(raw_proc_run1), proc1);
21401 }
21402
21403 #[test]
21404 fn the_procfs_device_is_never_minted_for_another_device() {
21405 use super::DevicePool;
21406
21407 let mut pool = DevicePool::new();
21408 pool.next_device = crate::procfs_inode::VIRTUAL_PROC_DEVICE - 1;
21409 let before = pool.determinize(libc::makedev(8, 1));
21410 let after = pool.determinize(libc::makedev(8, 2));
21411 assert_eq!(before, crate::procfs_inode::VIRTUAL_PROC_DEVICE - 1);
21412 assert_eq!(after, crate::procfs_inode::VIRTUAL_PROC_DEVICE + 1);
21413 }
21414
21415 #[test]
21416 fn this_namespaces_procfs_device_is_detcores() {
21417 use std::os::unix::fs::MetadataExt;
21418
21419 use super::DevicePool;
21420
21421 let mut pool = DevicePool::for_this_mount_namespace();
21422 let proc = std::fs::metadata("/proc").unwrap().dev();
21423 assert_eq!(
21424 pool.determinize(proc),
21425 crate::procfs_inode::VIRTUAL_PROC_DEVICE
21426 );
21427 assert_eq!(pool.determinize(libc::makedev(8, 1)), 1);
21428 }
21429
21430 #[test]
21431 fn mountinfo_prepopulation_is_reused_by_later_stat_observations() {
21432 use super::DevicePool;
21433
21434 let mountinfo_devices = [libc::makedev(8, 1), libc::makedev(0, 44)];
21435 let mut pool = DevicePool::new();
21436 let rendered = mountinfo_devices
21437 .into_iter()
21438 .map(|raw| pool.determinize(raw))
21439 .collect::<Vec<_>>();
21440
21441 assert_eq!(rendered, [1, 2]);
21442 assert_eq!(pool.determinize(libc::makedev(0, 44)), rendered[1]);
21443 assert_eq!(pool.determinize(libc::makedev(8, 1)), rendered[0]);
21444 assert_eq!(pool.determinize(libc::makedev(259, 7)), 3);
21445 }
21446
21447 #[tokio::test]
21448 async fn late_futex_rpc_after_thread_removal_returns_eintr() {
21449 let config = Config {
21450 sequentialize_threads: true,
21451 ..Config::default()
21452 };
21453 let state = GlobalState::initialize(&config, false);
21454 let dettid = DetTid::from_raw(17);
21455 let detpid = DetPid::from_raw(17);
21456 let response = state
21457 .recv_futex_action(
21458 RpcIncarnation {
21459 dettid,
21460 mm: MmId::initial(detpid),
21461 },
21462 FutexAction::WaitRequest(None, None),
21463 FutexID::private(MmId::initial(detpid), 0x1000),
21464 0,
21465 u32::MAX,
21466 true,
21467 )
21468 .await;
21469
21470 assert!(matches!(
21471 response,
21472 Some(SchedValue::Value(value)) if value == nix::errno::Errno::EINTR as u64
21473 ));
21474 assert!(
21475 !state
21476 .sched
21477 .lock()
21478 .unwrap()
21479 .guest_held_marks_revoked_for_test()
21480 );
21481 }
21482
21483 #[tokio::test]
21484 async fn late_child_tid_wait_after_exit_returns_spurious_wake() {
21485 let config = Config {
21486 sequentialize_threads: true,
21487 ..Config::default()
21488 };
21489 let state = GlobalState::initialize(&config, false);
21490 let detpid = DetPid::from_raw(17);
21491 let child = DetTid::from_raw(18);
21492 let futex = FutexID::private(MmId::initial(detpid), 0x1000);
21493 state.sched.lock().unwrap().next_turns.insert(
21494 detpid,
21495 ThreadNextTurn {
21496 dettid: detpid,
21497 child_tid_addr: 0,
21498 req: Ivar::new(),
21499 resp: Ivar::new(),
21500 protocol: Default::default(),
21501 },
21502 );
21503 state
21504 .sched
21505 .lock()
21506 .unwrap()
21507 .wake_futex_child_cleartid(futex, child);
21508 assert!(
21509 state
21510 .sched
21511 .lock()
21512 .unwrap()
21513 .child_tid_was_cleared(futex, child.as_raw())
21514 );
21515 assert!(
21516 !state
21517 .sched
21518 .lock()
21519 .unwrap()
21520 .child_tid_was_cleared(futex, child.as_raw() + 1)
21521 );
21522
21523 let response = state
21524 .recv_futex_action(
21525 RpcIncarnation {
21526 dettid: detpid,
21527 mm: MmId::initial(detpid),
21528 },
21529 FutexAction::WaitRequest(None, None),
21530 futex,
21531 child.as_raw(),
21532 u32::MAX,
21533 false,
21534 )
21535 .await;
21536
21537 assert!(matches!(response, Some(SchedValue::Value(0))));
21538 assert!(state.sched.lock().unwrap().blocked.futex_waiters.is_empty());
21539 }
21540
21541 #[tokio::test]
21542 async fn late_resource_request_after_logical_kill_is_cancelled() {
21543 let (config, state, dettid, detpid) = cancellation_test_state();
21544 install_test_registration(&state, dettid, Ivar::new());
21545 let mut current_time = DetTime::new(&config);
21546 current_time.add_syscall();
21547 state.global_time.lock().unwrap().update_global_time(
21548 dettid,
21549 current_time.as_nanos(),
21550 LogicalTime::ZERO,
21551 );
21552 state
21553 .sched
21554 .lock()
21555 .unwrap()
21556 .logically_kill_thread(&dettid, &detpid, MmId::initial(detpid));
21557 let (global_before, thread_before) = {
21558 let global_time = state.global_time.lock().unwrap();
21559 (global_time.as_nanos(), global_time.threads_time(dettid))
21560 };
21561 let mut late_time = current_time;
21562 late_time.add_syscall();
21563
21564 let response = state
21565 .receive_rpc(
21566 reverie::Tid::from_raw(dettid.as_raw()),
21567 (
21568 late_time,
21569 MmId::initial(dettid),
21570 GlobalRequest::RequestResources(Resources::new(dettid), detpid),
21571 ),
21572 )
21573 .await;
21574
21575 assert_eq!(response, (None, GlobalResponse::ThreadExited));
21576 assert!(!state.sched.lock().unwrap().next_turns.contains_key(&dettid));
21577 let global_time = state.global_time.lock().unwrap();
21578 assert_eq!(global_time.as_nanos(), global_before);
21579 assert_eq!(global_time.threads_time(dettid), thread_before);
21580 }
21581
21582 #[tokio::test]
21583 async fn duplicate_deregistration_is_acknowledged_without_clock_or_scheduler_mutation() {
21584 let (config, state, dettid, detpid) = cancellation_test_state();
21585 install_test_registration(&state, dettid, Ivar::new());
21586 let mut current_time = DetTime::new(&config);
21587 current_time.add_syscall();
21588 state.global_time.lock().unwrap().update_global_time(
21589 dettid,
21590 current_time.as_nanos(),
21591 LogicalTime::ZERO,
21592 );
21593 state
21594 .sched
21595 .lock()
21596 .unwrap()
21597 .logically_kill_thread(&dettid, &detpid, MmId::initial(detpid));
21598 let (global_before, thread_before) = {
21599 let global_time = state.global_time.lock().unwrap();
21600 (global_time.as_nanos(), global_time.threads_time(dettid))
21601 };
21602 let mut late_time = current_time;
21603 late_time.add_syscall();
21604 let mut final_stats = TimesliceStats::default();
21605 final_stats.record(7);
21606
21607 let first_response = state
21608 .receive_rpc(
21609 reverie::Tid::from_raw(dettid.as_raw()),
21610 (
21611 late_time.clone(),
21612 MmId::initial(dettid),
21613 GlobalRequest::DeregisterThread(ThreadDeregistration {
21614 thread_start_entered: true,
21615 vfork_origin: None,
21616 dettid,
21617 detpid,
21618 mm: MmId::initial(detpid),
21619 timeslice_stats: final_stats,
21620 syscall_count: 17,
21621 syscall_histogram: Default::default(),
21622 chaos_epochs: Vec::new(),
21623 killed_by_group_signal: false,
21624 passed_descriptors_out: false,
21625 }),
21626 ),
21627 )
21628 .await;
21629 assert_eq!(first_response, (None, GlobalResponse::DeregisterThread(())));
21630 assert_eq!(
21631 state
21632 .sched
21633 .lock()
21634 .unwrap()
21635 .per_thread_timeslice
21636 .get(&dettid),
21637 Some(&final_stats)
21638 );
21639 assert_eq!(
21640 state.sched.lock().unwrap().per_thread_syscalls.get(&dettid),
21641 Some(&17)
21642 );
21643
21644 late_time.add_syscall();
21645 let duplicate_response = state
21646 .receive_rpc(
21647 reverie::Tid::from_raw(dettid.as_raw()),
21648 (
21649 late_time,
21650 MmId::initial(dettid),
21651 GlobalRequest::DeregisterThread(ThreadDeregistration {
21652 thread_start_entered: true,
21653 vfork_origin: None,
21654 dettid,
21655 detpid,
21656 mm: MmId::initial(detpid),
21657 timeslice_stats: final_stats,
21658 syscall_count: 99,
21659 syscall_histogram: Default::default(),
21660 chaos_epochs: Vec::new(),
21661 killed_by_group_signal: false,
21662 passed_descriptors_out: false,
21663 }),
21664 ),
21665 )
21666 .await;
21667 assert_eq!(
21668 duplicate_response,
21669 (None, GlobalResponse::DeregisterThread(()))
21670 );
21671 assert_eq!(
21672 state
21673 .sched
21674 .lock()
21675 .unwrap()
21676 .per_thread_timeslice
21677 .get(&dettid),
21678 Some(&final_stats)
21679 );
21680 assert_eq!(
21681 state.sched.lock().unwrap().per_thread_syscalls.get(&dettid),
21682 Some(&17),
21683 "a duplicate deregistration must not double-count or replace final accounting"
21684 );
21685 let summary = state
21686 .sched
21687 .lock()
21688 .unwrap()
21689 .generate_partial_run_summary(None)
21690 .unwrap();
21691 assert_eq!(summary.syscalls, Some(17));
21692 assert!(!state.sched.lock().unwrap().next_turns.contains_key(&dettid));
21693 let global_time = state.global_time.lock().unwrap();
21694 assert_eq!(global_time.as_nanos(), global_before);
21695 assert_eq!(global_time.threads_time(dettid), thread_before);
21696 }
21697
21698 #[tokio::test]
21699 async fn child_registration_fails_closed_for_a_tombstoned_tid() {
21700 let (config, state, parent, detpid) = cancellation_test_state();
21701 install_test_registration(&state, parent, Ivar::new());
21702 let child = DetTid::from_raw(18);
21703 state
21704 .sched
21705 .lock()
21706 .unwrap()
21707 .thread_tree
21708 .add_child(parent, child, false);
21709 install_test_registration(&state, child, Ivar::new());
21710 state
21711 .sched
21712 .lock()
21713 .unwrap()
21714 .logically_kill_thread(&child, &detpid, MmId::initial(detpid));
21715
21716 let response = state
21717 .receive_rpc(
21718 reverie::Tid::from_raw(parent.as_raw()),
21719 (
21720 DetTime::new(&config),
21721 MmId::initial(parent),
21722 GlobalRequest::CreateChildThread(
21723 child,
21724 detpid,
21725 0,
21726 None,
21727 libc::SIGCHLD,
21728 None,
21729 Some(DEFAULT_PRIORITY),
21730 ),
21731 ),
21732 )
21733 .await;
21734
21735 assert_eq!(response, (None, GlobalResponse::ThreadExited));
21736 let scheduler = state.sched.lock().unwrap();
21737 assert!(scheduler.thread_is_logically_killed(child));
21738 assert!(!scheduler.next_turns.contains_key(&child));
21739 assert!(!scheduler.priorities.contains_key(&child));
21740 assert!(!state.global_time.lock().unwrap().contains_thread(parent));
21741 }
21742
21743 #[tokio::test]
21744 async fn pending_resource_request_woken_by_logical_kill_is_terminal() {
21745 let (_, state, dettid, detpid) = cancellation_test_state();
21746 let request_seen = Ivar::new();
21747 install_test_registration(&state, dettid, request_seen.clone());
21748
21749 let request = state.recv_request_resources(
21750 reverie::Tid::from_raw(dettid.as_raw()),
21751 detpid,
21752 Resources::new(dettid),
21753 None,
21754 );
21755 let kill_after_request = async {
21756 while request_seen.try_read().is_none() {
21757 tokio::task::yield_now().await;
21758 }
21759 state.sched.lock().unwrap().logically_kill_thread(
21760 &dettid,
21761 &detpid,
21762 MmId::initial(detpid),
21763 );
21764 };
21765
21766 let (response, ()) = tokio::join!(request, kill_after_request);
21767 assert_eq!(response, (SchedulerRpcResult::ThreadExited, None));
21768 }
21769
21770 #[tokio::test]
21777 async fn an_exit_group_grant_response_takes_only_the_callers_own_threads() {
21778 let tid = DetTid::from_raw;
21779 let exit_group = |caller: DetTid, process: DetPid| {
21780 let mut rs = Resources::new(caller);
21781 rs.insert(
21782 ResourceID::Exit {
21783 group: true,
21784 process,
21785 mm: MmId::initial(process),
21786 },
21787 Permission::RW,
21788 );
21789 rs
21790 };
21791 let granted = || SchedResponse::Go(Some(SchedValue::Value(1)));
21792
21793 let state = GlobalState::initialize(&Config::default(), false);
21795 let (parent, process, reused, other) = (tid(37), tid(38), tid(39), tid(50));
21796 {
21797 let mut sched = state.sched.lock().unwrap();
21798 sched.thread_tree.add_child(parent, parent, true);
21799 sched.thread_tree.add_child(parent, process, true);
21800 sched.thread_tree.add_child(process, reused, false);
21801 sched.thread_tree.add_child(parent, other, true);
21802 sched.thread_tree.add_child(other, reused, false);
21803 }
21804 for thread in [parent, process, reused, other] {
21805 install_test_registration(&state, thread, Ivar::new());
21806 }
21807 let (result, _) = state
21808 .finish_resource_response(
21809 reverie::Tid::from_raw(process.as_raw()),
21810 process,
21811 exit_group(process, process),
21812 None,
21813 granted(),
21814 )
21815 .await;
21816 assert!(matches!(result, SchedulerRpcResult::Continue(_)));
21817 assert!(state.sched.lock().unwrap().next_turns.contains_key(&reused));
21818
21819 let state = GlobalState::initialize(&Config::default(), false);
21822 let (parent, caller, other, sibling) = (tid(37), tid(38), tid(50), tid(51));
21823 {
21824 let mut sched = state.sched.lock().unwrap();
21825 sched.thread_tree.add_child(parent, parent, true);
21826 sched.thread_tree.add_child(parent, caller, true);
21827 sched.thread_tree.add_child(parent, other, true);
21828 sched.thread_tree.add_child(other, caller, false);
21829 sched.thread_tree.add_child(other, sibling, false);
21830 }
21831 for thread in [parent, caller, other, sibling] {
21832 install_test_registration(&state, thread, Ivar::new());
21833 }
21834 let (result, _) = state
21835 .finish_resource_response(
21836 reverie::Tid::from_raw(caller.as_raw()),
21837 other,
21838 exit_group(caller, other),
21839 None,
21840 granted(),
21841 )
21842 .await;
21843 assert!(matches!(result, SchedulerRpcResult::Continue(_)));
21844 let sched = state.sched.lock().unwrap();
21845 assert!(!sched.next_turns.contains_key(&sibling));
21846 assert!(sched.next_turns.contains_key(&caller));
21847 }
21848
21849 #[tokio::test]
21850 async fn trace_replay_yield_propagates_terminal_scheduler_cancellation() {
21851 let dettid = DetTid::from_raw(17);
21852 let detpid = DetPid::from_raw(17);
21853 let next_tid = DetTid::from_raw(18);
21854 let event = SchedEvent {
21855 dettid,
21856 op: Op::OtherInstructions,
21857 count: 1,
21858 start_rip: None,
21859 end_rip: None,
21860 end_time: Some(LogicalTime::from_nanos(1)),
21861 };
21862 let next_event = SchedEvent {
21863 dettid: next_tid,
21864 end_time: Some(LogicalTime::from_nanos(2)),
21865 ..event.clone()
21866 };
21867 let trace_file = tempfile::NamedTempFile::new().unwrap();
21868 std::fs::write(
21869 trace_file.path(),
21870 PreemptionRecord::from_sched_events(vec![event.clone(), next_event]).to_string(),
21871 )
21872 .unwrap();
21873 let config = Config {
21874 sequentialize_threads: true,
21875 replay_schedule_from: Some(trace_file.path().to_path_buf()),
21876 ..Config::default()
21877 }
21878 .with_backend(|backend| {
21879 backend.needs_killed_thread_rpc_cancellation = true;
21880 });
21881 let state = GlobalState::initialize(&config, false);
21882 state
21883 .sched
21884 .lock()
21885 .unwrap()
21886 .thread_tree
21887 .add_child(dettid, dettid, true);
21888 state
21889 .sched
21890 .lock()
21891 .unwrap()
21892 .thread_tree
21893 .add_child(dettid, next_tid, false);
21894 let request_seen = Ivar::new();
21895 install_test_registration(&state, dettid, request_seen.clone());
21896 install_test_registration(&state, next_tid, Ivar::new());
21897
21898 let replay = state.recv_trace_schedevent(event, detpid, MmId::initial(detpid), false, None);
21899 let kill_after_replay_yield = async {
21900 while request_seen.try_read().is_none() {
21901 tokio::task::yield_now().await;
21902 }
21903 state.sched.lock().unwrap().logically_kill_thread(
21904 &dettid,
21905 &detpid,
21906 MmId::initial(detpid),
21907 );
21908 };
21909 let (response, ()) = tokio::time::timeout(Duration::from_secs(1), async {
21910 tokio::join!(replay, kill_after_replay_yield)
21911 })
21912 .await
21913 .expect("trace replay cancellation did not terminate the pending scheduler RPC");
21914
21915 assert_eq!(response, SchedulerRpcResult::ThreadExited);
21916 }
21917
21918 #[tokio::test]
21919 async fn pending_start_request_woken_by_logical_kill_is_terminal() {
21920 let (config, state, dettid, detpid) = cancellation_test_state();
21921 let request_seen = Ivar::new();
21922 install_test_registration(&state, dettid, request_seen.clone());
21923 let request = state.receive_rpc(
21924 reverie::Tid::from_raw(dettid.as_raw()),
21925 (
21926 DetTime::new(&config),
21927 MmId::initial(dettid),
21928 GlobalRequest::StartNewThread(dettid, detpid, None, None),
21929 ),
21930 );
21931 let kill_after_request = async {
21932 while request_seen.try_read().is_none() {
21933 tokio::task::yield_now().await;
21934 }
21935 state.sched.lock().unwrap().logically_kill_thread(
21936 &dettid,
21937 &detpid,
21938 MmId::initial(detpid),
21939 );
21940 };
21941
21942 let (response, ()) = tokio::join!(request, kill_after_request);
21943 assert_eq!(response, (None, GlobalResponse::ThreadExited));
21944 assert!(!state.sched.lock().unwrap().priorities.contains_key(&dettid));
21945 }
21946
21947 #[tokio::test]
21948 async fn required_physical_thread_id_missing_is_terminal() {
21949 let config = Config {
21950 sequentialize_threads: true,
21951 ..Config::default()
21952 }
21953 .with_backend(|backend| {
21954 backend.needs_killed_thread_rpc_cancellation = true;
21955 backend.requires_thread_directed_process_signals = true;
21956 });
21957 let state = GlobalState::initialize(&config, false);
21958 let dettid = DetTid::from_raw(17);
21959 let detpid = DetPid::from_raw(17);
21960 state
21961 .sched
21962 .lock()
21963 .unwrap()
21964 .thread_tree
21965 .add_child(dettid, dettid, true);
21966 install_test_registration(&state, dettid, Ivar::new());
21967
21968 let response = state
21969 .receive_rpc(
21970 reverie::Tid::from_raw(dettid.as_raw()),
21971 (
21972 DetTime::new(&config),
21973 MmId::initial(detpid),
21974 GlobalRequest::StartNewThread(dettid, detpid, None, None),
21975 ),
21976 )
21977 .await;
21978
21979 assert_eq!(response, (None, GlobalResponse::ThreadExited));
21980 let scheduler = state.sched.lock().unwrap();
21981 assert!(!scheduler.next_turns.contains_key(&dettid));
21982 }
21983
21984 #[tokio::test]
21985 async fn tombstoned_timer_registration_cannot_mutate_scheduler_state() {
21986 let (_, state, dettid, detpid) = cancellation_test_state();
21987 install_test_registration(&state, dettid, Ivar::new());
21988 state
21989 .sched
21990 .lock()
21991 .unwrap()
21992 .logically_kill_thread(&dettid, &detpid, MmId::initial(detpid));
21993 let now = LogicalTime::from_nanos(100);
21994
21995 let alarm = state
21996 .recv_register_alarm(
21997 detpid,
21998 RpcIncarnation {
21999 dettid,
22000 mm: MmId::initial(detpid),
22001 },
22002 now,
22003 LogicalTime::from_nanos(10),
22004 LogicalTime::ZERO,
22005 SigWrapper::from(Signal::SIGALRM),
22006 )
22007 .await;
22008 assert_eq!(alarm, SchedulerRpcResult::ThreadExited);
22009
22010 let posix = state
22011 .recv_register_posix_timer(
22012 detpid,
22013 RpcIncarnation {
22014 dettid,
22015 mm: MmId::initial(detpid),
22016 },
22017 1,
22018 Some(now + LogicalTime::from_nanos(10)),
22019 LogicalTime::ZERO,
22020 super::PosixTimerNotification {
22021 sig: SigWrapper::from(Signal::SIGALRM),
22022 thread: None,
22023 value: 1,
22024 deleted: false,
22025 },
22026 )
22027 .await;
22028 assert_eq!(posix, SchedulerRpcResult::ThreadExited);
22029 assert!(state.sched.lock().unwrap().blocked.timed_waiters.is_empty());
22030 }
22031
22032 #[tokio::test]
22033 async fn pending_futex_request_woken_by_logical_kill_is_terminal() {
22034 let (config, state, dettid, detpid) = cancellation_test_state();
22035 install_test_registration(&state, dettid, Ivar::new());
22036 let request = state.receive_rpc(
22037 reverie::Tid::from_raw(dettid.as_raw()),
22038 (
22039 DetTime::new(&config),
22040 MmId::initial(dettid),
22041 GlobalRequest::FutexAction(
22042 dettid,
22043 FutexAction::WaitRequest(None, None),
22044 FutexID::private(MmId::initial(detpid), 0x1000),
22045 0,
22046 u32::MAX,
22047 false,
22048 ),
22049 ),
22050 );
22051 let kill_after_wait = async {
22052 while state.sched.lock().unwrap().blocked.futex_waiters.is_empty() {
22053 tokio::task::yield_now().await;
22054 }
22055 state.sched.lock().unwrap().logically_kill_thread(
22056 &dettid,
22057 &detpid,
22058 MmId::initial(detpid),
22059 );
22060 };
22061
22062 let (response, ()) = tokio::time::timeout(Duration::from_secs(1), async {
22063 tokio::join!(request, kill_after_wait)
22064 })
22065 .await
22066 .expect("futex teardown did not wake the blocked RPC");
22067 assert_eq!(response, (None, GlobalResponse::ThreadExited));
22068 }
22069
22070 #[tokio::test]
22071 async fn parent_continue_propagates_terminal_scheduler_cancellation() {
22072 let (config, state, parent, detpid) = cancellation_test_state();
22073 let parent_request = Ivar::new();
22074 install_test_registration(&state, parent, parent_request.clone());
22075 let child = DetTid::from_raw(18);
22076 let physical_pid = std::process::id() as i32;
22077 let physical_tid = unsafe { libc::syscall(libc::SYS_gettid) as i32 };
22078 let request = state.receive_rpc(
22079 reverie::Tid::from_raw(parent.as_raw()),
22080 (
22081 DetTime::new(&config),
22082 MmId::initial(parent),
22083 GlobalRequest::CreateChildThread(
22084 child,
22085 detpid,
22086 0,
22087 None,
22088 libc::SIGCHLD,
22089 Some((physical_pid, physical_tid)),
22090 Some(DEFAULT_PRIORITY),
22091 ),
22092 ),
22093 );
22094 let kill_after_parent_parks = async {
22095 while parent_request.try_read().is_none() {
22096 tokio::task::yield_now().await;
22097 }
22098 let mut scheduler = state.sched.lock().unwrap();
22099 let (registered_mm, registered_pid, registered_tid) = scheduler
22100 .physical_thread_identity(child)
22101 .expect("parent registration must install the child pidfd before continuing");
22102 assert_eq!(
22103 registered_mm,
22104 MmId::for_clone(MmId::initial(parent), child, false)
22105 );
22106 assert_eq!(
22107 (registered_pid, registered_tid),
22108 (physical_pid, physical_tid)
22109 );
22110 scheduler.logically_kill_thread(&parent, &detpid, MmId::initial(detpid));
22111 };
22112
22113 let (response, ()) = tokio::join!(request, kill_after_parent_parks);
22114 assert_eq!(response, (None, GlobalResponse::ThreadExited));
22115 }
22116
22117 #[test]
22118 fn unsupported_syscall_warning_is_sorted_and_aggregated() {
22119 let syscalls = BTreeSet::from([
22120 "vmsplice".to_owned(),
22121 "getppid".to_owned(),
22122 "getppid".to_owned(),
22123 ]);
22124
22125 assert_eq!(
22126 format_unsupported_syscall_warning(&syscalls).as_deref(),
22127 Some("syscalls getppid,vmsplice used but not yet supported")
22128 );
22129 assert_eq!(format_unsupported_syscall_warning(&BTreeSet::new()), None);
22130 }
22131
22132 #[tokio::test]
22133 async fn abnormal_cleanup_cancels_an_unstarted_scheduler() {
22134 let config = Config {
22135 sequentialize_threads: true,
22136 ..Config::default()
22137 };
22138 let mut state = GlobalState::initialize(&config, true);
22139 state.cancel_internal_scheduler().await;
22140 let summary_path = None;
22141 let cleanup = state.clean_up(false, &summary_path);
22142
22143 assert!(
22144 matches!(
22145 tokio::time::timeout(Duration::from_millis(100), cleanup).await,
22146 Ok(Ok(()))
22147 ),
22148 "cleanup waited for a scheduler whose guest never registered"
22149 );
22150 }
22151
22152 #[tokio::test]
22153 async fn abnormal_cleanup_cancels_a_registered_scheduler() {
22154 let config = Config {
22155 sequentialize_threads: true,
22156 ..Config::default()
22157 };
22158 let mut state = GlobalState::initialize(&config, true);
22159 let dettid = DetTid::from_raw(1);
22160 {
22161 let mut scheduler = state.sched.lock().unwrap();
22162 scheduler.priorities.insert(dettid, DEFAULT_PRIORITY);
22163 scheduler.next_turns.insert(
22164 dettid,
22165 ThreadNextTurn {
22166 dettid,
22167 child_tid_addr: 0,
22168 req: Ivar::new(),
22169 resp: Ivar::new(),
22170 protocol: Default::default(),
22171 },
22172 );
22173 scheduler.runqueue_push_back(dettid);
22174 scheduler.started_up.put(());
22175 }
22176 tokio::task::yield_now().await;
22177
22178 state.cancel_internal_scheduler().await;
22179 let summary_path = None;
22180 assert!(
22181 matches!(
22182 tokio::time::timeout(
22183 Duration::from_millis(100),
22184 state.clean_up(false, &summary_path),
22185 )
22186 .await,
22187 Ok(Ok(()))
22188 ),
22189 "cleanup waited after cancelling a registered scheduler"
22190 );
22191 }
22192
22193 #[test]
22199 fn det_inodes_are_minted_not_passed_through() {
22200 use crate::types::DetInode;
22201
22202 let mut pool = super::InodePool::new();
22203 let t = LogicalTime::from_nanos(0);
22204 let seen = super::ObservedMtime::Unobserved;
22205
22206 let host_a = crate::types::RawInode::new(33, 221_742_951);
22208 let host_b = crate::types::RawInode::new(33, 998_877_665);
22209 let (a, _) = pool.add_inode(host_a, seen, t);
22210 let (b, _) = pool.add_inode(host_b, seen, t);
22211
22212 assert_ne!(
22213 a.as_raw(),
22214 host_a.ino,
22215 "det inode must not be the host inode"
22216 );
22217 assert_ne!(
22218 b.as_raw(),
22219 host_b.ino,
22220 "det inode must not be the host inode"
22221 );
22222 assert_eq!(
22223 a,
22224 crate::consts::DET_INODE_OFFSET,
22225 "minting starts above the fixed stdio inodes"
22226 );
22227 assert_eq!(b, DetInode::mint(9001), "minting is monotonic");
22228
22229 let (a_again, _) = pool.add_inode(host_a, seen, t);
22231 assert_eq!(a, a_again, "mapping must be stable per host inode");
22232 }
22233
22234 #[test]
22241 fn a_host_reusing_a_freed_inode_does_not_renumber_the_new_file() {
22242 use super::InodeSighting::Descriptor;
22243 use super::InodeSighting::Name;
22244 use crate::types::DetInode;
22245 use crate::types::RawInode;
22246
22247 let t = LogicalTime::from_nanos(0);
22248 let seen = super::ObservedMtime::Unobserved;
22249 const EXT4: u64 = 2049;
22250 let old = RawInode::new(EXT4, 12);
22251 let fresh = RawInode::new(EXT4, 13);
22252 let steps = |new: RawInode, opened: bool, first: super::InodeSighting| {
22255 let mut pool = super::InodePool::new();
22256 let old_number = pool.add_sighted_inode(old, seen, t, Name).0;
22257 pool.retire(old);
22258 if opened {
22259 pool.forget_retired(new);
22260 }
22261 let new_number = pool.add_sighted_inode(new, seen, t, first).0;
22262 let listed = pool.add_sighted_inode(new, seen, t, Name).0;
22263 let later: Vec<DetInode> = [RawInode::new(EXT4, 40), RawInode::new(EXT4, 41)]
22264 .into_iter()
22265 .map(|raw| pool.add_sighted_inode(raw, seen, t, Name).0)
22266 .collect();
22267 (old_number, new_number, listed, later)
22268 };
22269 for (opened, first) in [(true, Descriptor), (true, Name), (false, Name)] {
22270 let reused = steps(old, opened, first);
22271 let not_reused = steps(fresh, opened, first);
22272 assert_eq!(
22273 reused, not_reused,
22274 "opened={opened} first={first:?}: reuse changed a number"
22275 );
22276 let (old_number, new_number, listed, _) = reused;
22277 assert_ne!(old_number, new_number, "the new file took the old number");
22278 assert_eq!(new_number, listed, "the new file's number is stable");
22279 }
22280 }
22281
22282 #[test]
22287 fn an_unlinked_file_keeps_its_number_through_its_descriptors() {
22288 use super::InodeSighting::Descriptor;
22289 use super::InodeSighting::Name;
22290 use crate::types::RawInode;
22291
22292 let t = LogicalTime::from_nanos(0);
22293 let seen = super::ObservedMtime::Unobserved;
22294 let raw = RawInode::new(2049, 12);
22295 let mut pool = super::InodePool::new();
22296 let (number, _) = pool.add_sighted_inode(raw, seen, t, Name);
22297 pool.retire(raw);
22298 assert_eq!(pool.add_sighted_inode(raw, seen, t, Descriptor).0, number);
22299 let written = LogicalTime::from_nanos(7_000);
22300 pool.set_mtime(raw, written);
22301 assert_eq!(
22302 pool.add_sighted_inode(raw, seen, t, Descriptor),
22303 (number, written),
22304 "a write through the descriptor reached the retired file"
22305 );
22306 let (reused, _) = pool.add_sighted_inode(raw, seen, t, Name);
22307 assert_ne!(reused, number, "a name of a retired inode is another file");
22308 assert_eq!(
22309 pool.add_sighted_inode(raw, seen, t, Descriptor).0,
22310 reused,
22311 "descriptors now reach the new file"
22312 );
22313 let other = RawInode::new(2049, 99);
22315 let (live, _) = pool.add_sighted_inode(other, seen, t, Descriptor);
22316 assert_eq!(pool.add_sighted_inode(other, seen, t, Name).0, live);
22317 }
22318
22319 #[test]
22325 fn a_pending_mtime_stays_with_its_file_after_the_last_name_goes() {
22326 use super::InodeSighting::Descriptor;
22327 use super::InodeSighting::Name;
22328 use crate::types::RawInode;
22329
22330 let t = LogicalTime::from_nanos(0);
22331 let seen = super::ObservedMtime::Unobserved;
22332 let old = RawInode::new(2049, 12);
22333 let fresh = RawInode::new(2049, 13);
22334 let written = LogicalTime::from_nanos(7_000);
22335 let next_file = |new: RawInode, opened: bool| {
22337 let mut pool = super::InodePool::new();
22338 pool.set_mtime(old, written);
22339 pool.retire(old);
22340 if opened {
22341 pool.forget_retired(new);
22342 }
22343 pool.add_sighted_inode(new, seen, t, Name)
22344 };
22345 for opened in [true, false] {
22346 assert_eq!(
22347 next_file(old, opened),
22348 next_file(fresh, opened),
22349 "opened={opened}: the reused inode inherited the removed file's mtime"
22350 );
22351 }
22352
22353 let mut pool = super::InodePool::new();
22356 pool.set_mtime(old, written);
22357 pool.retire(old);
22358 let (number, mtime) = pool.add_sighted_inode(old, seen, t, Descriptor);
22359 assert_eq!(mtime, written);
22360 let rewritten = LogicalTime::from_nanos(9_000);
22361 pool.set_mtime(old, rewritten);
22362 assert_eq!(
22363 pool.add_sighted_inode(old, seen, t, Descriptor),
22364 (number, rewritten)
22365 );
22366 let (other, _) = pool.add_sighted_inode(old, seen, t, Name);
22368 assert_ne!(other, number);
22369 }
22370
22371 #[test]
22378 fn a_stale_directory_entry_does_not_renumber_a_held_file() {
22379 use super::InodeSighting::Descriptor;
22380 use super::InodeSighting::Listed;
22381 use super::InodeSighting::Name;
22382 use crate::types::RawInode;
22383
22384 let t = LogicalTime::from_nanos(0);
22385 let seen = super::ObservedMtime::Unobserved;
22386 let raw = RawInode::new(2049, 12);
22387 let mut pool = super::InodePool::new();
22388 let (number, _) = pool.add_sighted_inode(raw, seen, t, Name);
22389 let before = pool.retirements;
22390 pool.retire(raw);
22391 assert_eq!(
22392 pool.add_sighted_inode(raw, seen, t, Listed(before)).0,
22393 number
22394 );
22395 assert_eq!(pool.add_sighted_inode(raw, seen, t, Descriptor).0, number);
22396 let after = pool.retirements;
22397 let (reused, _) = pool.add_sighted_inode(raw, seen, t, Listed(after));
22398 assert_ne!(
22399 reused, number,
22400 "a snapshot taken after the retirement shows a new file"
22401 );
22402 }
22403
22404 #[test]
22412 fn a_stale_directory_entry_keeps_its_file_after_the_inode_is_reused() {
22413 use super::InodeSighting::Listed;
22414 use super::InodeSighting::Name;
22415 use crate::types::DetInode;
22416 use crate::types::RawInode;
22417
22418 let t = LogicalTime::from_nanos(0);
22419 let seen = super::ObservedMtime::Unobserved;
22420 let f = RawInode::new(2049, 12);
22421 let fresh = RawInode::new(2049, 13);
22422 let history = |g: RawInode, numbered_first: bool| -> Vec<DetInode> {
22423 let mut pool = super::InodePool::new();
22424 let mut numbers = Vec::new();
22425 if numbered_first {
22426 numbers.push(pool.add_sighted_inode(f, seen, t, Name).0);
22427 }
22428 let snapshot = pool.retirements;
22429 pool.retire(f);
22430 pool.forget_retired(g); numbers.push(pool.add_sighted_inode(g, seen, t, Name).0);
22432 numbers.push(pool.add_sighted_inode(f, seen, t, Listed(snapshot)).0);
22433 numbers.push(pool.add_sighted_inode(f, seen, t, Listed(snapshot)).0);
22434 numbers.push(
22435 pool.add_sighted_inode(RawInode::new(2049, 40), seen, t, Name)
22436 .0,
22437 );
22438 numbers
22439 };
22440 for numbered_first in [true, false] {
22441 let reused = history(f, numbered_first);
22442 assert_eq!(
22443 reused,
22444 history(fresh, numbered_first),
22445 "numbered_first={numbered_first}: host reuse changed a number"
22446 );
22447 let cached = reused[reused.len() - 3];
22448 let g = reused[reused.len() - 4];
22449 assert_ne!(cached, g, "F's cached entry took G's number");
22450 assert_eq!(
22451 cached,
22452 reused[reused.len() - 2],
22453 "the cached entry is stable"
22454 );
22455 }
22456 }
22457
22458 #[test]
22464 fn a_write_after_the_last_name_reaches_the_snapshot_record() {
22465 use super::InodeSighting::Listed;
22466 use super::InodeSighting::Name;
22467 use crate::types::RawInode;
22468
22469 let t = LogicalTime::from_nanos(0);
22470 let seen = super::ObservedMtime::Unobserved;
22471 let f = RawInode::new(2049, 12);
22472 let fresh = RawInode::new(2049, 13);
22473 let cached = |g: RawInode| {
22474 let mut pool = super::InodePool::new();
22475 pool.set_mtime(f, LogicalTime::from_nanos(1_000));
22476 let snapshot = pool.retirements;
22477 pool.retire(f);
22478 pool.set_mtime(f, LogicalTime::from_nanos(2_000));
22479 pool.forget_retired(g);
22480 pool.add_sighted_inode(g, seen, t, Name);
22481 pool.add_sighted_inode(f, seen, t, Listed(snapshot))
22482 };
22483 assert_eq!(cached(f), cached(fresh));
22484 assert_eq!(cached(f).1, LogicalTime::from_nanos(2_000));
22485 }
22486
22487 #[test]
22494 fn a_snapshot_lookup_does_not_scan_the_inodes_history() {
22495 use super::InodeSighting::Listed;
22496 use super::InodeSighting::Name;
22497 use crate::types::RawInode;
22498
22499 const GENERATIONS: usize = 4096;
22500 let t = LogicalTime::from_nanos(0);
22501 let seen = super::ObservedMtime::Unobserved;
22502 let raw = RawInode::new(2049, 12);
22503 let mut pool = super::InodePool::new();
22504 let mut held = Vec::with_capacity(GENERATIONS);
22507 for _ in 0..GENERATIONS {
22508 pool.forget_retired(raw);
22509 let number = pool.add_sighted_inode(raw, seen, t, Name).0;
22510 held.push((pool.retirements, number));
22511 pool.retire(raw);
22512 }
22513 pool.forget_retired(raw);
22515 let current = pool.add_sighted_inode(raw, seen, t, Name).0;
22516
22517 pool.listed_probes = 0;
22518 for _ in 0..1000 {
22519 let snapshot = pool.retirements;
22520 assert_eq!(
22521 pool.add_sighted_inode(raw, seen, t, Listed(snapshot)).0,
22522 current
22523 );
22524 }
22525 assert_eq!(
22526 pool.listed_probes, 0,
22527 "a current snapshot examined history records"
22528 );
22529
22530 let bound = (GENERATIONS.ilog2() + 2) as u64;
22531 for step in (0..GENERATIONS).step_by(37) {
22532 let (snapshot, number) = held[step];
22533 pool.listed_probes = 0;
22534 assert_eq!(
22535 pool.add_sighted_inode(raw, seen, t, Listed(snapshot)).0,
22536 number,
22537 "the snapshot taken while holder {step} held the inode"
22538 );
22539 assert!(
22540 pool.listed_probes <= bound,
22541 "holder {step}: {} probes for {GENERATIONS} generations",
22542 pool.listed_probes
22543 );
22544 }
22545 }
22546
22547 #[test]
22551 fn retiring_and_forgetting_consume_no_counter_values() {
22552 use crate::types::RawInode;
22553
22554 let t = LogicalTime::from_nanos(0);
22555 let seen = super::ObservedMtime::Unobserved;
22556 let raw = RawInode::new(2049, 12);
22557 let numbers = |retire: bool| {
22558 let mut pool = super::InodePool::new();
22559 pool.add_inode(raw, seen, t);
22560 if retire {
22561 pool.retire(raw);
22562 pool.forget_retired(raw);
22563 pool.forget_retired(RawInode::new(2049, 77));
22564 }
22565 [RawInode::new(2049, 20), RawInode::new(2049, 21)]
22566 .map(|raw| pool.add_inode(raw, seen, t).0)
22567 };
22568 assert_eq!(numbers(true), numbers(false));
22569 }
22570
22571 #[test]
22579 fn a_host_dependent_new_inode_does_not_renumber_later_files() {
22580 use crate::types::DetInode;
22581 use crate::types::RawInode;
22582
22583 let t = LogicalTime::from_nanos(0);
22584 let seen = super::ObservedMtime::Unobserved;
22585 const ROOTFS: u64 = 2049;
22586 const SYSFS: u64 = 22;
22587 let later_files = |pool: &mut super::InodePool| -> Vec<DetInode> {
22588 [
22589 RawInode::new(SYSFS, 76_927),
22590 RawInode::new(SYSFS, 76_925),
22591 RawInode::new(SYSFS, 243_324),
22592 RawInode::new(ROOTFS, 1_311),
22593 RawInode::new(ROOTFS, 1_312),
22594 ]
22595 .into_iter()
22596 .map(|raw| pool.add_inode(raw, seen, t).0)
22597 .collect()
22598 };
22599
22600 let mut unchanged = super::InodePool::new();
22602 unchanged.add_inode(RawInode::new(ROOTFS, 94_136_091), seen, t);
22603 unchanged.add_inode(RawInode::new(ROOTFS, 94_136_091), seen, t);
22604 let mut replaced = super::InodePool::new();
22606 replaced.add_inode(RawInode::new(ROOTFS, 94_136_091), seen, t);
22607 replaced.add_inode(RawInode::new(ROOTFS, 94_154_661), seen, t);
22608 assert_eq!(
22609 later_files(&mut unchanged),
22610 later_files(&mut replaced),
22611 "a host-dependent new inode renumbered files minted after it"
22612 );
22613
22614 let mut linked = super::InodePool::new();
22617 linked.add_inode(RawInode::new(ROOTFS, 500), seen, t);
22618 linked.add_inode(RawInode::new(ROOTFS, 500), seen, t);
22619 let mut separate = super::InodePool::new();
22620 separate.add_inode(RawInode::new(ROOTFS, 500), seen, t);
22621 separate.add_inode(RawInode::new(ROOTFS, 501), seen, t);
22622 assert_eq!(
22623 later_files(&mut linked),
22624 later_files(&mut separate),
22625 "a host-dependent reused inode renumbered files minted after it"
22626 );
22627 }
22628
22629 #[test]
22635 fn an_mtime_update_does_not_number_files() {
22636 use crate::types::DetInode;
22637 use crate::types::RawInode;
22638
22639 let epoch = LogicalTime::from_nanos(0);
22640 let set = LogicalTime::from_nanos(5_000_000_000);
22641 let seen = super::ObservedMtime::Unobserved;
22642 let touched = RawInode::new(2049, 77);
22643 let later_files = |pool: &mut super::InodePool| -> Vec<DetInode> {
22644 (1_000..1_003)
22645 .map(|ino| pool.add_inode(RawInode::new(2049, ino), seen, epoch).0)
22646 .collect()
22647 };
22648
22649 let mut applied = super::InodePool::new();
22651 applied.set_mtime(touched, set);
22652 let (_, mtime) = applied.add_inode(touched, seen, epoch);
22653 assert_eq!(
22654 mtime, set,
22655 "a pending mtime is lost when the file is numbered"
22656 );
22657 applied.set_mtime(touched, set);
22658 let mut skipped = super::InodePool::new();
22660 skipped.add_inode(touched, seen, epoch);
22661 assert_eq!(
22662 later_files(&mut applied),
22663 later_files(&mut skipped),
22664 "a host-dependent mtime update renumbered later files"
22665 );
22666 }
22667
22668 #[tokio::test]
22673 async fn the_serial_grant_ends_at_the_next_request_and_is_not_a_background_grant() {
22674 let (_config, state, tid, _) = cancellation_test_state();
22675 let first = Ivar::new();
22676 first.put(Ok(Resources::new(tid)));
22677 install_test_registration(&state, tid, first);
22678 assert!(!state.sched.lock().unwrap().holds_serial_grant(tid));
22679
22680 let grant = |state: &GlobalState| {
22681 let (chosen, request, response) =
22682 state.sched.lock().unwrap().select_test_turn().unwrap();
22683 assert_eq!(chosen, tid);
22684 crate::scheduler::finish_selected_turn(
22685 state.sched.clone(),
22686 state.global_time.clone(),
22687 chosen,
22688 request,
22689 response,
22690 )
22691 };
22692 grant(&state).await.unwrap();
22693 assert!(state.sched.lock().unwrap().holds_serial_grant(tid));
22694
22695 let next = state.sched.lock().unwrap().next_turns[&tid].req.clone();
22697 let mut background = Resources::new(tid);
22698 background.insert(
22699 ResourceID::BlockingExternalIO(ExternalOpId::new(tid, 1)),
22700 Permission::RW,
22701 );
22702 next.put(Ok(background));
22703 assert!(!state.sched.lock().unwrap().holds_serial_grant(tid));
22704
22705 assert!(grant(&state).await.is_err());
22708 let sched = state.sched.lock().unwrap();
22709 assert!(sched.blocked.external_io_blockers.contains_key(&tid));
22710 assert!(!sched.holds_serial_grant(tid));
22711 }
22712
22713 #[tokio::test]
22716 async fn controller_brk_capture_authenticates_the_view_without_spending_the_grant() {
22717 use std::os::unix::fs::MetadataExt;
22718
22719 use crate::syscalls::memory::BrkMappingCheck;
22720
22721 let config = Config {
22722 sequentialize_threads: true,
22723 ..Config::default()
22724 };
22725 let state = GlobalState::initialize(&config, false);
22726 let tid = DetTid::from_raw(37);
22727 let mm = MmId::initial(tid);
22728 let pid = std::process::id() as i32;
22729 let physical_tid = unsafe { libc::syscall(libc::SYS_gettid) as i32 };
22730 let procfs = std::fs::metadata("/proc").unwrap();
22731 let fact = reverie::ControllerCaptureFact {
22732 physical_pid: reverie::Pid::from_raw(pid),
22733 physical_tid: reverie::Pid::from_raw(physical_tid),
22734 procfs_device: procfs.dev(),
22735 procfs_inode: procfs.ino(),
22736 };
22737 {
22738 let mut sched = state.sched.lock().unwrap();
22739 sched.thread_tree.add_child(tid, tid, true);
22740 sched.record_thread_mm(tid, mm);
22741 sched.next_turns.insert(
22742 tid,
22743 ThreadNextTurn {
22744 dettid: tid,
22745 child_tid_addr: 0,
22746 req: Ivar::new(),
22747 resp: Ivar::new(),
22748 protocol: Default::default(),
22749 },
22750 );
22751 sched
22752 .register_physical_thread(tid, mm, pid, physical_tid)
22753 .unwrap();
22754 sched.set_running_for_test(tid);
22755 }
22756 let before = state.global_time.lock().unwrap().as_nanos();
22757 let check = BrkMappingCheck::Grow {
22760 old_end: 0,
22761 new_end: u64::MAX,
22762 };
22763 let answer = state
22764 .receive_rpc(
22765 Tid::from_raw(tid.as_raw()),
22766 (
22767 DetTime::new(&config),
22768 mm,
22769 GlobalRequest::CheckBrkMappings(check, fact),
22770 ),
22771 )
22772 .await;
22773 assert_eq!(
22774 answer,
22775 (None, GlobalResponse::CheckBrkMappings(Some(false)))
22776 );
22777 assert_eq!(state.global_time.lock().unwrap().as_nanos(), before);
22778 assert!(state.sched.lock().unwrap().holds_serial_grant(tid));
22779 assert_eq!(
22780 state.capture_brk_mappings(
22781 Tid::from_raw(tid.as_raw()),
22782 mm,
22783 check,
22784 reverie::ControllerCaptureFact {
22785 procfs_inode: fact.procfs_inode + 1,
22786 ..fact
22787 },
22788 ),
22789 None,
22790 );
22791 let address = &before as *const _ as u64;
22792 assert_eq!(
22793 state.capture_brk_mappings(
22794 Tid::from_raw(tid.as_raw()),
22795 mm,
22796 BrkMappingCheck::Shrink {
22797 new_end: address,
22798 old_end: address + 1
22799 },
22800 fact,
22801 ),
22802 Some(true),
22803 );
22804 let unsequentialized = GlobalState::initialize(
22805 &Config {
22806 sequentialize_threads: false,
22807 ..config
22808 },
22809 false,
22810 );
22811 assert_eq!(
22812 unsequentialized.capture_brk_mappings(Tid::from_raw(tid.as_raw()), mm, check, fact,),
22813 None
22814 );
22815 }
22816
22817 #[tokio::test]
22821 async fn shared_open_file_control_needs_the_serial_grant() {
22822 use nix::fcntl::OFlag;
22823
22824 use crate::fd::DetFd;
22825 use crate::fd::FdType;
22826 use crate::shared_open_files::OpenFileControl;
22827 use crate::shared_open_files::OpenFileControlReply;
22828 use crate::types::OpenFileId;
22829
22830 let config = Config {
22831 sequentialize_threads: true,
22832 ..Config::default()
22833 };
22834 let state = GlobalState::initialize(&config, false);
22835 let granted = DetTid::from_raw(21);
22836 let other = DetTid::from_raw(22);
22837 let id = OpenFileId::new(granted, 0);
22838 {
22839 let mut sched = state.sched.lock().unwrap();
22840 for tid in [granted, other] {
22841 sched.thread_tree.add_child(tid, tid, true);
22842 sched.next_turns.insert(
22843 tid,
22844 ThreadNextTurn {
22845 dettid: tid,
22846 child_tid_addr: 0,
22847 req: Ivar::new(),
22848 resp: Ivar::new(),
22849 protocol: Default::default(),
22850 },
22851 );
22852 }
22853 sched.set_running_for_test(granted);
22854 }
22855 state
22856 .shared_open_files
22857 .lock()
22858 .unwrap()
22859 .insert(DetFd::new(3, OFlag::O_RDWR, FdType::Regular, id).model_for_test())
22860 .unwrap();
22861 let global_before = state.global_time.lock().unwrap().as_nanos();
22862 let take = |tid: DetTid| {
22863 state.receive_rpc(
22864 reverie::Tid::from_raw(tid.as_raw()),
22865 (
22866 DetTime::new(&config),
22867 MmId::initial(tid),
22868 GlobalRequest::SharedOpenFile(OpenFileControl::Take { id }),
22869 ),
22870 )
22871 };
22872
22873 assert!(matches!(
22874 take(granted).await,
22875 (
22876 None,
22877 GlobalResponse::SharedOpenFile(Ok(OpenFileControlReply::Taken { .. }))
22878 )
22879 ));
22880 assert!(state.shared_open_files.lock().unwrap().is_leased(id));
22881 {
22882 let global_time = state.global_time.lock().unwrap();
22883 assert_eq!(global_time.as_nanos(), global_before, "no clock accounting");
22884 assert!(!global_time.contains_thread(granted));
22885 }
22886
22887 state.complete_physical_process_exit(granted.as_raw());
22888 assert!(!state.shared_open_files.lock().unwrap().is_leased(id));
22889 assert!(!state.sched.lock().unwrap().backend_failed());
22890
22891 assert!(matches!(
22894 take(other).await,
22895 (None, GlobalResponse::SharedOpenFile(Err(_)))
22896 ));
22897 assert!(!state.shared_open_files.lock().unwrap().is_leased(id));
22898 assert!(state.sched.lock().unwrap().backend_failed());
22899 }
22900
22901 #[tokio::test]
22905 async fn sigalrm_control_is_answered_before_clock_accounting() {
22906 let config = Config {
22907 sequentialize_threads: true,
22908 ..Config::default()
22909 };
22910 let state = GlobalState::initialize(&config, false);
22911 let (handler, sender) = (DetTid::from_raw(21), DetTid::from_raw(22));
22912 {
22913 let mut sched = state.sched.lock().unwrap();
22914 for tid in [handler, sender] {
22915 sched.thread_tree.add_child(tid, tid, true);
22916 sched.next_turns.insert(
22917 tid,
22918 ThreadNextTurn {
22919 dettid: tid,
22920 child_tid_addr: 0,
22921 req: Ivar::new(),
22922 resp: Ivar::new(),
22923 protocol: Default::default(),
22924 },
22925 );
22926 }
22927 sched.set_sigalrm_handled(DetPid::from_raw(21), true);
22928 sched.set_running_for_test(sender);
22929 }
22930 let global_before = state.global_time.lock().unwrap().as_nanos();
22931 let send_to = |target: DetTid| {
22932 state.receive_rpc(
22933 reverie::Tid::from_raw(sender.as_raw()),
22934 (
22935 DetTime::new(&config),
22936 MmId::initial(sender),
22937 GlobalRequest::Sigalrm(super::SigalrmControl::SendTo(target)),
22938 ),
22939 )
22940 };
22941
22942 assert_eq!(
22943 send_to(handler).await,
22944 (None, GlobalResponse::Sigalrm(true))
22945 );
22946 assert_eq!(
22947 send_to(sender).await,
22948 (None, GlobalResponse::Sigalrm(false))
22949 );
22950 let global_time = state.global_time.lock().unwrap();
22951 assert_eq!(global_time.as_nanos(), global_before, "no clock accounting");
22952 assert!(!global_time.contains_thread(sender));
22953 }
22954
22955 #[tokio::test]
22961 async fn a_schedule_replay_reports_host_inputs_changed_since_its_recording() {
22962 use detcore_model::host_input::HostFileIdentity;
22963 use detcore_model::host_input::HostInputChange;
22964 use detcore_model::host_input::HostInputRecord;
22965
22966 let identity = |ino: u64, size: i64| HostFileIdentity {
22967 dev: 33,
22968 ino,
22969 size,
22970 mtime_sec: 1_700_000_000,
22971 mtime_nsec: 5,
22972 };
22973 let open = |path: &str, ino: u64, size: i64| HostInputRecord {
22974 path: path.to_owned(),
22975 dtid: 3,
22976 syscall: 1,
22977 identity: identity(ino, size),
22978 content: None,
22979 exec: false,
22980 kernel_object: false,
22981 };
22982 let directory = tempfile::tempdir().unwrap();
22983 let recorded = directory.path().join("recorded.json");
22984 let mut writer = crate::preemptions::PreemptionWriter::new(Some(recorded.clone()));
22985 writer.set_host_inputs(crate::host_inputs::input_set(
22986 &[
22987 open("/etc/ld.so.cache", 9, 36887),
22988 open("/lib64/libc.so.6", 10, 2_000_000),
22989 ],
22990 &BTreeSet::new(),
22991 ));
22992 writer.flush().unwrap();
22993
22994 let rerecorded = directory.path().join("rerecorded.json");
22995 let summary = directory.path().join("summary.json");
22996 let config = Config {
22997 sequentialize_threads: true,
22998 record_host_inputs: true,
22999 replay_preemptions_from: Some(recorded),
23000 record_preemptions: true,
23001 record_preemptions_to: Some(rerecorded.clone()),
23002 ..Config::default()
23003 };
23004 let state = GlobalState::initialize(&config, false);
23005 let dettid = DetTid::from_raw(3);
23006 let sender_time = DetTime::new(&config);
23007 let global_before = state.global_time.lock().unwrap().as_nanos();
23008 for (path, ino, size) in [
23009 ("/etc/ld.so.cache", 11, 36839),
23010 ("/lib64/libc.so.6", 10, 2_000_000),
23011 ("/etc/ld.so.cache", 12, 36887),
23013 ] {
23014 let response = state
23015 .receive_rpc(
23016 reverie::Tid::from_raw(dettid.as_raw()),
23017 (
23018 sender_time.clone(),
23019 MmId::initial(dettid),
23020 GlobalRequest::RecordHostInput {
23021 path: path.into(),
23022 syscall: 1,
23023 identity: identity(ino, size),
23024 source: None,
23025 regular: true,
23026 exec: false,
23027 kernel_object: false,
23028 },
23029 ),
23030 )
23031 .await;
23032 assert_eq!(response, (None, GlobalResponse::RecordHostInput(())));
23033 }
23034 assert_eq!(
23035 state.global_time.lock().unwrap().as_nanos(),
23036 global_before,
23037 "no clock accounting"
23038 );
23039
23040 state.clean_up(false, &Some(summary.clone())).await.unwrap();
23041 let summary: detcore_model::summary::RunSummary =
23042 serde_json::from_str(&std::fs::read_to_string(&summary).unwrap()).unwrap();
23043 assert_eq!(
23044 summary.host_inputs_changed,
23045 vec![HostInputChange {
23046 path: "/etc/ld.so.cache".into(),
23047 recorded: identity(9, 36887),
23048 now: identity(11, 36839),
23049 }]
23050 );
23051 let inputs = summary.host_inputs.expect("the run's host-input set");
23052 assert_eq!(
23053 inputs
23054 .files
23055 .iter()
23056 .map(|file| (file.path.as_str(), file.identity.ino))
23057 .collect::<Vec<_>>(),
23058 [("/etc/ld.so.cache", 11), ("/lib64/libc.so.6", 10)]
23059 );
23060 let rerecorded =
23061 crate::preemptions::boundary_recording::Recording::read(&rerecorded).unwrap();
23062 assert_eq!(rerecorded.host_inputs(), Some(&inputs));
23063 }
23064
23065 #[tokio::test]
23071 async fn host_input_contents_are_read_when_the_run_has_ended() {
23072 use std::path::Path;
23073 use std::path::PathBuf;
23074
23075 use detcore_model::host_input::HostFileIdentity;
23076 use detcore_model::host_input::HostInputRecord;
23077
23078 let directory = tempfile::tempdir().unwrap();
23079 let kept = directory.path().join("kept");
23080 let edited = directory.path().join("edited");
23081 let replaced = directory.path().join("replaced");
23082 for file in [&kept, &edited, &replaced] {
23083 std::fs::write(file, "input\n").unwrap();
23084 }
23085 let identity_of =
23086 |path: &Path| HostFileIdentity::from_stat(&crate::util::raw_stat(path).unwrap());
23087 let input = "7d3f9b6284c6f36e77b425cac882e8fbbcc97a4727ec20790853076d0f463453";
23089 let recorded = directory.path().join("recorded.json");
23091 let mut writer = crate::preemptions::PreemptionWriter::new(Some(recorded.clone()));
23092 writer.set_host_inputs(crate::host_inputs::input_set(
23093 &[&kept, &edited, &replaced].map(|file| HostInputRecord {
23094 path: file.to_str().unwrap().to_owned(),
23095 dtid: 3,
23096 syscall: 1,
23097 identity: identity_of(file),
23098 content: Some(input.to_owned()),
23099 exec: false,
23100 kernel_object: false,
23101 }),
23102 &BTreeSet::new(),
23103 ));
23104 writer.flush().unwrap();
23105 std::fs::write(&edited, "other\n").unwrap();
23108 let new = directory.path().join("new");
23109 std::fs::write(&new, "other\n").unwrap();
23110 std::fs::rename(&new, &replaced).unwrap();
23111
23112 let summary = directory.path().join("summary.json");
23113 let config = Config {
23114 sequentialize_threads: true,
23115 record_host_inputs: true,
23116 replay_preemptions_from: Some(recorded),
23117 ..Config::default()
23118 };
23119 let state = GlobalState::initialize(&config, false);
23120 let dettid = DetTid::from_raw(3);
23121 let sender_time = DetTime::new(&config);
23122 let opened: Vec<(PathBuf, HostFileIdentity)> = [&kept, &edited, &replaced]
23123 .map(|file| (file.clone(), identity_of(file)))
23124 .into();
23125 for (file, identity) in &opened {
23126 let response = state
23127 .receive_rpc(
23128 reverie::Tid::from_raw(dettid.as_raw()),
23129 (
23130 sender_time.clone(),
23131 MmId::initial(dettid),
23132 GlobalRequest::RecordHostInput {
23133 path: file.to_str().unwrap().to_owned(),
23134 syscall: 1,
23135 identity: *identity,
23136 source: Some(file.to_str().unwrap().to_owned()),
23137 regular: true,
23138 exec: false,
23139 kernel_object: false,
23140 },
23141 ),
23142 )
23143 .await;
23144 assert_eq!(response, (None, GlobalResponse::RecordHostInput(())));
23145 }
23146 std::fs::write(&new, "input\n").unwrap();
23149 std::fs::rename(&new, &replaced).unwrap();
23150
23151 state.clean_up(false, &Some(summary.clone())).await.unwrap();
23152 let summary: detcore_model::summary::RunSummary =
23153 serde_json::from_str(&std::fs::read_to_string(&summary).unwrap()).unwrap();
23154 let inputs = summary.host_inputs.expect("the run's host-input set");
23155 let content = |file: &Path| inputs.file(file.to_str().unwrap()).unwrap().content.clone();
23156 assert_eq!(content(&kept).as_deref(), Some(input));
23157 assert_ne!(content(&edited).as_deref(), Some(input));
23158 assert!(content(&edited).is_some());
23159 assert_eq!(content(&replaced), None);
23161 let changed: Vec<&str> = summary
23162 .host_inputs_changed
23163 .iter()
23164 .map(|change| change.path.as_str())
23165 .collect();
23166 assert_eq!(
23167 changed,
23168 [edited.to_str().unwrap(), replaced.to_str().unwrap()]
23169 );
23170 }
23171
23172 #[tokio::test]
23173 async fn captured_host_inputs_publish_origins_only_for_authenticated_snapshot_bytes() {
23174 use std::path::Path;
23175
23176 use detcore_model::host_input::HostFileIdentity;
23177 use detcore_model::host_input::HostInputRecord;
23178 use detcore_model::host_input::HostInputSnapshot;
23179
23180 for case in [
23181 "kept",
23182 "alias",
23183 "wrong-object",
23184 "no-source",
23185 "replaced",
23186 "removed",
23187 "modified",
23188 "already-changed",
23189 ] {
23190 let directory = tempfile::tempdir().unwrap();
23191 let origin_path = directory.path().join("origin");
23192 let copy = directory.path().join("copy");
23193 let other = directory.path().join("other");
23194 for path in [&origin_path, ©, &other] {
23195 std::fs::write(path, b"original").unwrap();
23196 }
23197 let alias = directory.path().join("alias");
23198 std::fs::hard_link(©, &alias).unwrap();
23199 let identity_of =
23200 |path: &Path| HostFileIdentity::from_stat(&crate::util::raw_stat(path).unwrap());
23201 let origin = identity_of(&origin_path);
23202 let physical = identity_of(©);
23203 let opened = if case == "wrong-object" {
23204 identity_of(&other)
23205 } else {
23206 physical
23207 };
23208 let guest_path = if case == "alias" {
23209 "/another-name"
23210 } else {
23211 "/etc/ld.so.cache"
23212 };
23213 let content = crate::Digest::new(b"original").to_string();
23214 let mut recorded_identity = origin;
23215 if case == "already-changed" {
23216 recorded_identity.size += 1;
23217 }
23218 let recorded = directory.path().join("recorded.json");
23219 let mut writer = crate::preemptions::PreemptionWriter::new(Some(recorded.clone()));
23220 writer.set_host_inputs(crate::host_inputs::input_set(
23221 &[HostInputRecord {
23222 path: guest_path.into(),
23223 dtid: 3,
23224 syscall: 1,
23225 identity: recorded_identity,
23226 content: None,
23229 exec: false,
23230 kernel_object: false,
23231 }],
23232 &BTreeSet::new(),
23233 ));
23234 writer.flush().unwrap();
23235 let config = Config {
23236 sequentialize_threads: true,
23237 record_host_inputs: true,
23238 replay_preemptions_from: Some(recorded),
23239 host_input_snapshots: vec![HostInputSnapshot {
23240 physical,
23241 origin,
23242 content: content.clone(),
23243 }],
23244 ..Config::default()
23245 };
23246 let state = GlobalState::initialize(&config, false);
23247 let dettid = DetTid::from_raw(3);
23248 let before = state.global_time.lock().unwrap().as_nanos();
23249 for syscall in [1, 2] {
23250 state
23251 .receive_rpc(
23252 reverie::Tid::from_raw(3),
23253 (
23254 DetTime::new(&config),
23255 MmId::initial(dettid),
23256 GlobalRequest::RecordHostInput {
23257 path: guest_path.into(),
23258 syscall,
23259 identity: opened,
23260 source: (case != "no-source").then(|| {
23261 match case {
23262 "wrong-object" => &other,
23263 "alias" => &alias,
23264 _ => ©,
23265 }
23266 .to_str()
23267 .unwrap()
23268 .to_owned()
23269 }),
23270 regular: true,
23271 exec: false,
23272 kernel_object: false,
23273 },
23274 ),
23275 )
23276 .await;
23277 }
23278 assert_eq!(state.global_time.lock().unwrap().as_nanos(), before);
23279 let at_open = state.host_inputs.lock().unwrap()[0].identity;
23280 assert_eq!(
23281 at_open,
23282 if matches!(case, "wrong-object" | "no-source") {
23283 opened
23284 } else {
23285 origin
23286 },
23287 "{case}"
23288 );
23289 assert!(
23290 state.host_inputs.lock().unwrap()[0].content.is_none(),
23291 "no live-guest read"
23292 );
23293 if case == "replaced" {
23294 std::fs::rename(&other, ©).unwrap();
23295 } else if case == "removed" {
23296 std::fs::remove_file(©).unwrap();
23297 } else if matches!(case, "modified" | "already-changed") {
23298 let mtime = std::fs::metadata(©).unwrap().modified().unwrap();
23299 std::fs::write(©, b"modified").unwrap();
23300 std::fs::File::options()
23301 .write(true)
23302 .open(©)
23303 .unwrap()
23304 .set_modified(mtime)
23305 .unwrap();
23306 assert_eq!(
23307 identity_of(©),
23308 physical,
23309 "the digest, not stat, must reject this copy"
23310 );
23311 }
23312 state.settle_host_inputs();
23313 let records = state.host_inputs.lock().unwrap();
23314 assert_eq!(records.len(), 2);
23315 let observed = &records[0];
23316 let trusted = matches!(case, "kept" | "alias");
23317 assert_eq!(
23318 observed.identity,
23319 if trusted { origin } else { opened },
23320 "{case}"
23321 );
23322 assert_eq!(records[1].identity, observed.identity, "later open: {case}");
23323 assert_eq!(records[1].content, None, "only the first open is hashed");
23324 match case {
23325 "no-source" | "replaced" | "removed" => {
23326 assert_eq!(observed.content, None, "{case}")
23327 }
23328 "modified" | "already-changed" => assert_eq!(
23329 observed.content,
23330 Some(crate::Digest::new(b"modified").to_string())
23331 ),
23332 _ => assert_eq!(
23333 observed.content.as_deref(),
23334 Some(content.as_str()),
23335 "{case}"
23336 ),
23337 }
23338 let replay = state.replay_host_inputs.as_ref().unwrap().lock().unwrap();
23339 let changes = replay.changes();
23340 assert_eq!(
23341 changes.len(),
23342 usize::from(!matches!(case, "kept" | "alias")),
23343 "{case}: {changes:?}"
23344 );
23345 if let Some(change) = changes.first() {
23346 assert_eq!(
23347 change.now,
23348 if case == "already-changed" {
23349 origin
23350 } else {
23351 opened
23352 },
23353 "{case}"
23354 );
23355 }
23356 }
23357 }
23358
23359 #[tokio::test]
23368 async fn host_input_observations_are_kept_by_the_run_and_written_at_its_end() {
23369 use detcore_model::host_input::HostFileIdentity;
23370 use detcore_model::host_input::HostInputLogEnd;
23371 use detcore_model::host_input::HostInputRecord;
23372
23373 let directory = tempfile::tempdir().unwrap();
23374 let log = directory.path().join("host-inputs");
23375 let config = Config {
23376 sequentialize_threads: true,
23377 record_host_inputs: true,
23378 host_input_log: Some(log.clone()),
23379 ..Config::default()
23380 };
23381 let state = GlobalState::initialize(&config, false);
23382 let dettid = DetTid::from_raw(17);
23383 let identity = HostFileIdentity {
23384 dev: 33,
23385 ino: 7,
23386 size: 100,
23387 mtime_sec: 1_700_000_000,
23388 mtime_nsec: 5,
23389 };
23390 let mut sender_time = DetTime::new(&config);
23391 sender_time.add_syscall();
23392 sender_time.add_syscall();
23393 let global_before = state.global_time.lock().unwrap().as_nanos();
23394 let response = state
23395 .receive_rpc(
23396 reverie::Tid::from_raw(dettid.as_raw()),
23397 (
23398 sender_time.clone(),
23399 MmId::initial(dettid),
23400 GlobalRequest::RecordHostInput {
23401 path: "/etc/ld.so.cache".into(),
23402 syscall: 12,
23403 identity,
23404 source: None,
23405 regular: true,
23406 exec: false,
23407 kernel_object: false,
23408 },
23409 ),
23410 )
23411 .await;
23412 assert_eq!(response, (None, GlobalResponse::RecordHostInput(())));
23413 for _ in 0..2 {
23414 let response = state
23415 .receive_rpc(
23416 reverie::Tid::from_raw(dettid.as_raw()),
23417 (
23418 sender_time.clone(),
23419 MmId::initial(dettid),
23420 GlobalRequest::RecordHostMutation {
23421 path: "/data/F".into(),
23422 },
23423 ),
23424 )
23425 .await;
23426 assert_eq!(response, (None, GlobalResponse::RecordHostMutation(())));
23427 }
23428 {
23429 let global_time = state.global_time.lock().unwrap();
23430 assert_eq!(global_time.as_nanos(), global_before, "no clock accounting");
23431 assert!(
23432 !global_time.contains_thread(dettid),
23433 "the sender's clock was not pushed"
23434 );
23435 }
23436 assert!(!log.exists(), "nothing is written while the run goes on");
23437
23438 state.clean_up(false, &None).await.unwrap();
23439 let text = std::fs::read_to_string(&log).unwrap();
23440 let lines: Vec<&str> = text.lines().collect();
23441 assert_eq!(lines.len(), 3, "{text}");
23442 assert_eq!(
23443 serde_json::from_str::<HostInputRecord>(lines[0]).unwrap(),
23444 HostInputRecord {
23445 path: "/etc/ld.so.cache".into(),
23446 dtid: 17,
23447 syscall: 12,
23448 identity,
23449 content: None,
23450 exec: false,
23451 kernel_object: false,
23452 }
23453 );
23454 assert_eq!(
23456 serde_json::from_str::<detcore_model::host_input::HostMutationRecord>(lines[1])
23457 .unwrap()
23458 .mutated,
23459 "/data/F"
23460 );
23461 assert_eq!(
23462 serde_json::from_str::<HostInputLogEnd>(lines[2]).unwrap(),
23463 HostInputLogEnd { records: 2 }
23464 );
23465 }
23466
23467 #[test]
23473 fn files_on_different_devices_with_one_inode_number_stay_distinct() {
23474 use super::ObservedMtime;
23475
23476 let epoch = LogicalTime::from_secs(1_798_761_600);
23477 let mut pool = super::InodePool::new();
23478 let sysfs = crate::types::RawInode::new(23, 76_927);
23480 let rootfs = crate::types::RawInode::new(33, 76_927);
23481
23482 let (on_sysfs, sysfs_mtime) = pool.add_inode(sysfs, ObservedMtime::HostSpecific, epoch);
23483 let (on_rootfs, rootfs_mtime) = pool.add_inode(
23484 rootfs,
23485 ObservedMtime::Canonical(LogicalTime::from_secs(1)),
23486 epoch,
23487 );
23488 assert_ne!(on_sysfs, on_rootfs, "different files, different det inodes");
23489 assert_eq!(sysfs_mtime, epoch);
23490 assert_eq!(rootfs_mtime, LogicalTime::from_secs(1), "its own mtime");
23491 assert_eq!(
23492 pool.add_inode(sysfs, ObservedMtime::Unobserved, epoch).0,
23493 on_sysfs
23494 );
23495 assert_eq!(
23496 pool.add_inode(rootfs, ObservedMtime::Unobserved, epoch).0,
23497 on_rootfs
23498 );
23499 }
23500
23501 #[test]
23509 fn a_rebuilt_procfs_entry_keeps_its_number() {
23510 use std::path::PathBuf;
23511
23512 use crate::procfs_inode::DirectoryInodes;
23513 use crate::procfs_inode::ProcfsDirectory;
23514 use crate::types::DetInode;
23515
23516 let t = LogicalTime::from_nanos(0);
23517 let seen = super::ObservedMtime::Unobserved;
23518 let proc_device = libc::makedev(0, 23);
23519 let names: [&[u8]; 4] = [b".", b"..", b"0", b"1"];
23520 let listings = |with_paths: bool, first: [u64; 4], second: [u64; 4]| -> Vec<DetInode> {
23524 let mut pool = super::InodePool::new();
23525 let directory = with_paths.then(|| ProcfsDirectory::new(PathBuf::from("/3/fd")));
23526 let inodes = DirectoryInodes::new(proc_device, directory);
23527 let mut numbers = Vec::new();
23528 for host in [first, second] {
23529 for (name, ino) in names.iter().zip(host) {
23530 let (raw, procfs) = inodes.entry_inode(name, ino);
23531 if let Some(procfs) = procfs {
23532 let path = pool.procfs_path_key(&procfs);
23533 pool.procfs_key(raw, path);
23534 }
23535 let sighting = super::InodeSighting::Listed(0);
23536 numbers.push(pool.add_sighted_inode(raw, seen, t, sighting).0);
23537 }
23538 }
23539 numbers
23540 };
23541 let cached = [4_100, 4_000, 4_101, 4_102];
23542 let rebuilt = [4_100, 4_000, 4_901, 4_902];
23544 assert_eq!(
23545 listings(true, cached, cached),
23546 listings(true, cached, rebuilt),
23547 "a host rebuilding procfs dentries changed guest inode numbers"
23548 );
23549 assert_ne!(
23551 listings(false, cached, cached),
23552 listings(false, cached, rebuilt)
23553 );
23554 }
23555
23556 #[test]
23560 fn procfs_aliases_share_one_number() {
23561 use std::path::Path;
23562
23563 use crate::procfs_inode::path_inode;
23564
23565 let t = LogicalTime::from_nanos(0);
23566 let seen = super::ObservedMtime::Unobserved;
23567 let device = libc::makedev(0, 23);
23568 let host = |ino| crate::types::RawInode::new(device, ino);
23569 let net = path_inode(device, Path::new("/3/net/tcp"));
23570 let task = path_inode(device, Path::new("/3/task/3/net/tcp"));
23571 let mut pool = super::InodePool::new();
23572 let mut number = |raw, path| {
23573 pool.procfs_key(raw, path);
23574 pool.add_inode(raw, seen, t).0
23575 };
23576 let listed = number(host(4_100), net);
23577 assert_eq!(number(host(4_100), task), listed, "an alias");
23578 assert_eq!(number(host(4_900), task), listed, "rebuilt, by the alias");
23579 assert_eq!(number(host(4_901), net), listed, "rebuilt again");
23580 let other = number(host(4_200), path_inode(device, Path::new("/3/net/udp")));
23581 assert_ne!(other, listed);
23582 }
23583
23584 #[test]
23588 fn a_numbered_host_inode_keeps_its_number_when_its_path_is_named() {
23589 use std::path::Path;
23590
23591 use crate::procfs_inode::path_inode;
23592
23593 let t = LogicalTime::from_nanos(0);
23594 let seen = super::ObservedMtime::Unobserved;
23595 let device = libc::makedev(0, 23);
23596 let host = |ino| crate::types::RawInode::new(device, ino);
23597 let path = path_inode(device, Path::new("/3/fd/0"));
23598 let mut pool = super::InodePool::new();
23599 pool.procfs_key(host(4_100), path);
23600 let first = pool.add_inode(host(4_100), seen, t).0;
23601 let without_path = pool.add_inode(host(4_900), seen, t).0;
23602 assert_ne!(without_path, first);
23603 pool.procfs_key(host(4_900), path);
23604 assert_eq!(pool.add_inode(host(4_900), seen, t).0, without_path);
23605 pool.procfs_key(host(4_901), path);
23606 assert_eq!(pool.add_inode(host(4_901), seen, t).0, without_path);
23607 assert_eq!(pool.add_inode(host(4_100), seen, t).0, first);
23608 }
23609
23610 #[test]
23615 fn a_write_to_a_rebuilt_procfs_entry_sets_the_mtime_of_its_number() {
23616 use std::path::Path;
23617
23618 use crate::procfs_inode::path_inode;
23619
23620 let t = LogicalTime::from_nanos(0);
23621 let written = LogicalTime::from_secs(7);
23622 let seen = super::ObservedMtime::Unobserved;
23623 let device = libc::makedev(0, 23);
23624 let host = |ino| crate::types::RawInode::new(device, ino);
23625 let path = path_inode(device, Path::new("/3/comm"));
23626 for open_first in [true, false] {
23627 let mut pool = super::InodePool::new();
23628 pool.procfs_key(host(4_100), path);
23629 let (number, mtime) = pool.add_inode(host(4_100), seen, t);
23630 assert_ne!(mtime, written);
23631 if open_first {
23632 pool.procfs_key(host(4_900), path);
23633 pool.set_mtime(host(4_900), written);
23634 } else {
23635 pool.set_mtime(host(4_900), written);
23636 pool.procfs_key(host(4_900), path);
23637 }
23638 assert_eq!(
23639 pool.add_inode(host(4_100), seen, t),
23640 (number, written),
23641 "open_first={open_first}"
23642 );
23643 assert_eq!(pool.add_inode(host(4_900), seen, t), (number, written));
23644 }
23645 }
23646
23647 #[test]
23653 fn a_reused_task_id_names_new_procfs_entries() {
23654 use std::path::Path;
23655
23656 use crate::procfs_inode::ProcfsName;
23657 use crate::types::RawInode;
23658
23659 let t = LogicalTime::from_nanos(0);
23660 let seen = super::ObservedMtime::Unobserved;
23661 let device = libc::makedev(0, 23);
23662 let host = |ino| RawInode::new(device, ino);
23663 let process = ProcfsName::new(device, Path::new("/7"));
23664 let number = |pool: &mut super::InodePool, raw: RawInode| {
23665 let path = pool.procfs_path_key(&process);
23666 pool.procfs_key(raw, path);
23667 pool.add_inode(raw, seen, t).0
23668 };
23669 let mut pool = super::InodePool::new();
23670 pool.count_incarnation(7);
23671 let first = number(&mut pool, host(4_100));
23672 assert_eq!(number(&mut pool, host(4_900)), first, "rebuilt");
23673 pool.count_incarnation(7);
23674 let second = number(&mut pool, host(5_000));
23675 assert_ne!(second, first, "a new process took the old one's number");
23676 assert_eq!(pool.add_inode(host(4_900), seen, t).0, first, "held");
23677 assert_eq!(number(&mut pool, host(5_001)), second, "rebuilt");
23678 let thread = ProcfsName::new(device, Path::new("/7/task/9/stat"));
23680 assert_eq!(thread.tasks, [7, 9]);
23681 let before = pool.procfs_path_key(&thread);
23682 pool.count_incarnation(9);
23683 assert_ne!(pool.procfs_path_key(&thread), before);
23684 }
23685
23686 #[test]
23692 fn a_reused_pid_is_a_new_incarnation_for_procfs_files() {
23693 use crate::procfs::TaskIncarnation;
23694 use crate::types::DetTid;
23695 let mut pool = super::InodePool::new();
23696 assert_eq!(pool.incarnation(7), 0, "an ID no task has held");
23697 pool.count_incarnation(7);
23698 pool.count_incarnation(9);
23699 assert_eq!((pool.incarnation(7), pool.incarnation(9)), (1, 1));
23700 let (seven, nine) = (DetTid::from_raw(7), DetTid::from_raw(9));
23701 let first = pool.procfs_task_answer(seven, Some(nine), (true, false));
23702 assert_eq!(
23703 first.incarnation(),
23704 TaskIncarnation {
23705 process: 1,
23706 thread: Some(1),
23707 }
23708 );
23709 let bound = first.incarnation();
23710 assert!(first.still_names(bound));
23711 pool.count_incarnation(7);
23713 let reused = pool.procfs_task_answer(seven, Some(nine), (true, false));
23714 assert_eq!(reused.process_incarnation, 2);
23715 assert_eq!(reused.thread_incarnation, Some(1));
23716 assert!(!reused.still_names(bound), "the file of the old pid 7");
23717 assert!(reused.still_names(reused.incarnation()));
23718 pool.count_incarnation(9);
23720 let thread_reused = pool.procfs_task_answer(seven, Some(nine), (true, false));
23721 assert!(!thread_reused.still_names(reused.incarnation()));
23722 let gone = pool.procfs_task_answer(seven, None, (false, false));
23725 assert!(!gone.still_names(gone.incarnation()));
23726 assert_eq!(gone.thread_incarnation, None);
23728 }
23729
23730 #[test]
23733 fn a_read_of_rendered_file_state_keeps_nothing() {
23734 use crate::procfs::listing::RenderedFileChange;
23735 let mut pool = super::InodePool::new();
23736 let read = pool.rendered_file_state(5, RenderedFileChange::Read);
23737 assert_eq!(read, Default::default());
23738 assert!(pool.rendered_file_states.is_empty());
23739 let written = pool.rendered_file_state(5, RenderedFileChange::Mtime(3, 4));
23740 assert_eq!(written.mtime, Some((3, 4)));
23741 let hinted = pool.rendered_file_state(5, RenderedFileChange::WriteHint(2));
23742 assert_eq!((hinted.mtime, hinted.write_hint), (Some((3, 4)), 2));
23743 assert_eq!(
23744 pool.rendered_file_state(6, RenderedFileChange::Read),
23745 Default::default()
23746 );
23747 assert_eq!(pool.rendered_file_states.len(), 1);
23748 assert_eq!(
23749 pool.rendered_file_state(5, RenderedFileChange::Read),
23750 hinted
23751 );
23752 }
23753
23754 #[test]
23760 fn a_snapshot_entry_names_the_task_that_held_its_id() {
23761 use std::path::Path;
23762
23763 use crate::procfs_inode::ProcfsName;
23764 use crate::types::RawInode;
23765
23766 let t = LogicalTime::from_nanos(0);
23767 let seen = super::ObservedMtime::Unobserved;
23768 let device = libc::makedev(0, 23);
23769 let host = |ino| RawInode::new(device, ino);
23770 let number = |pool: &mut super::InodePool, raw: RawInode, name: &ProcfsName| {
23771 let path = pool.procfs_path_key(name);
23772 pool.procfs_key(raw, path);
23773 pool.add_inode(raw, seen, t).0
23774 };
23775 let process = ProcfsName::new(device, Path::new("/7"));
23776 let mut pool = super::InodePool::new();
23777 pool.count_incarnation(7);
23778 let first = number(&mut pool, host(4_100), &process);
23779 let listed = process.clone().in_snapshot(pool.threads_counted);
23780 pool.count_incarnation(7);
23781 let second = number(&mut pool, host(5_000), &process);
23782 assert_ne!(second, first, "a new process took the old one's number");
23783 assert_eq!(
23784 number(&mut pool, host(4_900), &listed),
23785 first,
23786 "the listed process's entry, rebuilt"
23787 );
23788 let unborn = ProcfsName::new(device, Path::new("/8"));
23790 let listed = unborn.clone().in_snapshot(pool.threads_counted);
23791 let unsalted = pool.procfs_path_key(&listed);
23792 assert_eq!(unsalted, pool.procfs_path_key(&unborn));
23793 pool.count_incarnation(8);
23794 assert_eq!(pool.procfs_path_key(&listed), unsalted);
23795 assert_ne!(pool.procfs_path_key(&unborn), unsalted);
23796 }
23797
23798 #[test]
23801 fn only_exact_canonical_host_mtimes_are_kept() {
23802 use super::ObservedMtime;
23803
23804 assert_eq!(
23805 ObservedMtime::from_host_mtime(1, 0),
23806 ObservedMtime::Canonical(LogicalTime::from_secs(1))
23807 );
23808 assert_eq!(
23809 ObservedMtime::from_host_mtime(0, 0),
23810 ObservedMtime::Canonical(LogicalTime::from_secs(0))
23811 );
23812 for (secs, nanos) in [(1, 5), (0, 1), (2, 0), (-1, 0), (1_600_000_000, 0)] {
23813 assert_eq!(
23814 ObservedMtime::from_host_mtime(secs, nanos),
23815 ObservedMtime::HostSpecific,
23816 "{secs}.{nanos:09} is a real timestamp, not a canonical one"
23817 );
23818 }
23819 }
23820
23821 #[test]
23824 fn first_seen_mtime_is_resolved_by_the_first_stat() {
23825 use super::ObservedMtime;
23826
23827 let epoch = LogicalTime::from_secs(1_798_761_600);
23828 let canonical = ObservedMtime::Canonical(LogicalTime::from_secs(1));
23829 let mut pool = super::InodePool::new();
23830
23831 let (_, mtime) = pool.add_inode(
23833 crate::types::RawInode::new(1, 10),
23834 ObservedMtime::HostSpecific,
23835 epoch,
23836 );
23837 assert_eq!(mtime, epoch);
23838 let (_, mtime) = pool.add_inode(crate::types::RawInode::new(1, 11), canonical, epoch);
23840 assert_eq!(mtime, LogicalTime::from_secs(1));
23841
23842 let (_, mtime) = pool.add_inode(
23844 crate::types::RawInode::new(1, 12),
23845 ObservedMtime::Unobserved,
23846 epoch,
23847 );
23848 assert_eq!(mtime, epoch, "an unresolved mtime reads as the epoch");
23849 let (_, mtime) = pool.add_inode(crate::types::RawInode::new(1, 12), canonical, epoch);
23850 assert_eq!(mtime, LogicalTime::from_secs(1));
23851
23852 let (_, mtime) = pool.add_inode(crate::types::RawInode::new(1, 10), canonical, epoch);
23854 assert_eq!(mtime, epoch);
23855 let (_, mtime) = pool.add_inode(
23856 crate::types::RawInode::new(1, 11),
23857 ObservedMtime::HostSpecific,
23858 epoch,
23859 );
23860 assert_eq!(mtime, LogicalTime::from_secs(1));
23861 }
23862 include!("tool_global/callback_history_tests.rs");
23863}
23864
23865#[cfg(test)]
23866mod robust_exit_clock_tests {
23867 use std::sync::Mutex;
23868 use std::task::Poll;
23869
23870 use nix::sys::signal::Signal;
23871 use reverie::ExitStatus;
23872 use reverie::GlobalRPC;
23873 use reverie::GlobalTool;
23874 use reverie::Tid;
23875 use reverie::Tool;
23876
23877 use super::GlobalRequest;
23878 use super::GlobalResponse;
23879 use super::GlobalState;
23880 use crate::Detcore;
23881 use crate::ThreadState;
23882 use crate::config::Config;
23883 use crate::ivar::Ivar;
23884 use crate::resources::Permission;
23885 use crate::resources::ResourceID;
23886 use crate::resources::Resources;
23887 use crate::scheduler::DEFAULT_PRIORITY;
23888 use crate::scheduler::SkipTurn;
23889 use crate::scheduler::ThreadNextTurn;
23890 use crate::tool_local::RobustListExit;
23891 use crate::tool_local::RobustListWake;
23892 use crate::types::*;
23893
23894 #[derive(Debug, PartialEq)]
23895 struct WakeObservation {
23896 wakes: Vec<(DetTid, FutexID)>,
23897 counts: Vec<u64>,
23898 clocks: serde_json::Value,
23899 turn: u64,
23900 }
23901
23902 #[derive(Debug)]
23903 struct RpcObservation {
23904 sender: DetTid,
23905 kind: &'static str,
23906 accepted: bool,
23907 clocks: serde_json::Value,
23908 queued: Vec<DetTid>,
23909 waiters: usize,
23910 turn: u64,
23911 }
23912
23913 struct ExitRpc<'a> {
23916 state: &'a GlobalState,
23917 sender: DetTid,
23918 observations: &'a Mutex<Vec<WakeObservation>>,
23919 rpc_observations: &'a Mutex<Vec<RpcObservation>>,
23920 }
23921
23922 #[reverie::tool]
23923 impl GlobalRPC<GlobalState> for ExitRpc<'_> {
23924 async fn send_rpc(
23925 &self,
23926 request: <GlobalState as GlobalTool>::Request,
23927 ) -> <GlobalState as GlobalTool>::Response {
23928 let kind = match &request.2 {
23929 GlobalRequest::RobustListWakes(wakes) if wakes.is_empty() => "empty-wake",
23930 GlobalRequest::RobustListWakes(_) => "wake",
23931 GlobalRequest::DeregisterThread(_) => "deregister",
23932 GlobalRequest::TakeSigkillRobustList => "take-sigkill-list",
23935 _ => panic!("unexpected exit RPC: {:?}", request.2),
23936 };
23937 let wakes = match &request.2 {
23938 GlobalRequest::RobustListWakes(wakes) if !wakes.is_empty() => Some(wakes.clone()),
23939 _ => None,
23940 };
23941 let response = self
23942 .state
23943 .receive_rpc(Tid::from_raw(self.sender.as_raw()), request)
23944 .await;
23945 let clocks = serde_json::to_value(&*self.state.global_time.lock().unwrap()).unwrap();
23946 {
23947 let sched = self.state.sched.lock().unwrap();
23948 self.rpc_observations.lock().unwrap().push(RpcObservation {
23949 sender: self.sender,
23950 kind,
23951 accepted: !matches!(response.1, GlobalResponse::ThreadExited),
23952 clocks: clocks.clone(),
23953 queued: sched.run_queue.tids().copied().collect(),
23954 waiters: sched
23955 .blocked
23956 .futex_waiters
23957 .values()
23958 .map(Vec::len)
23959 .sum::<usize>(),
23960 turn: sched.turn,
23961 });
23962 }
23963 if let Some(wakes) = wakes {
23964 let GlobalResponse::RobustListWakes(counts) = &response.1 else {
23965 panic!("a complete admitted exit batch was refused: {response:?}");
23966 };
23967 let clocks =
23968 serde_json::to_value(&*self.state.global_time.lock().unwrap()).unwrap();
23969 let turn = self.state.sched.lock().unwrap().turn;
23970 self.observations.lock().unwrap().push(WakeObservation {
23971 wakes,
23972 counts: counts.clone(),
23973 clocks,
23974 turn,
23975 });
23976 }
23977 response
23978 }
23979
23980 fn config(&self) -> &Config {
23981 &self.state.cfg
23982 }
23983 }
23984
23985 #[tokio::test]
23991 async fn an_exit_publishes_pending_descriptor_exports_before_retirement() {
23992 for exported in [false, true] {
23993 let config = Config {
23994 sequentialize_threads: true,
23995 ..Config::default()
23996 };
23997 let state = GlobalState::initialize(&config, false);
23998 let reader = DetTid::from_raw(3);
23999 let sender = DetTid::from_raw(5);
24000 let tool: Detcore = Detcore::new(Tid::from_raw(sender.as_raw()), &config);
24001 let mut thread = ThreadState::new(sender, &config, ());
24002 thread.detpid = Some(sender);
24003 thread.passed_descriptors_out_unreported = exported;
24004 {
24005 let mut sched = state.sched.lock().unwrap();
24006 sched.thread_tree.add_child(reader, reader, true);
24007 sched.thread_tree.add_child(reader, sender, true);
24008 for tid in [reader, sender] {
24009 sched.register_thread_for_test(tid);
24010 sched.install_test_exec_incarnation(tid, MmId::initial(tid));
24011 }
24012 }
24013 let observations = Mutex::new(Vec::new());
24014 let rpc_observations = Mutex::new(Vec::new());
24015 let rpc = ExitRpc {
24016 state: &state,
24017 sender,
24018 observations: &observations,
24019 rpc_observations: &rpc_observations,
24020 };
24021 tool.on_exit_thread(
24022 Tid::from_raw(sender.as_raw()),
24023 &rpc,
24024 thread,
24025 ExitStatus::Signaled(Signal::SIGKILL, false),
24026 )
24027 .await
24028 .unwrap();
24029 {
24030 let sched = state.sched.lock().unwrap();
24031 assert!(!sched.next_turns.contains_key(&sender));
24032 assert!(sched.next_turns.contains_key(&reader));
24033 assert_eq!(sched.turn, 0, "exit publication must not take a turn");
24034 let rpcs = rpc_observations.lock().unwrap();
24035 let deregisters: Vec<_> =
24039 rpcs.iter().filter(|rpc| rpc.kind == "deregister").collect();
24040 assert_eq!(deregisters.len(), 1, "reuse the existing exit notification");
24041 assert!(deregisters[0].accepted);
24042 assert!(rpcs[0].accepted);
24043 }
24044 assert_reader_after_sender_exit(&state, reader, exported).await;
24045 }
24046 }
24047
24048 pub(super) async fn assert_reader_after_sender_exit(
24049 state: &GlobalState,
24050 reader: DetTid,
24051 exported: bool,
24052 ) {
24053 let mut poll = Resources::new(reader);
24054 poll.insert(ResourceID::InternalIOPolling, Permission::W);
24055 poll.polls_guest_state_only = true;
24056 poll.relies_on_guest_held_marks = true;
24057 poll.set_signal_interrupt_errno(reverie::syscalls::Errno::ERESTARTSYS);
24060 poll.signal_state_task = Some((reader.as_raw(), reader.as_raw()));
24061 {
24062 let mut sched = state.sched.lock().unwrap();
24063 sched.default_signal_state_for_test(reader);
24064 sched.queue_request_for_test(reader, poll.clone());
24065 }
24066 let first = crate::scheduler::do_a_turn_blocking(
24069 state.sched.clone(),
24070 state.global_time.clone(),
24071 &Err(SkipTurn),
24072 )
24073 .await;
24074 assert!(first.is_ok(), "the first read must get an attempt");
24075 poll.poll_attempt = 2;
24076 {
24077 let mut sched = state.sched.lock().unwrap();
24078 sched.next_turns[&reader].req.put(Ok(poll));
24079 if !sched.run_queue.contains_tid(reader) {
24080 sched.runqueue_push_back(reader);
24081 }
24082 }
24083 let mut last = first;
24087 let mut granted = false;
24088 for _ in 0..3 {
24089 last = crate::scheduler::do_a_turn_blocking(
24090 state.sched.clone(),
24091 state.global_time.clone(),
24092 &last,
24093 )
24094 .await;
24095 if last.is_ok() {
24096 granted = true;
24097 break;
24098 }
24099 }
24100 assert_eq!(
24101 granted, exported,
24102 "a host-held peer must keep the surviving reader polling; without an export it must diagnose the deadlock"
24103 );
24104 let sched = state.sched.lock().unwrap();
24105 let report = sched.terminal_deadlock_for_test();
24106 if exported {
24107 assert_eq!(report, None, "an exported peer prevents a terminal verdict");
24108 } else {
24109 let headline = if sched.blocked.futex_waiters.is_empty() {
24110 "Deadlock detected: thread(s) retrying a pipe or socket-pair call no remaining thread can satisfy, but no runnable threads left."
24111 } else {
24112 "Deadlock detected: thread(s) waiting on futex, and thread(s) retrying a pipe or socket-pair call no remaining thread can satisfy, but no runnable threads left."
24113 };
24114 assert_eq!(
24115 report.and_then(|report| report.lines().next()),
24116 Some(headline)
24117 );
24118 }
24119 }
24120
24121 struct Fixture {
24122 state: GlobalState,
24123 tool: Detcore,
24124 owners: [ThreadState<()>; 2],
24125 initial_clocks: [DetTime; 2],
24126 waiters: [DetTid; 2],
24127 peer: DetTid,
24128 futexes: [FutexID; 2],
24129 observations: Mutex<Vec<WakeObservation>>,
24130 rpc_observations: Mutex<Vec<RpcObservation>>,
24131 }
24132
24133 impl Fixture {
24134 fn new(
24135 reason: RobustListExit,
24136 equal_clocks: bool,
24137 empty_owner: Option<usize>,
24138 needs_rpc_cancellation: bool,
24139 ) -> Self {
24140 let config = Config {
24141 sequentialize_threads: true,
24142 ..Config::default().with_backend(|backend| {
24143 backend.needs_killed_thread_rpc_cancellation = needs_rpc_cancellation
24144 })
24145 };
24146 let state = GlobalState::initialize(&config, false);
24147 let parent = DetTid::from_raw(1);
24148 let leader = DetTid::from_raw(17);
24149 let worker = DetTid::from_raw(18);
24150 let waiters = [DetTid::from_raw(21), DetTid::from_raw(23)];
24151 let peer = DetTid::from_raw(25);
24152 let mm = MmId::initial(leader);
24153 let mut first = ThreadState::new(leader, &config, ());
24154 first.detpid = Some(leader);
24155 let mut second = first.clone();
24156 second.dettid = worker;
24157 let mut inherited = DetTime::new(&config);
24158 inherited.add_syscall_with_cost(1_000);
24159 let first_initial = inherited.clone_for_child();
24160 inherited.add_syscall_with_cost(370);
24161 let second_initial = inherited.clone_for_child();
24162 first.thread_logical_time = first_initial.clone();
24163 second.thread_logical_time = second_initial.clone();
24164 first
24165 .thread_logical_time
24166 .add_syscall_with_cost(if equal_clocks { 407 } else { 37 });
24167 second.thread_logical_time.add_syscall_with_cost(37);
24168 first.record_robust_list_head(Some(0x404100));
24169 second.record_robust_list_head(Some(0x404200));
24170 let object = SharedMemoryObjectId::Anonymous {
24171 origin: MmId::initial(parent),
24172 sequence: 1,
24173 };
24174 let futexes = [FutexID::shared(object, 0), FutexID::shared(object, 8)];
24175 first.stage_robust_list_wakes(
24176 reason,
24177 vec![
24178 (
24179 worker,
24180 if empty_owner == Some(1) {
24181 Vec::new()
24182 } else {
24183 vec![RobustListWake { futex: futexes[1] }]
24184 },
24185 ),
24186 (
24187 leader,
24188 if empty_owner == Some(0) {
24189 Vec::new()
24190 } else {
24191 vec![RobustListWake { futex: futexes[0] }]
24192 },
24193 ),
24194 ],
24195 );
24196 {
24197 let mut sched = state.sched.lock().unwrap();
24198 sched.thread_tree.add_child(parent, parent, true);
24199 sched.thread_tree.add_child(parent, leader, true);
24200 sched.thread_tree.add_child(leader, worker, false);
24201 for tid in [waiters[0], waiters[1], peer] {
24202 sched.thread_tree.add_child(parent, tid, true);
24203 }
24204 for tid in [leader, worker, waiters[0], waiters[1], peer] {
24205 sched.priorities.insert(tid, DEFAULT_PRIORITY);
24206 sched.next_turns.insert(
24207 tid,
24208 ThreadNextTurn {
24209 dettid: tid,
24210 child_tid_addr: 0,
24211 req: Ivar::new(),
24212 resp: Ivar::new(),
24213 protocol: Default::default(),
24214 },
24215 );
24216 sched.install_test_exec_incarnation(
24217 tid,
24218 if tid == leader || tid == worker {
24219 mm
24220 } else {
24221 MmId::initial(tid)
24222 },
24223 );
24224 }
24225 sched.runqueue_push_back(leader);
24229 sched.runqueue_push_back(worker);
24230 sched.runqueue_push_back(peer);
24231 sched.next_turns[&peer].req.put(Ok(Resources::new(peer)));
24232 for (waiter, futex) in waiters.into_iter().zip(futexes) {
24233 sched.sleep_futex_waiter(&waiter, futex, None, u32::MAX, None);
24234 }
24235 }
24236 {
24237 let mut time = state.global_time.lock().unwrap();
24238 for (tid, clock) in [(leader, &first_initial), (worker, &second_initial)] {
24239 time.update_global_time(tid, clock.as_nanos(), clock.inherited_nanos());
24240 }
24241 }
24242 let tool = Detcore::new(Tid::from_raw(leader.as_raw()), &config);
24243 Self {
24244 state,
24245 tool,
24246 owners: [first, second],
24247 initial_clocks: [first_initial, second_initial],
24248 waiters,
24249 peer,
24250 futexes,
24251 observations: Mutex::new(Vec::new()),
24252 rpc_observations: Mutex::new(Vec::new()),
24253 }
24254 }
24255
24256 fn assert_clocks(&self, completed: &[usize]) -> serde_json::Value {
24257 let time = self.state.global_time.lock().unwrap();
24258 let snapshot = serde_json::to_value(&*time).unwrap();
24259 let epoch = DetTime::new(&self.state.cfg).as_nanos();
24260 let mut expected = epoch;
24261 for (index, owner) in self.owners.iter().enumerate() {
24262 let clock = if completed.contains(&index) {
24263 &owner.thread_logical_time
24264 } else {
24265 &self.initial_clocks[index]
24266 };
24267 assert_eq!(time.threads_time(owner.dettid), clock.as_nanos());
24268 assert_eq!(
24269 snapshot["inherited_time"][owner.dettid.as_raw().to_string()],
24270 serde_json::to_value(clock.inherited_nanos()).unwrap()
24271 );
24272 expected = expected + (clock.as_nanos() - epoch - clock.inherited_nanos());
24273 }
24274 assert_eq!(
24275 time.as_nanos(),
24276 expected,
24277 "only each owner's own uninherited work contributes"
24278 );
24279 snapshot
24280 }
24281
24282 async fn exit(&self, index: usize, status: ExitStatus) {
24283 self.exit_thread(self.owners[index].clone(), status).await;
24284 }
24285
24286 async fn exit_thread(&self, thread: ThreadState<()>, status: ExitStatus) {
24287 let rpc = ExitRpc {
24288 state: &self.state,
24289 sender: thread.dettid,
24290 observations: &self.observations,
24291 rpc_observations: &self.rpc_observations,
24292 };
24293 let exit =
24294 self.tool
24295 .on_exit_thread(Tid::from_raw(rpc.sender.as_raw()), &rpc, thread, status);
24296 let mut exit = std::pin::pin!(exit);
24297 assert!(
24302 matches!(futures::poll!(exit.as_mut()), Poll::Ready(Ok(()))),
24303 "exit callback yielded before its accounting and cleanup completed"
24304 );
24305 }
24306
24307 async fn nonmember_exit(&self, raw_tid: i32) {
24308 let mut thread = self.owners[0].clone();
24309 thread.dettid = DetTid::from_raw(raw_tid);
24310 thread.thread_logical_time = DetTime::new(&self.state.cfg);
24311 let tid = thread.dettid;
24312 {
24313 let mut sched = self.state.sched.lock().unwrap();
24314 sched
24315 .thread_tree
24316 .add_child(self.owners[0].dettid, tid, false);
24317 sched.priorities.insert(tid, DEFAULT_PRIORITY);
24318 sched.next_turns.insert(
24319 tid,
24320 ThreadNextTurn {
24321 dettid: tid,
24322 child_tid_addr: 0,
24323 req: Ivar::new(),
24324 resp: Ivar::new(),
24325 protocol: Default::default(),
24326 },
24327 );
24328 sched.install_test_exec_incarnation(tid, thread.mm_id);
24329 sched.runqueue_push_back(tid);
24330 }
24331 let before = self.rpc_observations.lock().unwrap().len();
24332 self.exit_thread(thread, ExitStatus::Exited(0)).await;
24333 let observations = self.rpc_observations.lock().unwrap();
24334 assert_eq!(
24335 observations.len(),
24336 before + 1,
24337 "a nonmember sent an acknowledgement or wake RPC"
24338 );
24339 assert_eq!(observations[before].sender, tid);
24340 assert_eq!(observations[before].kind, "deregister");
24341 assert!(observations[before].accepted);
24342 }
24343 }
24344
24345 #[tokio::test]
24346 async fn robust_exit_acknowledgements_preserve_global_action_order_and_eligibility() {
24347 for order in [[0, 1], [1, 0]] {
24348 for empty_owner in [0, 1] {
24349 let f = Fixture::new(RobustListExit::ExitGroup, false, Some(empty_owner), false);
24350 f.nonmember_exit(30).await;
24351 assert!(f.observations.lock().unwrap().is_empty());
24352 let queue_before_first: Vec<_> = f
24353 .state
24354 .sched
24355 .lock()
24356 .unwrap()
24357 .run_queue
24358 .tids()
24359 .copied()
24360 .collect();
24361 let first_start = f.rpc_observations.lock().unwrap().len();
24362 f.exit(order[0], ExitStatus::Exited(0)).await;
24363 let first_clocks = f.assert_clocks(&[order[0]]);
24364 {
24365 let observations = f.rpc_observations.lock().unwrap();
24366 let actions = &observations[first_start..];
24367 assert_eq!(
24368 actions.iter().map(|a| a.kind).collect::<Vec<_>>(),
24369 ["empty-wake", "deregister"]
24370 );
24371 assert!(actions.iter().all(|a| a.accepted
24372 && a.sender == f.owners[order[0]].dettid
24373 && a.clocks == first_clocks
24374 && a.waiters == 2
24375 && a.turn == 0));
24376 assert_eq!(
24377 actions[0].queued, queue_before_first,
24378 "empty acknowledgement changed scheduler eligibility"
24379 );
24380 }
24381 f.nonmember_exit(31).await;
24382 f.assert_clocks(&[order[0]]);
24383 assert!(
24384 f.observations.lock().unwrap().is_empty(),
24385 "a nonmember completed the physical-exit barrier"
24386 );
24387 let queue_before_last: Vec<_> = f
24388 .state
24389 .sched
24390 .lock()
24391 .unwrap()
24392 .run_queue
24393 .tids()
24394 .copied()
24395 .collect();
24396 let last_start = f.rpc_observations.lock().unwrap().len();
24397 f.exit(order[1], ExitStatus::Exited(0)).await;
24398 let final_clocks = f.assert_clocks(&[0, 1]);
24399 {
24400 let observations = f.rpc_observations.lock().unwrap();
24401 let actions = &observations[last_start..];
24402 assert_eq!(
24403 actions.iter().map(|a| a.kind).collect::<Vec<_>>(),
24404 ["empty-wake", "wake", "deregister"]
24405 );
24406 assert!(actions.iter().all(|a| a.accepted
24407 && a.sender == f.owners[order[1]].dettid
24408 && a.clocks == final_clocks
24409 && a.turn == 0));
24410 assert_eq!(actions[0].waiters, 2);
24411 assert_eq!(actions[1].waiters, 1);
24412 assert_eq!(actions[0].queued, queue_before_last);
24413 assert_eq!(
24414 actions[1].queued, queue_before_last,
24415 "wake bypassed deferred admission"
24416 );
24417 }
24418 f.nonmember_exit(32).await;
24419 f.assert_clocks(&[0, 1]);
24420 assert_eq!(f.observations.lock().unwrap().len(), 1);
24421 let observations = f.rpc_observations.lock().unwrap();
24422 for owner in &f.owners {
24423 assert_eq!(
24424 observations
24425 .iter()
24426 .filter(|a| a.sender == owner.dettid && a.kind == "empty-wake")
24427 .count(),
24428 1,
24429 "each unique matching owner, including an empty-wake owner, must acknowledge before the batch clears"
24430 );
24431 }
24432 }
24433 }
24434 }
24435
24436 #[tokio::test]
24437 async fn robust_exit_callbacks_preserve_owner_clocks_across_arrival_orders() {
24438 for reason in [
24439 RobustListExit::ExitGroup,
24440 RobustListExit::Signal(libc::SIGTERM),
24441 ] {
24442 for equal_clocks in [false, true] {
24443 for empty_owner in [None, Some(0), Some(1)] {
24444 let mut results = Vec::new();
24445 for order in [[0, 1], [1, 0]] {
24446 let f = Fixture::new(reason, equal_clocks, empty_owner, false);
24447 let status = match reason {
24448 RobustListExit::ExitGroup => ExitStatus::Exited(0),
24449 RobustListExit::Signal(_) => {
24450 ExitStatus::Signaled(Signal::SIGTERM, false)
24451 }
24452 };
24453 f.exit(order[0], status).await;
24454 f.assert_clocks(&[order[0]]);
24455 assert!(f.observations.lock().unwrap().is_empty());
24456 {
24457 let sched = f.state.sched.lock().unwrap();
24458 assert_eq!(
24459 sched
24460 .blocked
24461 .futex_waiters
24462 .values()
24463 .map(Vec::len)
24464 .sum::<usize>(),
24465 2
24466 );
24467 assert_eq!(sched.turn, 0);
24468 }
24469 {
24472 let last = Err(SkipTurn);
24473 let turn = crate::scheduler::do_a_turn_blocking(
24474 f.state.sched.clone(),
24475 f.state.global_time.clone(),
24476 &last,
24477 );
24478 let mut turn = std::pin::pin!(turn);
24479 assert!(matches!(futures::poll!(turn.as_mut()), Poll::Pending));
24480 }
24481 f.exit(order[0], status).await;
24482 f.assert_clocks(&[order[0]]);
24483 assert!(
24484 f.observations.lock().unwrap().is_empty(),
24485 "duplicate physical exit released an incomplete group"
24486 );
24487 f.exit(order[1], status).await;
24488 let clocks = f.assert_clocks(&[0, 1]);
24489 {
24490 let observations = f.observations.lock().unwrap();
24491 assert_eq!(observations.len(), 1);
24492 let expected: Vec<_> = (0..2)
24493 .filter(|i| Some(*i) != empty_owner)
24494 .map(|i| (f.owners[i].dettid, f.futexes[i]))
24495 .collect();
24496 assert_eq!(observations[0].wakes, expected);
24497 assert_eq!(observations[0].counts, vec![1; expected.len()]);
24498 assert_eq!(
24499 observations[0].clocks, clocks,
24500 "all owner clocks must be accounted before wake admission"
24501 );
24502 assert_eq!(observations[0].turn, 0);
24503 }
24504 f.exit(order[1], status).await;
24505 assert_eq!(f.assert_clocks(&[0, 1]), clocks);
24506 assert_eq!(
24507 f.observations.lock().unwrap().len(),
24508 1,
24509 "repeated cleanup emitted a second batch"
24510 );
24511 {
24512 let sched = f.state.sched.lock().unwrap();
24513 assert!(!sched.run_queue.contains_tid(f.waiters[0]));
24514 assert!(!sched.run_queue.contains_tid(f.waiters[1]));
24515 }
24516 let result = crate::scheduler::do_a_turn_blocking(
24519 f.state.sched.clone(),
24520 f.state.global_time.clone(),
24521 &Err(SkipTurn),
24522 )
24523 .await;
24524 assert!(result.is_ok());
24525 let sched = f.state.sched.lock().unwrap();
24526 assert_eq!(sched.turn, 1);
24527 let queued: Vec<_> = sched.run_queue.tids().copied().collect();
24528 for (i, waiter) in f.waiters.iter().enumerate() {
24529 assert_eq!(queued.contains(waiter), Some(i) != empty_owner);
24530 }
24531 assert!(queued.contains(&f.peer));
24532 results.push((
24533 clocks,
24534 queued,
24535 std::mem::take(&mut *f.observations.lock().unwrap()),
24536 ));
24537 }
24538 assert_eq!(
24539 results[0], results[1],
24540 "callback order changed final clocks, typed wake observations or the actual scheduler drain"
24541 );
24542 }
24543 }
24544 }
24545 }
24546
24547 #[tokio::test]
24548 async fn robust_exit_callbacks_keep_rejected_or_mismatched_batches_incomplete() {
24549 for rejection in ["old-mm", "tombstone", "wrong-signal", "normal-exit"] {
24550 let f = Fixture::new(
24551 RobustListExit::Signal(libc::SIGTERM),
24552 false,
24553 None,
24554 rejection == "tombstone",
24555 );
24556 let rejected = f.owners[0].dettid;
24557 let mm = f.owners[0].mm_id;
24558 f.exit(1, ExitStatus::Signaled(Signal::SIGTERM, false))
24559 .await;
24560 let replacement_request = Ivar::new();
24561 if rejection == "old-mm" {
24562 let mut sched = f.state.sched.lock().unwrap();
24563 sched.install_test_exec_incarnation(rejected, mm.for_exec(rejected));
24564 sched.next_turns.insert(
24565 rejected,
24566 ThreadNextTurn {
24567 dettid: rejected,
24568 child_tid_addr: 0,
24569 req: replacement_request.clone(),
24570 resp: Ivar::new(),
24571 protocol: Default::default(),
24572 },
24573 );
24574 let mut replacement_time = f.owners[0].thread_logical_time.clone();
24575 replacement_time.add_syscall_with_cost(500);
24576 f.state.global_time.lock().unwrap().update_global_time(
24577 rejected,
24578 replacement_time.as_nanos(),
24579 replacement_time.inherited_nanos(),
24580 );
24581 } else if rejection == "tombstone" {
24582 let mut sched = f.state.sched.lock().unwrap();
24584 sched.logically_kill_thread(&rejected, &rejected, mm);
24585 }
24586 let before = serde_json::to_value(&*f.state.global_time.lock().unwrap()).unwrap();
24587 let status = match rejection {
24588 "wrong-signal" => ExitStatus::Signaled(Signal::SIGKILL, false),
24589 "normal-exit" => ExitStatus::Exited(0),
24590 _ => ExitStatus::Signaled(Signal::SIGTERM, false),
24591 };
24592 f.exit(0, status).await;
24593 assert!(
24594 f.observations.lock().unwrap().is_empty(),
24595 "{rejection} released a group"
24596 );
24597 if rejection == "old-mm" || rejection == "tombstone" {
24598 assert_eq!(
24599 serde_json::to_value(&*f.state.global_time.lock().unwrap()).unwrap(),
24600 before,
24601 "rejected acknowledgement changed clock state"
24602 );
24603 }
24604 let sched = f.state.sched.lock().unwrap();
24605 assert_eq!(
24606 sched
24607 .blocked
24608 .futex_waiters
24609 .values()
24610 .map(Vec::len)
24611 .sum::<usize>(),
24612 2,
24613 "rejected group lost a real waiter"
24614 );
24615 assert!(
24616 f.waiters
24617 .iter()
24618 .all(|tid| !sched.run_queue.contains_tid(*tid))
24619 );
24620 if rejection == "old-mm" {
24621 assert!(sched.rpc_incarnation_matches(rejected, mm.for_exec(rejected)));
24622 assert_eq!(
24623 sched.next_turns[&rejected].req, replacement_request,
24624 "old cleanup destroyed replacement registration"
24625 );
24626 }
24627 }
24628 }
24629
24630 #[tokio::test]
24631 #[should_panic(expected = "Attempted to update tid 17 time")]
24632 async fn robust_exit_clock_ack_still_refuses_a_backwards_owner_sample() {
24633 let f = Fixture::new(RobustListExit::ExitGroup, false, None, false);
24634 let owner = &f.owners[0];
24635 let mut later = owner.thread_logical_time.clone();
24636 later.add_syscall_with_cost(1);
24637 f.state.global_time.lock().unwrap().update_global_time(
24638 owner.dettid,
24639 later.as_nanos(),
24640 later.inherited_nanos(),
24641 );
24642 f.exit(0, ExitStatus::Exited(0)).await;
24643 }
24644
24645 #[tokio::test]
24646 async fn backend_failure_preserves_consuming_robust_exit_clock_accounting() {
24647 for order in [[0, 1], [1, 0]] {
24648 let f = Fixture::new(RobustListExit::ExitGroup, false, None, false);
24649 let selected = {
24650 let mut sched = f.state.sched.lock().unwrap();
24651 sched.next_turns.get_mut(&f.peer).unwrap().req = Ivar::new();
24652 sched.select_test_turn().unwrap()
24653 };
24654 let responses = {
24655 let sched = f.state.sched.lock().unwrap();
24656 f.waiters.map(|tid| sched.next_turns[&tid].resp.clone())
24657 };
24658 let mut turn = std::pin::pin!(crate::scheduler::finish_selected_turn(
24659 f.state.sched.clone(),
24660 f.state.global_time.clone(),
24661 selected.0,
24662 selected.1,
24663 selected.2,
24664 ));
24665 assert!(futures::poll!(turn.as_mut()).is_pending());
24666 f.state.report_backend_failure(reverie::BackendFailure {
24667 pid: Tid::from_raw(17),
24668 tid: Tid::from_raw(18),
24669 phase: "native robust cleanup control",
24670 });
24671 for index in order {
24672 f.exit(index, ExitStatus::Exited(0)).await;
24673 }
24674 assert!(matches!(futures::poll!(turn.as_mut()), Poll::Ready(Err(_))));
24675 f.assert_clocks(&[0, 1]);
24676 let observations = f.observations.lock().unwrap();
24677 assert_eq!(observations.len(), 1, "one complete batch");
24678 assert_eq!(observations[0].counts, vec![1, 1]);
24679 assert!(
24680 responses
24681 .iter()
24682 .all(|response| response.try_read().is_none())
24683 );
24684 let mut sched = f.state.sched.lock().unwrap();
24685 assert_eq!(sched.turn, 0);
24686 for owner in &f.owners {
24687 assert!(!sched.next_turns.contains_key(&owner.dettid));
24688 assert!(!sched.note_deregistration_accounted(owner.dettid));
24689 }
24690 }
24691 }
24692}
24693
24694#[cfg(test)]
24695mod replay_departure_tests {
24696 use super::*;
24697 use crate::preemptions::PreemptionRecord;
24698 use crate::preemptions::PreemptionWriter;
24699
24700 #[test]
24701 fn edited_history_rerecording_inherits_the_consumed_history_departure() {
24702 let directory = tempfile::tempdir().unwrap();
24703 let path = directory.path().join("history.json");
24704 for marker in [None, Some(0), Some(71)] {
24705 let mut writer = PreemptionWriter::new(None);
24706 if let Some(event) = marker {
24707 writer.mark_replay_departed(event);
24708 }
24709 std::fs::write(&path, writer.into_string()).unwrap();
24710 let history = HistoryReader::read(&path).unwrap();
24711 history.require_replay_mode(true).unwrap();
24712 assert_eq!(
24713 history.require_replay_mode(false).is_err(),
24714 marker.is_some()
24715 );
24716 let config = Config {
24717 replay_preemptions_from: Some(path.clone()),
24718 replay_edited_preemptions: true,
24719 record_preemptions: true,
24720 ..Config::default()
24721 };
24722 let global = GlobalState::for_tests(&config);
24723 let text = global
24724 .sched
24725 .lock()
24726 .unwrap()
24727 .preemption_writer
24728 .take()
24729 .unwrap()
24730 .into_string();
24731 let written: PreemptionRecord = serde_json::from_str(&text).unwrap();
24732 assert_eq!(written.replay_departed_at(), marker);
24733 }
24734 }
24735}