Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"crates/subc-jsonc",
"crates/subc-cgroup",
"crates/subc-uptime",
"crates/subc-jobobject",
"crates/subc-core",
"crates/subc-daemon",
"crates/subc-daemon/tests/consumer",
Expand Down
45 changes: 45 additions & 0 deletions crates/subc-core/src/bin/fake-aft-stub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ const FAKE_AFT_ORPHAN_WRITER_MODE_ENV: &str = "FAKE_AFT_ORPHAN_WRITER_MODE";
/// cannot be asked what it did with a signal, and what it does with a signal is
/// the other half of what these tests need to observe.
const FAKE_AFT_NEVER_CONNECT_ENV: &str = "FAKE_AFT_NEVER_CONNECT";
/// Presence marks a GRANDCHILD: park forever, touch nothing else.
///
/// The child→grandchild shape a teardown test needs, because a supervision test
/// that only ever observes the direct child cannot tell a reaped tree from a
/// leaked helper. Checked before every other arm, since the grandchild must not
/// dial subc, exit on its own, or register a signal handler.
const FAKE_AFT_GRANDCHILD_MODE_ENV: &str = "FAKE_AFT_GRANDCHILD_MODE";
/// Where a parent stub records the pid of the grandchild it spawned.
///
/// The PARENT writes this, from the `Child` handle it already holds, rather than
/// the grandchild reporting itself: a self-report would need the grandchild to
/// reach a point where it can write, which is a race against the teardown the
/// test is about to perform.
const FAKE_AFT_GRANDCHILD_PID_FILE_ENV: &str = "FAKE_AFT_GRANDCHILD_PID_FILE";
/// Where to write a marker file when SIGTERM arrives, just before exiting 0.
///
/// The marker is the witness that the supervisor ASKED before it forced: the
Expand Down Expand Up @@ -242,6 +256,12 @@ async fn main() -> Result<(), StubError> {
// never dialled subc either. Checking first also means a connect/HELLO
// failure can never land its own noise in the very stderr ring this knob
// is configured to control.
if env_flag(FAKE_AFT_GRANDCHILD_MODE_ENV) {
// A grandchild does nothing but exist: no subc dial, no exit, no signal
// handler. Its whole purpose is to be a process the teardown must reach.
std::future::pending::<()>().await;
unreachable!("a pending future never resolves");
}
if env_flag(FAKE_AFT_ORPHAN_WRITER_MODE_ENV) {
return run_detached_orphan_writer().await;
}
Expand Down Expand Up @@ -297,6 +317,7 @@ async fn run_never_connect() -> Result<(), StubError> {
}

announce_never_connect_ready()?;
spawn_grandchild_if_requested()?;

// Park. The supervisor's teardown -- signal, or the kill behind it -- is what
// ends this process; nothing here decides to stop on its own, because a test
Expand All @@ -306,6 +327,30 @@ async fn run_never_connect() -> Result<(), StubError> {
unreachable!("a pending future never resolves");
}

/// Spawn a grandchild and record its pid, when the run asks for one.
///
/// The pid is written from HERE, out of the `Child` handle, so the test can
/// address the grandchild without racing its startup. The `Child` is
/// deliberately dropped rather than kept: `std::process::Child` closes its handle
/// on drop and does not wait, so the grandchild keeps running while the parent
/// holds nothing that would keep the process object alive past its death.
fn spawn_grandchild_if_requested() -> Result<(), StubError> {
let Ok(pid_file) = env::var(FAKE_AFT_GRANDCHILD_PID_FILE_ENV) else {
return Ok(());
};
let exe = env::current_exe().map_err(StubError::Io)?;
let grandchild = Command::new(exe)
.env(FAKE_AFT_GRANDCHILD_MODE_ENV, "1")
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.map_err(StubError::Io)?;
fs::write(&pid_file, grandchild.id().to_string()).map_err(StubError::Io)?;
drop(grandchild);
Ok(())
}

fn announce_never_connect_ready() -> Result<(), StubError> {
let Ok(path) = env::var(FAKE_AFT_NEVER_CONNECT_READY_PATH_ENV) else {
return Ok(());
Expand Down
5 changes: 5 additions & 0 deletions crates/subc-daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ tracing = "0.1"
[target.'cfg(target_os = "linux")'.dependencies]
subc-cgroup = { path = "../subc-cgroup", version = "0.1.1" }

[target.'cfg(windows)'.dependencies]
# Job-object containment for module grandchildren (issue #109). A leaf crate
# because this crate forbids unsafe code and the Win32 calls need it.
subc-jobobject = { path = "../subc-jobobject" }

[target.'cfg(unix)'.dependencies]
# SIGTERM for `protocol: "none"` teardown. This crate forbids unsafe code, so
# `libc::kill` is not reachable from here; rustix wraps the same syscall safely
Expand Down
Loading
Loading