Skip to content
Merged
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
14 changes: 7 additions & 7 deletions .rust-mutants.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ outcome = "survived"

[[mutation.expect]]
path = "src/transaction.rs"
item = "SpawnTransaction::create"
item = "attribute_list"
rule = "condition-to-false"
original = "attribute_count == 0"
original = "count == 0"
reason = "Equivalence: CreateProcessW accepts an empty attribute list."
outcome = "survived"

Expand Down Expand Up @@ -94,20 +94,20 @@ outcome = "survived"

[[mutation.skip]]
path = "src/sys.rs"
lines = "1210-1210"
lines = "1246-1246"
reason = "Unreachable: GetEnvironmentStringsW returns null only when out of memory."

[[mutation.skip]]
path = "src/sys.rs"
lines = "1219-1219"
lines = "1255-1255"
reason = "Hang-only: continuing past the block terminator loops forever."

[[mutation.skip]]
path = "src/transaction.rs"
lines = "572-572"
lines = "565-565"
reason = "Unreachable: GetFullPathNameW cannot return a double quote."

[[mutation.skip]]
path = "src/transaction.rs"
lines = "127-131"
reason = "Unreachable: wide_nul fails only on an interior NUL, which validate_command rejects first; rejects_every_malformed_text_component holds it."
lines = "77-77"
reason = "Unreachable: current_directory fails only on an interior NUL, which validate_command rejects first; rejects_every_malformed_text_component holds it."
18 changes: 18 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ multiple-versions-include-dev = true
skip = []
skip-tree = []
wildcards = "deny"
# ADR 0016: no erased errors, erased dispatch, runtime registration, or implicit global state.
deny = [
{ crate = "anyhow", reason = "erases error types" },
{ crate = "arc-swap", reason = "shares mutable global state" },
{ crate = "async-trait", reason = "erases futures behind trait objects" },
{ crate = "color-eyre", reason = "erases error types" },
{ crate = "dashmap", reason = "shares mutable state and orders by hash" },
{ crate = "downcast-rs", reason = "dispatches on runtime types" },
{ crate = "erased-serde", reason = "erases serialization behind trait objects" },
{ crate = "eyre", reason = "erases error types" },
{ crate = "inventory", reason = "registers items at run time" },
{ crate = "lazy_static", reason = "initializes global state implicitly" },
{ crate = "linkme", reason = "registers items at link time" },
{ crate = "once_cell", reason = "initializes global state implicitly" },
{ crate = "parking_lot", reason = "shares mutable state behind locks" },
{ crate = "serde_traitobject", reason = "serializes trait objects" },
{ crate = "typetag", reason = "registers trait-object types at run time" },
]

[sources]
allow-git = []
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0007-spawn-transaction.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 0007 — Process creation is transactional

Status: accepted (2026-08-02)
Status: accepted (2026-08-02); ADR 0016 moves the rollback of the created process into its handle

## Context

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 0013 — Repository laws are enforced mechanically

Status: accepted (2026-09-25)
Status: accepted (2026-09-25); ADR 0016 extends the trait-object rule to every `dyn`

## Context

Expand Down
40 changes: 40 additions & 0 deletions docs/adr/0016-spawn-state-is-explicit-and-typed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# 0016 — Spawn state is explicit and typed

Status: accepted (2026-10-13); moves the rollback of ADR 0007 into the process handle, and extends the trait-object rule of ADR 0013

## Context

A suspended child and an uncommitted transaction kept their process in an `Option`, so that a consuming method could move it out past the type's `Drop`.
Each read of it needed an `expect`, and the lint registry excused both.
One function acquired every creation resource, and the registry excused its length too.

Handles travelled as `BorrowedHandle`, and handle values as `isize`.
Nothing stopped a thread handle from reaching a call for processes, or a value of the creator's table from joining the values an alternate parent's child inherits.
The attribute list held pointers to values that the transaction kept alive by convention.

PR #14 recorded in its ADR 0009 that trait objects, runtime reflection, shared interior mutability, global state, and hash-ordered collections hide what the compiler needs to check a spawn.
Main dropped #14, and with it those rules.

## Decision

- The process handle of a spawn carries its rollback.
Until the main thread runs, dropping the handle terminates the process.
A transaction, a `SuspendedChild`, and a `Child` own their parts directly, and `resume` and the commits move them out without an `Option`.
A running spawn commits by resuming a `SuspendedChild`, so both paths share one resume.
- The creation splits into functions for the standard I/O, the private Job, the Job list, the working directory, and the attribute list.
- Each handle names its object type.
`Owned<K>` and `Borrowed<'a, K>` name a process, a thread, a Job, a pipe end, a standard stream, or a handle the caller supplied, and each wrapper in `sys` takes the type its Win32 call needs.
- Handle values carry the table they belong to.
`Value<Creator>` names the alternate parent and the Jobs, and `Value<Inheritor>` names the inherited handles and the standard handles.
- `AttributeList<'a>` borrows the values its attributes point to, so the compiler keeps them alive and in place until `DeleteProcThreadAttributeList` runs.
- `cargo xtask gates` refuses a trait object in every file, except as the return type of `source` in an implementation of `Error`.
In library code it also refuses `Rc`, `Arc`, `Weak`, cell types, locks, atomics, `static` items, one-time initialization, `thread_local`, `Any`, `TypeId`, type names, and hash-based maps and sets.
Items under `cfg(test)` or `cfg(kani)`, and `src/failure_tests.rs`, keep their thread-local state, counters, and downcasts.
- `deny.toml` bans the crates #14 banned, which erase errors, erase serialization or futures, register items at run time, or keep implicit global state.

## Consequences

- The registry holds no `expect_used` or `too_many_lines` exception for `src/child.rs` or `src/transaction.rs`.
- A handle of the wrong kind, a value of the wrong table, or an attribute value that dies before its list fails to compile.
- Concurrency in the library needs a typed boundary and a model before it can pass the gate.
- The output readers of `Child::wait_with_output` stay threads that own what they read, so they share no state.
2 changes: 0 additions & 2 deletions public-api/windows-spawn.txt
Original file line number Diff line number Diff line change
Expand Up @@ -878,8 +878,6 @@ pub fn windows_spawn::SuspendedChild::main_thread_handle(&self) -> std::os::wind
pub fn windows_spawn::SuspendedChild::resume(self) -> windows_spawn::Result<windows_spawn::Child>
impl core::fmt::Debug for windows_spawn::SuspendedChild
pub fn windows_spawn::SuspendedChild::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
impl core::ops::drop::Drop for windows_spawn::SuspendedChild
pub fn windows_spawn::SuspendedChild::drop(&mut self)
impl std::os::windows::io::handle::AsHandle for windows_spawn::SuspendedChild
pub fn windows_spawn::SuspendedChild::as_handle(&self) -> std::os::windows::io::handle::BorrowedHandle<'_>
impl std::os::windows::io::raw::AsRawHandle for windows_spawn::SuspendedChild
Expand Down
Loading
Loading