Skip to content

embedder: cancelled async imports convert ownership-bearing results before discard #329

Description

@lannbot

Summary

Cancelled async host-import settlements are checked for discard only after the facade has converted their values. Conversion can register resources or otherwise transfer ownership, even though the guest will never receive the result.

Priority: high. Reproduced on fd4266b516632e80943456a45aa64b95ea42f36f with real Wasm, Deno 2.9.5 / V8 15.0, Linux aarch64.

Reproduction

A component imports a host resource r and make: async func() -> own<r>. Its run export:

  1. Calls the async lower of make with a result pointer.
  2. Extracts the subtask index from the packed result.
  3. Executes async subtask.cancel, then subtask.drop.
  4. Returns the cancellation state.

The host implements make with a deferred Promise. run() returns 4 (CANCELLED_BEFORE_RETURNED). The resource registry is empty at this point. Resolve the Promise with a new host resource object and allow one event-loop turn.

Observed: { liveAfterDiscard: 1, disposed: 0 }. A resource registration now exists, but no guest handle owns it. The expected registry count is zero: discarded delivery must not perform ownership conversion.

The local review fixture is /tmp/opencode/facade-review/cancel-own.wat; the failing test is cancelled async own result must not register a rep in that directory's repro_test.ts (local review artifacts, not repository dependencies).

Cause and authority

  • instantiate.ts:782–790 executes ok(v) / fail(e, args) in the facade Promise reaction.
  • boundary.ts:1852–1868 checks resolved/cancelled or poisoned recipients only in the reaction on that already-adapted Promise.
  • contracts/embedder-api.md:217–224 explicitly says discarded settlements are never lowered. Architecture §6 describes the same policy.

The existing low-level cancellation tests pass because they do not exercise facade ownership conversion before the boundary guard. Related policy: #241.

Acceptance criteria

  • Make the discard decision before facade result conversion, including ComponentException payload conversion.
  • A discarded result must not register resources, invalidate transferred guest wrappers, or start new producer pumps.
  • Preserve borrow-scope cleanup and handling of rejected discarded Promises.
  • Add facade-level cancellation/late-settlement coverage using an own-valued result; review the equivalent poisoned-recipient path. Run just gates for the repair.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingp1Correctness bugs likely to impact consumers; high-priority missing features

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions