Skip to content

Commit 5c2943b

Browse files
refactor(moonbit): a possibly better async implementation for wasi p3 (bytecodealliance#1659)
* fix(moonbit): update flavorful buffer type * feat(moonbit): preserve kebab-case package names * feat(moonbit): implement component async bindings * test(moonbit): add Rust async fixture pairings * refactor(moonbit): remove async duplication * fix(moonbit): serialize stream writer teardown * fix(moonbit): close stream test wakeup race * refactor(moonbit): clean up async binding code * fix(moonbit): correct async future rejection * fix(moonbit): harden async endpoint boundaries
1 parent 50b799a commit 5c2943b

79 files changed

Lines changed: 11006 additions & 1774 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

crates/moonbit/CONTEXT.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# MoonBit Bindings Context
2+
3+
MoonBit emits core wasm and `wasm-tools` adapts it into a component. Generated
4+
bindings expose WIT imports and adapt MoonBit exports to the component ABI.
5+
6+
Async design references:
7+
8+
- [Design contract](docs/async-design.md)
9+
- [Terminology](docs/async-glossary.md)
10+
- [FFI-boundary conversion decision](docs/adr/0001-async-ffi-boundary-conversion.md)
11+
- [Local Future/Promise decision](docs/adr/0002-local-future-promise.md)
12+
13+
Lowercase `future` and `stream` refer to Component Model types. Uppercase
14+
`Future` and `Stream` refer to local MoonBit types. Generated code converts
15+
between them only at concrete WIT positions whose intrinsic names come from
16+
`wit-parser`.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Keep local async values separate from component endpoints
2+
3+
Status: accepted; implemented
4+
5+
## Context
6+
7+
MoonBit `Future[T]` and `Stream[T]` must support arbitrary MoonBit values. A
8+
Component Model `future<T>` or `stream<T>` is instead a transferable readable
9+
endpoint whose operations and payload representation belong to a concrete WIT
10+
function position.
11+
12+
The previous implementation exposed generic component endpoint wrappers backed
13+
by operation vtables. That tied local construction and recursive conversion to
14+
position-specific ABI machinery.
15+
16+
## Decision
17+
18+
`Future[T]`, `Promise[T]`, `Stream[T]`, and `Sink[T]` are local coordination
19+
types and never contain component handles or operation tables.
20+
21+
For each endpoint occurrence, generated FFI code obtains intrinsic names from
22+
`wit-parser`, owns the raw endpoint, and recursively lifts or lowers the payload.
23+
Nested endpoints are converted one layer at a time when their containing value
24+
crosses or is read at the boundary.
25+
26+
## Consequences
27+
28+
- `Future::new()` creates a local Future/Promise pair; `Stream::new()` creates a
29+
local Stream/Sink pair.
30+
- Incoming endpoints become lazy generated sources. Outgoing local values are
31+
bridged through newly created component endpoint pairs.
32+
- Lowering uses explicit prepare, commit, and reject paths so resources and
33+
partial stream prefixes transfer or clean exactly once.
34+
- Once a component future readable end is exposed, its writer must eventually
35+
write a real value or observe that the reader was dropped. The binding cannot
36+
fabricate a default value or close it without a value.
37+
- Position-specific endpoint traversal remains generator data, not a MoonBit
38+
runtime vtable.
39+
- Producing a component future or stream requires an active component async task
40+
scope. Scope-free synchronous lowering remains unsupported.
41+
42+
Detailed API and lifecycle invariants live in the
43+
[async design contract](../async-design.md).
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Add a local Future/Promise pair
2+
3+
Status: accepted; implemented in the current branch
4+
5+
MoonBit needs a producer-facing one-shot primitive for local control-flow cycles
6+
that cannot be expressed as `Future::from(async () -> T)`. The motivating case
7+
is `wasi:http@0.3.0` request handling: `consume-body` takes a Future reporting
8+
processing completion before it returns the request body whose later processing
9+
determines that completion.
10+
11+
`Future::new()` returns `(Future[T], Promise[T])` and
12+
`Future::new_with_cleanup(cleanup)` adds explicit value-discard cleanup. The
13+
Promise can complete with a value, fail with a MoonBit error, or close without a
14+
value. It is local coordination state only. It does not create, wrap, or own a
15+
component `future` endpoint, and it works for arbitrary MoonBit `T`.
16+
17+
## Consequences
18+
19+
- `Promise::complete(value)` returns `true` only when the reader accepts
20+
ownership. If the Future was already dropped, it returns `false` and the
21+
caller retains `value`.
22+
- `Future::drop()` after accepted completion runs the cleanup supplied to
23+
`new_with_cleanup`. The plain `new()` constructor is appropriate only when
24+
discarded `T` needs no explicit cleanup.
25+
- `Promise::fail(error)` makes local `Future::get()` raise that error.
26+
`Promise::close()` makes it raise `PromiseClosed`.
27+
- Settlement is one-shot. Repeating `complete`, `fail`, or `close` after a
28+
successful settlement is a programmer error.
29+
- Dropping or otherwise abandoning a still-pending Promise does not implicitly
30+
close its Future because MoonBit has no generic deterministic destructor. The
31+
producer must explicitly complete, fail, or close it.
32+
- Task cancellation that reaches a waiting reader before settlement drops the
33+
reader, so later completion returns `false`. Once completion assigns the value
34+
and wakes the reader, completion wins a simultaneous cancellation race and the
35+
reader receives the value.
36+
- Explicit `Future::drop()` follows the same race rule while `get()` is pending:
37+
dropping before settlement wakes the reader with `Cancelled`, while an
38+
already-assigned value or error remains owned by the waiting reader.
39+
- A local failure or close cannot settle an already-exposed component future
40+
without a value. If such an outcome is expected across WIT, it belongs in the
41+
payload type, for example `Future[Result[V, E]]`.
42+
- Generated FFI-boundary code remains solely responsible for creating concrete
43+
component future pairs and satisfying their writable-end settlement rules.

0 commit comments

Comments
 (0)