linera-io/linera-protocol · error · anyhow
Child process {self:?} already exited with status: {status}
Error message
Child process {self:?} already exited with status: {status} What it means
Raised by `ChildExt::ensure_is_running` (linera-service util.rs) when the tokio child process wrapped by the test harness (e.g. a linera-service node, faucet, or proxy started for a test) has already terminated — `try_wait()` returns `Some(status)`. It is the harness's crash detector: any later operation on the dead child reports this with the child's debug representation and exit status. The real failure happened earlier (the child's own crash), so this error is a symptom, not the root cause.
Source
Thrown at linera-service/src/util.rs:35
use linera_base::data_types::TimeDelta;
pub use linera_client::util::*;
use tracing::debug;
/// Default pause, in seconds, inserted after `linera service` commands in readme e2e tests.
pub static DEFAULT_PAUSE_AFTER_LINERA_SERVICE_SECS: &str = "3";
/// Default pause, in seconds, inserted after GraphQL mutations in readme e2e tests.
pub static DEFAULT_PAUSE_AFTER_GQL_MUTATIONS_SECS: &str = "3";
/// Extension trait for [`tokio::process::Child`].
pub trait ChildExt: std::fmt::Debug {
/// Ensures the child process is still running, returning an error if it has exited.
fn ensure_is_running(&mut self) -> Result<()>;
}
impl ChildExt for tokio::process::Child {
fn ensure_is_running(&mut self) -> Result<()> {
if let Some(status) = self.try_wait().context("try_wait child process")? {
bail!("Child process {self:?} already exited with status: {status}");
}
debug!("Child process {self:?} is running as expected.");
Ok(())
}
}
/// Reads and deserializes a JSON value from the file at the given path.
pub fn read_json<T: serde::de::DeserializeOwned>(path: impl Into<std::path::PathBuf>) -> Result<T> {
Ok(serde_json::from_reader(fs_err::File::open(path)?)?)
}
/// Expands to the name of the current test function.
#[cfg(with_testing)]
#[macro_export]
macro_rules! test_name {
() => {
stdext::function_name!()
.strip_suffix("::{{closure}}")View on GitHub (pinned to 6c226ddcb3)
Solutions
- Look at the earlier output/stderr of the child process in the test logs — the actual crash reason is printed before this error
- Reproduce by running the same linera-service command manually with the same arguments
- Check for port conflicts between concurrently running tests (give each test unique ports)
- Make the test fail fast on the first child error instead of issuing further operations against a dead child
Defensive patterns
Strategy: validation
Validate before calling
// Rust: check the child is alive before issuing harness operations
use linera_service::util::ChildExt;
child.ensure_is_running()?; // fails fast with the exit status instead of a confusing later error
// or, manually:
if let Some(status) = child.try_wait()? {
anyhow::bail!("node exited early with {status}; inspect its stderr above");
} Try / catch
// Treat as symptom: surface the crash, do not retry operations against a dead child
let result = operation(&mut child).await;
if let Err(e) = &result {
if e.to_string().contains("already exited with status") {
// fail the test now; the root cause is in the child's earlier stderr
return Err(anyhow::anyhow!("child crashed earlier: {e}").context("see child stderr"));
}
} Prevention
- Call ensure_is_running() right before each interaction so the crash is caught early with context
- Capture and print child stderr/stdout in the harness so the real crash reason is visible
- Give each test unique ports to avoid children killing each other
- Abort the test on the first child error instead of continuing
When it happens
Trigger: Invoking any harness operation (query_node, process spawn, faucet call, etc.) that calls `ensure_is_running` after the wrapped child has exited — e.g. the node binary panicked at startup, exited due to bad arguments, hit a port conflict, or was killed by the OS/OOM.
Common situations: Parallel e2e tests racing for the same ports so one node exits immediately; a faucet/node failing on a missing genesis file; CI runners killing memory-heavy linera-service children; tests continuing after a child crash instead of aborting at the first failure.
Related errors
- {}: got non-zero error code {}
- Expected an `ExecutionError`. Got: {self:#?}
- Expected an `ExecutionError`. Got: {chain_error:#?}
- Failed to obtain a port
- Notification subscription failed: {errors:?}
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/9b5a1023e1812cda.
Report an issue: GitHub.