docs: exit-chunk code semantics — the -N encoding is not ExitStatus::code(), and the -1 sentinel collides with SIGHUP
- ADR-004 §1: the negative-signal encoding is this crate's convention
(Python subprocess.returncode-style, via ExitStatusExt::signal()); std's
ExitStatus::code() returns None for signal termination. The old §1
wrongly claimed the encoding "matches ExitStatus::code()'s behavior on
Unix" and the Negative section then self-corrected two pages later.
- ADR-004 Negative: replace the impossible "real exit -1" conflation with
the real one — SIGHUP (signal 1) encodes as exactly -1, the same value
as the backend wait-failure sentinel; narrow in practice (the usual
SIGHUP source is a client-sent Signal { name: "HUP" } the client knows
it sent), real otherwise.
- Carried the same correction into tty-wire.md §Control Channel, the
ControlMessage::Exit doc comment, TtySession::wait's doc comment, and
pipe.rs::exit_code_from's doc comment.
Verification: cargo test (5/5 targets ok), cargo test --all-features,
cargo clippy --all-targets -- -D warnings, cargo fmt --check,
cargo doc --no-deps, cargo check/clippy --target wasm32-unknown-unknown -D warnings
This commit is contained in:
1 parent
e3d7f6f3f2
commit
6e33320e69
5 files changed
+46
-26
No files matched your search
@@ -63,10 +63,14 @@ not data (process output). It rides on the control channel
|
||||
{"type":"exit","code":0}
|
||||
```
|
||||
|
||||
The `code` is an `i32` (matches `std::process::ExitStatus::code()` and
|
||||
Unix wait-status convention; negative values are signal-terminated,
|
||||
e.g., `-9` for SIGKILL, matching `ExitStatus::code()`'s behavior on
|
||||
Unix). The chunk is the last control chunk before stream close.
|
||||
The `code` is an `i32` in this crate's exit-status convention:
|
||||
`ExitStatus::code()` on normal exit; on Unix, the negative signal
|
||||
number on signal-terminated exit (e.g., `-9` for SIGKILL) — the same
|
||||
convention as Python's `subprocess.Popen.returncode`. It is **not**
|
||||
`ExitStatus::code()`'s behavior, which returns `None` for signal
|
||||
termination (the encoding comes from `ExitStatusExt::signal()`, see
|
||||
`src/local/pipe.rs::exit_code_from`). The chunk is the last control
|
||||
chunk before stream close.
|
||||
|
||||
### 2. The "exit chunk is last" invariant
|
||||
|
||||
@@ -156,15 +160,19 @@ the completion notification.
|
||||
code. That's correct — a cancelled stream doesn't have a
|
||||
deterministic exit; the client that cancels already knows it
|
||||
cancelled. The exit chunk is for the client that reads to completion.
|
||||
- The `-1` error-exit code conflates "the backend couldn't determine
|
||||
the exit" with "the process exited with code -1" (which doesn't
|
||||
happen on Unix — `ExitStatus::code()` returns `None` for signal
|
||||
termination, not -1; the POC's waiter thread sends -1 only on
|
||||
`wait()` failure, not on signal termination — signal termination
|
||||
sends the negative signal number, e.g., -9 for SIGKILL). A client
|
||||
that needs to distinguish "real exit -1" from "backend error" can't
|
||||
from the code alone. This is a documented edge case; if it becomes
|
||||
load-bearing, a future control message type
|
||||
- The `-1` sentinel collides with a real SIGHUP termination on the
|
||||
Unix pipe path: signal-terminated children are encoded `-N`, so
|
||||
SIGHUP (signal 1) encodes as exactly `-1` — the same value the
|
||||
adapter sends for a backend wait failure. A client can't
|
||||
distinguish a real SIGHUP termination from a backend wait failure
|
||||
from the code alone. The collision is narrow in practice (the usual
|
||||
way a pipe-mode child receives SIGHUP is a client-sent
|
||||
`Signal { name: "HUP" }` control chunk, which the client knows it
|
||||
sent), but it is real for any other SIGHUP source. Note
|
||||
`std::process::ExitStatus::code()` cannot itself cause the
|
||||
collision — it returns `None` for signal termination, so this
|
||||
conflation exists only through this crate's `-N` encoding. If
|
||||
distinguishing becomes load-bearing, a future control message type
|
||||
(`{"type":"exit_error","message":"..."}`) can carry the distinction
|
||||
additively (the `type`-tagged enum is the extension seam per
|
||||
ADR-001).
|
||||
|
||||
@@ -270,9 +270,13 @@ were indistinguishable on the same stream_type.
|
||||
backend's default kill (see [tty-local.md](tty-local.md) REQ-TTY-02 —
|
||||
`portable_pty`'s `ChildKiller::kill` sends SIGHUP).
|
||||
|
||||
**Exit code.** `code` is `i32` (matches `std::process::ExitStatus::code()`;
|
||||
negative values are signal-terminated, e.g., -9 for SIGKILL on Unix). The
|
||||
exit chunk is the last control chunk before stream close (ADR-004).
|
||||
**Exit code.** `code` is `i32`: `ExitStatus::code()` on normal exit; on
|
||||
Unix, the negative signal number on signal-terminated exit — the crate's
|
||||
own convention, not `ExitStatus::code()`'s, which returns `None` for
|
||||
signal termination. `-1` is the adapter's "could not determine" sentinel
|
||||
and (via the `-N` encoding) the SIGHUP value — the SIGHUP/sentinel
|
||||
collision is a documented edge case (ADR-004). The exit chunk is the
|
||||
last control chunk before stream close (ADR-004).
|
||||
|
||||
**Extensibility.** The `type` tag is the extension seam: new control
|
||||
message types are added by extending the tagged enum. Unknown `type`
|
||||
|
||||
+7
-4
@@ -85,10 +85,13 @@ pub enum ControlMessage {
|
||||
Eof,
|
||||
/// Process exit code (server→client, `STREAM_CTRL_OUT`). The exit
|
||||
/// chunk is the last control chunk before stream close (ADR-055).
|
||||
/// `code` is `i32` matching `std::process::ExitStatus::code()`;
|
||||
/// negative values are signal-terminated (e.g., `-9` for SIGKILL on
|
||||
/// Unix). `-1` is the adapter's best-effort "backend could not
|
||||
/// determine the exit code" sentinel (ADR-055 §4).
|
||||
/// `code` is `i32`: `ExitStatus::code()` on normal exit; on Unix,
|
||||
/// the negative signal number on signal-terminated exit (e.g.,
|
||||
/// `-9` for SIGKILL — the crate's convention, not
|
||||
/// `ExitStatus::code()`'s, which returns `None` there). `-1` is
|
||||
/// the adapter's best-effort "backend could not determine the
|
||||
/// exit code" sentinel (ADR-055 §4) — colliding with the SIGHUP
|
||||
/// encoding on the Unix pipe path.
|
||||
Exit { code: i32 },
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -283,8 +283,10 @@ impl Drop for PipeExitFuture {
|
||||
|
||||
/// Map an `ExitStatus` to the adapter's `i32` exit code: `code()` on normal
|
||||
/// exit; on Unix, the negative signal number on signal-terminated exit
|
||||
/// (matches `std` convention and ADR-055 §4). Falls back to `-1` (the
|
||||
/// adapter's "could not determine" sentinel — ADR-055 §4).
|
||||
/// (ADR-055 §4's convention — `ExitStatus::code()` returns `None` for
|
||||
/// signal termination, so this crate's encoding is via
|
||||
/// `ExitStatusExt::signal()`). Falls back to `-1` (the adapter's
|
||||
/// "could not determine" sentinel — ADR-055 §4).
|
||||
fn exit_code_from(status: ExitStatus) -> i32 {
|
||||
if let Some(code) = status.code() {
|
||||
return code;
|
||||
|
||||
+7
-4
@@ -509,10 +509,13 @@ impl TtySession {
|
||||
|
||||
/// Await the `Exit` control chunk and return the process exit
|
||||
/// code. The exit chunk is the last control chunk before stream
|
||||
/// close (ADR-055). `code` is `i32` matching
|
||||
/// `std::process::ExitStatus::code()`; negative values are
|
||||
/// signal-terminated, `-1` is the adapter's best-effort "backend
|
||||
/// could not determine the exit code" sentinel.
|
||||
/// close (ADR-055). `code` is `i32`: `ExitStatus::code()` on
|
||||
/// normal exit; on Unix, the negative signal number on
|
||||
/// signal-terminated exit (e.g., `-15` for SIGTERM — the crate's
|
||||
/// convention, not `ExitStatus::code()`'s, which returns `None`
|
||||
/// there). `-1` is the adapter's best-effort "backend could not
|
||||
/// determine the exit code" sentinel — colliding with the SIGHUP
|
||||
/// encoding on the Unix pipe path (ADR-055 §4).
|
||||
///
|
||||
/// Returns [`TtySessionError::NoExitChunk`] if the session ends
|
||||
/// (server closed the stream) before an exit chunk is observed.
|
||||
|
||||
Reference in new issue
Block a user