Skip to content

embedder/streams: Future.drop()/dispose on a lowered, never-written future crashes with AssertionError #90

Description

@lann

Severity: medium — public API door into a spec-unreachable state.

HostFuture.drop (exec/host_streams.ts:799-803) and embedder
Future.drop/Symbol.dispose (embedder/streams.ts:546-549, 562-564) have
no wrote-a-value guard. The spec's writable future end may not drop before
delivering its value (definitions.py:1183-1184); the host wrapper plays
the writable role once lowered.

Crash shapes today: (a) guest reader parked -> "a readable future end
cannot observe DROPPED" assert (stream_builtins.ts:264-268) fires
synchronously inside the host's drop() call; (b) no reader parked ->
guest's later future.read hits the shape assert at task/streams.ts:411.
The layer knows — the producer-failure path (embedder/streams.ts:714-749)
documents the invariant and dodges it — but the public door has no guard.

Ruling (fix shape): with the teardown-trap mechanism from the
poisoning-walk issue in place, host drop-before-write on a lowered
future becomes total and honest: arm the reader-side trap ("the host
dropped the writable end without writing a value") instead of DROPPED;
drop()/dispose never throw. On an unlowered future (guest never saw
it), plain state cleanup. Write-then-drop stays the normal path. The
producer-failure path keeps its store-level reporting (better attribution
for in-flight calls); its stale comment about the invariant gets updated.
embedder-api.md gains the drop semantics (orchestrator authors the
contract text).

Found in the 2026-08-10 adversarial conformance review (runtime + translator vs component-model @ 73b7ad5 definitions.py; wasmtime-environ 47.0.3 treated as trusted).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions