Skip to content

Commit 1109308

Browse files
committed
refactor: make TrackedChild easier to use
1 parent 79359e1 commit 1109308

File tree

2 files changed

+50
-25
lines changed

2 files changed

+50
-25
lines changed

crates/fspy/src/lib.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,38 @@ mod os_impl;
2020
mod arena;
2121
mod command;
2222

23-
use std::{env::temp_dir, ffi::OsStr, fs::create_dir, io, sync::OnceLock};
23+
use std::{env::temp_dir, ffi::OsStr, fs::create_dir, io, process::ExitStatus, sync::OnceLock};
2424

2525
pub use command::Command;
2626
pub use fspy_shared::ipc::{AccessMode, PathAccess};
2727
use futures_util::future::BoxFuture;
2828
pub use os_impl::PathAccessIterable;
2929
use os_impl::SpyInner;
30-
use tokio::process::Child;
30+
use tokio::process::{ChildStderr, ChildStdin, ChildStdout};
31+
32+
/// The result of a tracked child process upon its termination.
33+
pub struct ChildTermination {
34+
/// The exit status of the child process.
35+
pub status: ExitStatus,
36+
/// The path accesses captured from the child process.
37+
pub path_accesses: PathAccessIterable,
38+
}
3139

3240
pub struct TrackedChild {
33-
pub tokio_child: Child,
34-
/// This future lazily locks the IPC channel when it's polled.
35-
/// Do not `await` it until the child process has exited.
36-
pub accesses_future: BoxFuture<'static, io::Result<PathAccessIterable>>,
41+
/// The handle for writing to the child's standard input (stdin), if it has
42+
/// been captured.
43+
pub stdin: Option<ChildStdin>,
44+
45+
/// The handle for reading from the child's standard output (stdout), if it
46+
/// has been captured.
47+
pub stdout: Option<ChildStdout>,
48+
49+
/// The handle for reading from the child's standard error (stderr), if it
50+
/// has been captured.
51+
pub stderr: Option<ChildStderr>,
52+
53+
/// The future that resolves to exit status and path accesses when the process exits.
54+
pub wait_handle: BoxFuture<'static, io::Result<ChildTermination>>,
3755
}
3856

3957
pub struct Spy(SpyInner);

crates/fspy/src/unix/mod.rs

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use syscall_handler::SyscallHandler;
2222
use tokio::task::spawn_blocking;
2323

2424
use crate::{
25-
Command, TrackedChild,
25+
ChildTermination, Command, TrackedChild,
2626
arena::PathAccessArena,
2727
error::SpawnError,
2828
ipc::{OwnedReceiverLockGuard, SHM_CAPACITY},
@@ -131,26 +131,33 @@ pub(crate) async fn spawn_impl(mut command: Command) -> Result<TrackedChild, Spa
131131
// tokio_command.spawn blocks while executing the `pre_exec` closure.
132132
// Run it inside spawn_blocking to avoid blocking the tokio runtime, especially the supervisor loop,
133133
// which needs to accept incoming connections while `pre_exec` is connecting to it.
134-
let child = spawn_blocking(move || tokio_command.spawn())
134+
let mut child = spawn_blocking(move || tokio_command.spawn())
135135
.await
136136
.map_err(|err| SpawnError::OsSpawnError(err.into()))?
137137
.map_err(SpawnError::OsSpawnError)?;
138138

139-
let arenas_future = async move {
140-
let arenas = std::iter::once(exec_resolve_accesses);
141-
#[cfg(target_os = "linux")]
142-
let arenas =
143-
arenas.chain(supervisor.stop().await?.into_iter().map(|handler| handler.into_arena()));
144-
io::Result::Ok(arenas.collect::<Vec<_>>())
145-
};
146-
147-
let accesses_future = async move {
148-
let arenas = arenas_future.await?;
149-
// `receiver.lock()` blocks. Run it inside `spawn_blocking` to avoid blocking the tokio runtime.
150-
let ipc_receiver_lock_guard = OwnedReceiverLockGuard::lock_async(ipc_receiver).await?;
151-
Ok(PathAccessIterable { arenas, ipc_receiver_lock_guard })
152-
}
153-
.boxed();
154-
155-
Ok(TrackedChild { tokio_child: child, accesses_future })
139+
Ok(TrackedChild {
140+
stdin: child.stdin.take(),
141+
stdout: child.stdout.take(),
142+
stderr: child.stderr.take(),
143+
wait_handle: tokio::spawn(async move {
144+
let status = child.wait().await?;
145+
146+
let arenas = std::iter::once(exec_resolve_accesses);
147+
// Stop the supervisor and collect path accesses from it.
148+
#[cfg(target_os = "linux")]
149+
let arenas = arenas
150+
.chain(supervisor.stop().await?.into_iter().map(|handler| handler.into_arena()));
151+
let arenas = arenas.collect::<Vec<_>>();
152+
153+
// Lock the ipc channel after the child has exited.
154+
// We are not interested in path accesses from decendants after the main child has exited.
155+
let ipc_receiver_lock_guard = OwnedReceiverLockGuard::lock_async(ipc_receiver).await?;
156+
let path_accesses = PathAccessIterable { arenas, ipc_receiver_lock_guard };
157+
158+
io::Result::Ok(ChildTermination { status, path_accesses })
159+
})
160+
.map(|f| io::Result::Ok(f??)) // flatten JoinError and io::Result
161+
.boxed(),
162+
})
156163
}

0 commit comments

Comments
 (0)