1use std::any::Any;
14use std::any::TypeId;
15use std::cell::Cell;
16use std::cell::RefCell;
17use std::collections::BTreeMap;
18use std::ffi::CStr;
19use std::fmt;
20use std::fs::File;
21use std::future::Future;
22use std::future::poll_fn;
23use std::mem::ManuallyDrop;
24use std::os::fd::AsRawFd;
25use std::os::fd::FromRawFd;
26use std::os::fd::OwnedFd;
27use std::os::unix::fs::MetadataExt;
28use std::pin::Pin;
29use std::rc::Rc;
30use std::sync::Mutex;
31use std::sync::atomic::AtomicU64;
32use std::sync::atomic::Ordering;
33use std::task::Context;
34use std::task::Poll;
35use std::thread::ThreadId;
36use std::time::Duration;
37use std::time::Instant;
38
39use reverie_ptrace::CapturedPrefix;
40use reverie_ptrace::PtraceCallbackDiagnostic;
41use reverie_ptrace::PtraceRunFailure;
42use reverie_ptrace::PtraceTerminationHandle;
43use reverie_ptrace::ToolRunOutcome;
44use serde::Deserialize;
45use serde::Serialize;
46
47use crate::Error;
48use crate::FailureKind;
49use crate::GuestTimedOut;
50use crate::SchedulerEndedRun;
51
52const ATTEMPT_BUDGET: Duration = Duration::from_secs(2);
53static NEXT_KEY: AtomicU64 = AtomicU64::new(1);
54static UNRESOLVED: Mutex<usize> = Mutex::new(0);
57
58type ErasedFuture = Pin<Box<dyn Future<Output = Result<Box<dyn Any>, Error>>>>;
59
60struct EraseResult<F>(Pin<Box<F>>);
64impl<T: 'static, F: Future<Output = Result<T, Error>>> Future for EraseResult<F> {
65 type Output = Result<Box<dyn Any>, Error>;
66 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
67 self.get_mut()
68 .0
69 .as_mut()
70 .poll(cx)
71 .map(|result| result.map(|value| Box::new(value) as Box<dyn Any>))
72 }
73}
74
75thread_local! {
76 static OWNERS: RefCell<ManuallyDrop<BTreeMap<u64, Rc<Entry>>>> =
79 const { RefCell::new(ManuallyDrop::new(BTreeMap::new())) };
80}
81
82#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
84pub enum HermitCleanupStage {
85 Startup,
87 PtraceCleanup,
89 GlobalStateCleanup,
92 UnsupportedBackend,
94 Poisoned,
96}
97
98pub struct HermitPtraceFailure {
101 failure: PtraceRunFailure,
102 cleanup: detcore::BackendFailureCleanup,
103 callbacks: Vec<PtraceCallbackDiagnostic>,
104 timeout_during_cleanup: bool,
105}
106impl HermitPtraceFailure {
107 pub fn failure(&self) -> &PtraceRunFailure {
109 &self.failure
110 }
111 pub fn cleanup(&self) -> &detcore::BackendFailureCleanup {
113 &self.cleanup
114 }
115 pub fn callback_diagnostics(&self) -> &[PtraceCallbackDiagnostic] {
117 &self.callbacks
118 }
119 pub fn timeout_during_cleanup(&self) -> bool {
121 self.timeout_during_cleanup
122 }
123 pub(crate) fn kind(&self) -> FailureKind {
124 crate::error::classify_ptrace_primary(self.failure.primary())
125 }
126}
127impl fmt::Debug for HermitPtraceFailure {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 f.debug_struct("HermitPtraceFailure")
130 .field("failure", &self.failure)
131 .field("scheduler", &self.cleanup.scheduler)
132 .field("preemption_recording", &self.cleanup.preemption_recording)
133 .field("callbacks", &self.callbacks)
134 .field("timeout_during_cleanup", &self.timeout_during_cleanup)
135 .finish()
136 }
137}
138impl fmt::Display for HermitPtraceFailure {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 self.failure.fmt(f)
141 }
142}
143impl std::error::Error for HermitPtraceFailure {
144 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
145 Some(self.failure.primary())
146 }
147}
148
149#[derive(Clone, Debug)]
150struct Snapshot {
151 kind: FailureKind,
152 primary: String,
153 origin: Option<String>,
154 stdout: Option<Vec<u8>>,
155 stderr: Option<Vec<u8>>,
156 callbacks: Vec<PtraceCallbackDiagnostic>,
157}
158impl Snapshot {
159 fn startup() -> Self {
160 Self {
161 kind: FailureKind::Error,
162 primary: "ordinary operation is not yet complete".into(),
163 origin: None,
164 stdout: None,
165 stderr: None,
166 callbacks: Vec::new(),
167 }
168 }
169 fn failure(failure: &PtraceRunFailure, callbacks: Vec<PtraceCallbackDiagnostic>) -> Self {
170 Self {
171 kind: crate::error::classify_ptrace_primary(failure.primary()),
172 primary: failure.primary().to_string(),
173 origin: Some(format!("{:?}", failure.origin())),
174 stdout: failure.captured_prefix().map(|p| p.stdout().to_vec()),
175 stderr: failure.captured_prefix().map(|p| p.stderr().to_vec()),
176 callbacks,
177 }
178 }
179}
180
181#[derive(Clone, Debug)]
195pub struct HermitCleanupUnconfirmed {
196 key: u64,
197 origin: OriginNumbers,
198 stage: HermitCleanupStage,
199 snapshot: Snapshot,
200}
201impl HermitCleanupUnconfirmed {
202 pub fn recovery_key(&self) -> u64 {
204 self.key
205 }
206 pub fn stage(&self) -> HermitCleanupStage {
208 self.stage
209 }
210 pub fn primary_kind(&self) -> FailureKind {
212 self.snapshot.kind
213 }
214 pub fn primary_message(&self) -> &str {
216 &self.snapshot.primary
217 }
218 pub fn origin(&self) -> Option<&str> {
220 self.snapshot.origin.as_deref()
221 }
222 pub fn stdout_prefix(&self) -> Option<&[u8]> {
224 self.snapshot.stdout.as_deref()
225 }
226 pub fn stderr_prefix(&self) -> Option<&[u8]> {
228 self.snapshot.stderr.as_deref()
229 }
230 pub fn callback_diagnostics(&self) -> &[PtraceCallbackDiagnostic] {
232 &self.snapshot.callbacks
233 }
234 pub fn resume<T: Send + 'static>(&self) -> Result<T, Error> {
251 reject_nested_runtime()?;
252 let this = self.clone();
253 if let Some(resumed) =
254 crate::tracer_thread::execute_on(self.origin.thread, move || this.resume_here::<T>())
255 {
256 return resumed?;
257 }
258 self.resume_here()
259 }
260
261 fn resume_here<T: 'static>(&self) -> Result<T, Error> {
262 self.origin.check()?;
263 let entry = OWNERS
264 .try_with(|owners| owners.borrow().get(&self.key).cloned())
265 .map_err(|_| RecoveryRefusal::StorageUnavailable)?
266 .ok_or(RecoveryRefusal::UnknownKey)?;
267 if entry.result_type != TypeId::of::<T>() {
268 return Err(RecoveryRefusal::WrongResultType.into());
269 }
270 drive(entry, true)
271 }
272}
273impl fmt::Display for HermitCleanupUnconfirmed {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 write!(
276 f,
277 "Hermit cleanup remains unconfirmed ({:?}, recovery {}): {}",
278 self.stage, self.key, self.snapshot.primary
279 )
280 }
281}
282impl std::error::Error for HermitCleanupUnconfirmed {}
283
284#[derive(Debug)]
286pub enum RecoveryRefusal {
287 NestedRuntime,
289 WrongIdentity,
291 Identity(std::io::Error),
293 OriginalProcessUnavailable { ready: i32, events: i16 },
296 StorageUnavailable,
298 UnknownKey,
300 WrongResultType,
302 Reentrant,
304 Poisoned,
306 AdmissionClosed,
308 AdmissionPoisoned,
310 KeyExhausted,
312}
313impl fmt::Display for RecoveryRefusal {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 write!(f, "Hermit owner recovery refused: {self:?}")
316 }
317}
318impl std::error::Error for RecoveryRefusal {}
319
320#[derive(Clone, Copy, Debug, Eq, PartialEq)]
321struct OriginNumbers {
322 thread: ThreadId,
323 pid: u32,
324 namespace: PidNamespace,
325}
326impl OriginNumbers {
327 fn current() -> Result<(Self, Option<File>), RecoveryRefusal> {
328 let (namespace, file) = open_pid_namespace().map_err(RecoveryRefusal::Identity)?;
329 Ok((
330 Self {
331 thread: std::thread::current().id(),
332 pid: std::process::id(),
333 namespace,
334 },
335 file,
336 ))
337 }
338 fn check(&self) -> Result<(), RecoveryRefusal> {
339 let (current, _namespace) = Self::current()?;
340 if *self == current {
341 Ok(())
342 } else {
343 Err(RecoveryRefusal::WrongIdentity)
344 }
345 }
346}
347
348#[derive(Clone, Copy, Debug, Eq, PartialEq)]
350pub(crate) enum PidNamespace {
351 Inode(u64, u64),
353 Only,
357}
358
359pub(crate) fn open_pid_namespace() -> std::io::Result<(PidNamespace, Option<File>)> {
370 let fd = unsafe {
375 libc::open(
376 c"/proc/self/ns".as_ptr(),
377 libc::O_PATH | libc::O_DIRECTORY | libc::O_CLOEXEC,
378 )
379 };
380 if fd < 0 {
381 return Err(std::io::Error::last_os_error());
382 }
383 let directory = unsafe { File::from_raw_fd(fd) };
385 match open_entry(&directory, c"pid") {
386 Ok(file) => {
387 let meta = file.metadata()?;
388 Ok((PidNamespace::Inode(meta.dev(), meta.ino()), Some(file)))
389 }
390 Err(error)
391 if error.kind() == std::io::ErrorKind::NotFound
392 && is_procfs(&directory)
393 && has_entry(&directory, c"mnt") =>
394 {
395 Ok((PidNamespace::Only, None))
396 }
397 Err(error) => Err(error),
398 }
399}
400
401fn open_entry(directory: &File, name: &CStr) -> std::io::Result<File> {
403 let fd = unsafe {
405 libc::openat(
406 directory.as_raw_fd(),
407 name.as_ptr(),
408 libc::O_RDONLY | libc::O_CLOEXEC,
409 )
410 };
411 if fd < 0 {
412 return Err(std::io::Error::last_os_error());
413 }
414 Ok(unsafe { File::from_raw_fd(fd) })
416}
417
418fn is_procfs(directory: &File) -> bool {
420 let mut fs: libc::statfs = unsafe { std::mem::zeroed() };
423 let found = unsafe { libc::fstatfs(directory.as_raw_fd(), &mut fs) } == 0;
424 found && fs.f_type == libc::PROC_SUPER_MAGIC
425}
426
427fn has_entry(directory: &File, name: &CStr) -> bool {
429 let mut stat: libc::stat = unsafe { std::mem::zeroed() };
432 unsafe {
433 libc::fstatat(
434 directory.as_raw_fd(),
435 name.as_ptr(),
436 &mut stat,
437 libc::AT_SYMLINK_NOFOLLOW,
438 ) == 0
439 }
440}
441
442struct OriginalProcess(OwnedFd);
447impl OriginalProcess {
448 fn capture(pid: u32) -> Result<Self, RecoveryRefusal> {
449 let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid as libc::pid_t, 0) };
450 if fd < 0 {
451 return Err(RecoveryRefusal::Identity(std::io::Error::last_os_error()));
452 }
453 Ok(Self(unsafe { OwnedFd::from_raw_fd(fd as i32) }))
454 }
455 fn check(&self) -> Result<(), RecoveryRefusal> {
456 Self::check_fd(self.0.as_raw_fd())
457 }
458 fn check_fd(fd: i32) -> Result<(), RecoveryRefusal> {
459 if fd < 0 {
461 return Err(RecoveryRefusal::Identity(
462 std::io::Error::from_raw_os_error(libc::EBADF),
463 ));
464 }
465 let mut descriptor = libc::pollfd {
466 fd,
467 events: libc::POLLIN,
468 revents: 0,
469 };
470 let ready = unsafe { libc::poll(&mut descriptor, 1, 0) };
471 if ready < 0 {
472 return Err(RecoveryRefusal::Identity(std::io::Error::last_os_error()));
473 }
474 if ready != 0 || descriptor.revents != 0 {
475 return Err(RecoveryRefusal::OriginalProcessUnavailable {
476 ready,
477 events: descriptor.revents,
478 });
479 }
480 Ok(())
481 }
482}
483
484pub(crate) trait TerminalOutcomeResult: Sized {
489 fn terminal_outcome(
490 outcome: detcore::TerminalOutcome,
491 captured: Option<&CapturedPrefix>,
492 ) -> Option<Self>;
493}
494impl TerminalOutcomeResult for reverie::ExitStatus {
495 fn terminal_outcome(
496 outcome: detcore::TerminalOutcome,
497 _: Option<&CapturedPrefix>,
498 ) -> Option<Self> {
499 Some(reverie::ExitStatus::Exited(outcome.exit_status()))
500 }
501}
502impl TerminalOutcomeResult for reverie::process::Output {
503 fn terminal_outcome(
504 outcome: detcore::TerminalOutcome,
505 captured: Option<&CapturedPrefix>,
506 ) -> Option<Self> {
507 let captured = captured?;
508 Some(reverie::process::Output {
509 status: reverie::ExitStatus::Exited(outcome.exit_status()),
510 stdout: captured.stdout().to_vec(),
511 stderr: captured.stderr().to_vec(),
512 })
513 }
514}
515
516pub(crate) struct Control {
517 termination: RefCell<Option<PtraceTerminationHandle>>,
518 timeout: Option<(Instant, Duration)>,
519 expired: Cell<bool>,
520 received_failure: Cell<bool>,
521 survivors: RefCell<Option<String>>,
524 root: Cell<Option<i32>>,
528 cancellation_deadline: Cell<Option<Instant>>,
529 global_cleanup_deadline: Cell<Option<Instant>>,
530 stage: Cell<HermitCleanupStage>,
531 snapshot: RefCell<Snapshot>,
532 checkpoint: Cell<bool>,
533 epoch: Cell<u64>,
534}
535impl Control {
536 fn new(timeout: Option<Duration>) -> Self {
537 Self {
538 termination: RefCell::new(None),
539 timeout: timeout.map(|d| (Instant::now() + d, d)),
540 expired: Cell::new(false),
541 received_failure: Cell::new(false),
542 survivors: RefCell::new(None),
543 root: Cell::new(None),
544 cancellation_deadline: Cell::new(None),
545 global_cleanup_deadline: Cell::new(None),
546 stage: Cell::new(HermitCleanupStage::Startup),
547 snapshot: RefCell::new(Snapshot::startup()),
548 checkpoint: Cell::new(false),
549 epoch: Cell::new(0),
550 }
551 }
552 fn register(&self, handle: Option<PtraceTerminationHandle>) {
553 *self.termination.borrow_mut() = handle;
554 self.stage.set(HermitCleanupStage::PtraceCleanup);
555 if self.expired.get() {
556 self.publish_timeout();
557 }
558 }
559 fn unwind_terminal_outcomes(&self) -> Option<detcore::TerminalOutcomeUnwindGuard> {
567 let handle = self.termination.borrow().clone()?;
568 Some(detcore::TerminalOutcomeUnwindGuard::install(Box::new(
569 move |outcome| {
570 handle.terminate(reverie::Error::Tool(Error::new(SchedulerEndedRun::new(
571 outcome, None,
572 ))))
573 },
574 )))
575 }
576 fn publish_timeout(&self) {
577 if let (Some(handle), Some((_, limit))) = (self.termination.borrow().as_ref(), self.timeout)
578 {
579 handle.terminate(reverie::Error::Tool(Error::new(self.timed_out(limit))));
581 }
582 }
583 fn expire_if_due(&self, now: Instant) {
587 if !self.expired.get() && self.timeout.is_some_and(|(at, _)| now >= at) {
588 self.expired.set(true);
589 *self.survivors.borrow_mut() =
590 crate::timeout_survivors::traced_processes_now(self.root.get());
591 if !self.received_failure.get() {
592 let mut snapshot = self.snapshot.borrow_mut();
593 snapshot.kind = FailureKind::RunTimeout;
594 snapshot.primary = self.timed_out(self.timeout.unwrap().1).to_string();
595 }
596 self.publish_timeout();
597 self.cancellation_deadline
598 .set(Some(Instant::now() + ATTEMPT_BUDGET));
599 }
600 }
601 fn timed_out(&self, limit: Duration) -> GuestTimedOut {
603 GuestTimedOut {
604 limit,
605 survivors: self.survivors.borrow().clone(),
606 }
607 }
608 async fn yield_checkpoint(&self) {
609 let epoch = self.epoch.get();
610 self.checkpoint.set(true);
611 poll_fn(|_| {
612 if self.epoch.get() != epoch {
613 Poll::Ready(())
614 } else {
615 Poll::Pending
616 }
617 })
618 .await;
619 }
620 fn global_failure(&self, failure: &PtraceRunFailure, callbacks: Vec<PtraceCallbackDiagnostic>) {
621 self.termination.borrow_mut().take();
622 self.received_failure.set(true);
623 *self.snapshot.borrow_mut() = Snapshot::failure(failure, callbacks);
624 self.stage.set(HermitCleanupStage::GlobalStateCleanup);
625 self.global_cleanup_deadline
626 .set(Some(Instant::now() + ATTEMPT_BUDGET));
627 }
628}
629
630struct Driver {
631 runtime: tokio::runtime::Runtime,
632 future: ErasedFuture,
633 control: Rc<Control>,
634}
635struct Entry {
636 key: u64,
637 origin: OriginNumbers,
638 _namespace: Option<File>,
639 original_process: OriginalProcess,
640 result_type: TypeId,
641 driver: RefCell<ManuallyDrop<Driver>>,
642 in_flight: Cell<bool>,
643 poisoned: Cell<bool>,
644 quarantined: Cell<bool>,
645}
646impl Entry {
647 fn check_origin(&self) -> Result<(), RecoveryRefusal> {
648 self.origin.check()?;
649 self.original_process.check()
650 }
651}
652
653struct PollGuard<'a>(&'a Cell<bool>);
654impl Drop for PollGuard<'_> {
655 fn drop(&mut self) {
656 self.0.set(false);
657 }
658}
659fn reject_nested_runtime() -> Result<(), RecoveryRefusal> {
660 if tokio::runtime::Handle::try_current().is_ok() {
661 Err(RecoveryRefusal::NestedRuntime)
662 } else {
663 Ok(())
664 }
665}
666
667pub(crate) fn run_on_tracer_thread<T, F, Make>(
672 timeout: Option<Duration>,
673 make: Make,
674) -> Result<T, Error>
675where
676 T: Send + 'static,
677 F: Future<Output = Result<T, Error>> + 'static,
678 Make: FnOnce(Rc<Control>) -> F + Send + 'static,
679{
680 reject_nested_runtime()?;
681 crate::tracer_thread::execute(move || run(timeout, make))?
682}
683
684pub(crate) fn run<T, F, Make>(timeout: Option<Duration>, make: Make) -> Result<T, Error>
687where
688 T: 'static,
689 F: Future<Output = Result<T, Error>> + 'static,
690 Make: FnOnce(Rc<Control>) -> F,
691{
692 reject_nested_runtime()?;
693 let (origin, namespace) = OriginNumbers::current()?;
694 let original_process = OriginalProcess::capture(origin.pid)?;
695 let key = NEXT_KEY
696 .try_update(Ordering::Relaxed, Ordering::Relaxed, |n| n.checked_add(1))
697 .map_err(|_| RecoveryRefusal::KeyExhausted)?;
698 let control = Rc::new(Control::new(timeout));
699 let runtime = tokio::runtime::Builder::new_current_thread()
700 .enable_all()
701 .build()?;
702 let admission = UNRESOLVED
703 .lock()
704 .map_err(|_| RecoveryRefusal::AdmissionPoisoned)?;
705 if *admission != 0 {
706 return Err(RecoveryRefusal::AdmissionClosed.into());
707 }
708 drop(admission);
710 let future = make(control.clone());
711 let future: ErasedFuture = Box::pin(EraseResult(Box::pin(future)));
712 let entry = Rc::new(Entry {
713 key,
714 origin,
715 _namespace: namespace,
716 original_process,
717 result_type: TypeId::of::<T>(),
718 driver: RefCell::new(ManuallyDrop::new(Driver {
719 runtime,
720 future,
721 control,
722 })),
723 in_flight: Cell::new(false),
724 poisoned: Cell::new(false),
725 quarantined: Cell::new(false),
726 });
727 OWNERS
728 .try_with(|owners| {
729 owners.borrow_mut().insert(key, entry.clone());
730 })
731 .map_err(|_| RecoveryRefusal::StorageUnavailable)?;
732 drive(entry, false)
733}
734
735fn drive<T: 'static>(entry: Rc<Entry>, recovery: bool) -> Result<T, Error> {
736 entry.check_origin()?;
737 if entry.poisoned.get() {
738 return Err(RecoveryRefusal::Poisoned.into());
739 }
740 if entry.in_flight.replace(true) {
741 return Err(RecoveryRefusal::Reentrant.into());
742 }
743 let _poll_guard = PollGuard(&entry.in_flight);
744 let attempt_deadline = recovery.then(|| Instant::now() + ATTEMPT_BUDGET);
745 let polled = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
746 let mut stored = entry.driver.borrow_mut();
747 let Driver {
748 runtime,
749 future,
750 control,
751 } = &mut **stored;
752 if recovery {
753 control.checkpoint.set(false);
754 control.epoch.set(control.epoch.get().wrapping_add(1));
755 }
756 runtime.block_on(async {
757 let mut timer: Option<Pin<Box<tokio::time::Sleep>>> = None;
758 poll_fn(|cx| {
759 control.expire_if_due(Instant::now());
760 if !recovery
761 && control.expired.get()
762 && control.stage.get() == HermitCleanupStage::Startup
763 {
764 return Poll::Ready(None);
765 }
766 let polled = future.as_mut().poll(cx);
767 let now = Instant::now();
775 control.expire_if_due(now);
776 if let Poll::Ready(result) = polled {
777 let result = match (result, control.timeout) {
778 (Ok(_), Some((_, limit))) if control.expired.get() => {
779 Err(Error::new(control.timed_out(limit)))
780 }
781 (result, _) => result,
782 };
783 return Poll::Ready(Some(result));
784 }
785 if control.checkpoint.get() {
786 return Poll::Ready(None);
787 }
788 let deadline = if recovery {
789 attempt_deadline
790 } else {
791 [
792 control
793 .timeout
794 .filter(|_| !control.expired.get())
795 .map(|(at, _)| at),
796 control.cancellation_deadline.get(),
797 control.global_cleanup_deadline.get(),
798 ]
799 .into_iter()
800 .flatten()
801 .min()
802 };
803 if let Some(deadline) = deadline {
804 if now >= deadline {
805 return Poll::Ready(None);
806 }
807 let at = tokio::time::Instant::from_std(deadline);
808 match timer.as_mut() {
809 Some(timer) => timer.as_mut().reset(at),
810 None => timer = Some(Box::pin(tokio::time::sleep_until(at))),
811 }
812 if timer.as_mut().unwrap().as_mut().poll(cx).is_ready() {
813 cx.waker().wake_by_ref();
814 }
815 }
816 Poll::Pending
817 })
818 .await
819 })
820 }));
821 match polled {
822 Ok(Some(result)) => {
823 OWNERS.with(|owners| {
824 owners.borrow_mut().remove(&entry.key);
825 });
826 if entry.quarantined.replace(false) {
827 let mut unresolved = UNRESOLVED
828 .lock()
829 .unwrap_or_else(|poison| poison.into_inner());
830 *unresolved -= 1;
831 }
832 let driver = unsafe { ManuallyDrop::take(&mut *entry.driver.borrow_mut()) };
835 drop(driver);
836 result?
837 .downcast::<T>()
838 .map(|value| *value)
839 .map_err(|_| RecoveryRefusal::WrongResultType.into())
840 }
841 outcome => {
842 if !entry.quarantined.replace(true) {
843 let mut unresolved = UNRESOLVED
844 .lock()
845 .unwrap_or_else(|poison| poison.into_inner());
846 *unresolved = unresolved.saturating_add(1);
847 }
848 if outcome.is_err() {
849 entry.poisoned.set(true);
850 }
851 let stored = entry.driver.borrow();
852 let control = &stored.control;
853 if entry.poisoned.get() {
854 control.stage.set(HermitCleanupStage::Poisoned);
855 if !control.received_failure.get() && !control.expired.get() {
856 let mut snapshot = control.snapshot.borrow_mut();
857 snapshot.kind = FailureKind::Panic;
858 snapshot.primary =
859 "ordinary operation panicked while polling; owner is poisoned".into();
860 }
861 }
862 Err(Error::new(HermitCleanupUnconfirmed {
863 key: entry.key,
864 origin: entry.origin,
865 stage: control.stage.get(),
866 snapshot: control.snapshot.borrow().clone(),
867 }))
868 }
869 }
870}
871
872pub(crate) async fn wait(
878 tracer: reverie_ptrace::Tracer<detcore::GlobalState>,
879 control: Rc<Control>,
880) -> Result<(reverie::ExitStatus, detcore::GlobalState), Error> {
881 control.root.set(Some(tracer.guest_pid().as_raw()));
882 control.register(tracer.termination_handle());
883 let _unwind = control.unwind_terminal_outcomes();
884 consume(tracer.wait_completion().await, control).await
885}
886pub(crate) async fn wait_with_output(
889 tracer: reverie_ptrace::Tracer<detcore::GlobalState>,
890 control: Rc<Control>,
891) -> Result<(reverie::process::Output, detcore::GlobalState), Error> {
892 control.root.set(Some(tracer.guest_pid().as_raw()));
893 control.register(tracer.termination_handle());
894 let _unwind = control.unwind_terminal_outcomes();
895 consume(tracer.wait_with_output_completion().await, control).await
896}
897pub(crate) fn clean_terminal_outcome(
904 primary: &reverie::Error,
905 secondaries: usize,
906) -> Option<detcore::TerminalOutcome> {
907 match primary {
908 reverie::Error::Tool(inner) if secondaries == 0 => inner
909 .downcast_ref::<SchedulerEndedRun>()
910 .map(|ended| ended.outcome),
911 _ => None,
912 }
913}
914pub(crate) fn completed_terminal_outcome<R: TerminalOutcomeResult>(
921 primary: &reverie::Error,
922 secondaries: usize,
923 captured: Option<&CapturedPrefix>,
924) -> Option<R> {
925 let outcome = clean_terminal_outcome(primary, secondaries)?;
926 R::terminal_outcome(outcome, captured)
927}
928async fn consume<R: TerminalOutcomeResult>(
929 mut outcome: ToolRunOutcome<detcore::GlobalState, R>,
930 control: Rc<Control>,
931) -> Result<(R, detcore::GlobalState), Error> {
932 loop {
933 match outcome {
934 ToolRunOutcome::Complete(completion) => {
935 let callbacks = completion.callback_diagnostics().to_vec();
936 control.termination.borrow_mut().take();
937 return match completion.result {
938 Ok(result) => Ok((result, completion.global_state)),
939 Err(failure)
940 if let Some(result) = completed_terminal_outcome::<R>(
941 failure.primary(),
942 failure.secondary().len(),
943 failure.captured_prefix(),
944 ) =>
945 {
946 Ok((result, completion.global_state))
949 }
950 Err(failure) => {
951 control.global_failure(&failure, callbacks.clone());
952 let cleanup = completion
953 .global_state
954 .clean_up_after_backend_failure()
955 .await;
956 Err(HermitPtraceFailure {
957 failure,
958 cleanup,
959 callbacks,
960 timeout_during_cleanup: control.expired.get(),
961 }
962 .into())
963 }
964 };
965 }
966 ToolRunOutcome::CleanupPending(pending) => {
967 control.received_failure.set(true);
968 *control.snapshot.borrow_mut() =
969 Snapshot::failure(pending.failure(), pending.callback_diagnostics());
970 control.stage.set(HermitCleanupStage::PtraceCleanup);
971 control.yield_checkpoint().await;
972 outcome = pending.resume_cleanup().await;
973 }
974 ToolRunOutcome::UnsupportedBackend(tracer) => {
975 control.stage.set(HermitCleanupStage::UnsupportedBackend);
976 control.snapshot.borrow_mut().primary =
977 "configured tracer does not support ordinary owned completion".into();
978 loop {
981 std::hint::black_box(&tracer);
982 control.yield_checkpoint().await;
983 }
984 }
985 }
986 }
987}
988
989#[cfg(test)]
990#[path = "ptrace_completion_tests.rs"]
991mod tests;