Expand description
Internal platform contract, revision 2 (empty-wait instrumentation and native services).
Public only so the contract runner and independently developed backends can use
it; not a stable end-user extension API. All identifiers and buffers are core
types. No raw fd, OVERLAPPED pointer, pollable or browser object crosses here,
with one deliberate exception: raw_transport reports a RawTransport the core
passes straight through to the host and never acts on (§5a handle transfer).
Ownership still leaves through detach/Detached, never through that value.
§Ownership and completion rules (D1–D4)
open/attachbind a resource to the supplied generational Handle. On error they retain nothing. Open::Tcp creates an unconnected resource; the Connect request performs the connection. Once validated/accepted, even a synchronous I/O error is queued as a terminal Event. Submit errors reject before acceptance (unsupported/invalid operation, capacity/resource setup failure).submitaccepts ownership of a Request only on Ok; on Err it must have stopped all access to its buffers before returning.- Every accepted Request yields one terminal Event, including cancellation and errors. Multishot accepts/reads may yield preceding nonterminal Events. Their terminal Event acknowledges that buffers and native op storage are quiescent.
- Native completions carry the full OpId, never a token or a reusable index alone. Delayed kernel events must not complete a later generation. IOCP must keep OVERLAPPED storage pinned until the final kernel acknowledgement.
cancelinitiates cancellation, never frees native buffers prematurely. It must eventually emit a terminal event even when cancel races with success. The core arbitrates the result and emits Cancelled if cancellation won.prepare_closestarts nonblocking teardown before cancellation. Process backends terminate the owned child and retain its exit operation until reaped, including when cancellation is already pending. Other resources need no hook.- The core calls
releaseonly after every associated operation terminated and the Closed completion was appended to host output. Drop must synchronously quiesce any remaining I/O before freeing buffers (native completion draining may be necessary). The driver never invokes user callbacks.
§Turn and boundedness (D5–D7)
pollappends at mostevents.capacity() - events.len()Events; never grows the vector. A full buffer retains work for the next turn, without losing an edge. At most one OS wait is allowed (DESIGN §10 rule 3). Queued work prohibits blocking waits: the driver passes a zero timeout, and calls poll for that one discovery poll only with native operations pending and native output reserve. With no native operation pending, the driver skips poll for queued work, whateverhas_workreports (web, whose poll never enters the OS, keeps draining cached host work).Nonemeans an unbounded wait. Durations must retain sub-millisecond precision.Budget::acceptsbounds the connections multishot accepts deliver in one poll: nonterminalAccepted/PipeAcceptedevents (turnloop#77). It is the number of handles the core can still house for them, so each one past it would be accepted from the kernel and then destroyed. It throttles only those operations: every other source, single-shot accepts included, fills the vector exactly as before (DESIGN §10 rule 3 fairness). A multishot accept held back by it takes nothing further from the kernel (a readiness backend leaves the connection in the listener’s backlog; a completion backend does not re-arm, and keeps an already completed one in its operation storage), and is neither runnable work forhas_worknor a reason to end the wait early (rule 4a): the operation is resumed by the first poll whose budget is positive again.- Readiness backends execute I/O in poll, cache readiness until EAGAIN, and requeue partially processed work fairly. Completion backends drain native completions. WASI 0.2 polls pollables; 0.3 drives a waitable set; web drains callback results and rejects blocking waits on the main thread.
- EINTR ends this poll early; it must not restart the timeout.
PollInfo.waitscounts positive-timeout/infinite waits;discovery_pollscounts zero-time native polls. Their sum is at most one.zero_event_waitscounts raw empty calls across both counters, including EINTR. Private timeout events count as zero-event waits, just like a timed OS wait returning zero; never infer this from user completions. Every backend that implements its timeout with a private wakeup source normalizes it: epoll’s timerfd, the IOCP deadline packet, the WASI 0.2 deadline pollable and the WASI 0.3 deadline subtask. Real I/O or notifier events arriving in the same call still make it non-empty. - Cached readiness ending in EAGAIN with no completion must retain the original timeout for the one permitted OS wait. It must not force a zero-timeout turn.
has_workcovers queued completions and cached runnable I/O.wakeis called only after the notifier observed PARKED. It must be safe after loop drop: the wake object owns its native resource or detects closure, never uses a possibly reused raw handle. Errors cannot silently lose a wake.integrationopts into external waiting. The core then leaves its notifier PARKED between turns; hosts call it again only while preparing external waits. This reconciles a readable integration fd with zero syscalls while RUNNING.
§Transfer and platform constraints (§5a, §7)
detachis called only when all operations are quiescent. It unregisters the source loop before returning an owning Send transport.attachre-registers on the destination. Unsupported resources return Unsupported. For asynchronous cancellation, public detach can return WouldBlock while cancellation drains; the caller turns the source loop and retries. No blocking wait is hidden here.Detachedmay be an enum, not an integer: IOCP socket migration may require duplication; WASI/browser objects need not support transfer.Resourceis intentionally absent: each backend owns its native tables keyed by Handle.- A
Detachedis also how a descriptor leaves turnloop for good: the platform types exposeinto_fd/into_socket/into_handle, which restore whatever the backend changed on adoption (status flags, terminal or console mode) and then release ownership. Becausedetachalready proved quiescence, the loop has no operation, buffer or registration left for that transport. Wakeis Send + Sync on every target, but single-thread WASI/web may implement it as a flag/host scheduler token. No backend creates a loop-driving thread. Windows’ explicitly requested Integration::Event helper is the D7 exception.- Unsupported platforms/capabilities return errors; no fake successful I/O.
§Socket options (§7.7)
set_option/get_optionare synchronous and produce no Event. They run entirely inside the call, touch no operation storage, and must not allocate.- A backend reports
Unsupportedfor an option its platform lacks. It must never accept and ignore one: a host reads a lie back fromget_option, and “the option is on” is a security- and latency-relevant claim. ListenOpts::accept_defaultsis applied by the accepting backend to each accepted transport before itsAccepted/PipeAcceptedOutcome is produced, so the core hands the host an already-configured connection. A backend that cannot apply a requested default rejects the listener inopen.
§Filesystem (§7.6 Files)
FILESYSTEMselects where typedFsRequests run.Pool(native default): the core runs them on the shared blocking pool and never calls the backend.Backend(WASI):fsaccepts them under the ordinary Request rules, with a FIFO per handle, and yieldsOutcome::Fs. A request that opens binds its native object to the supplied new handle only on success; after a failed or cancelled open the core callsreleasefor that never-visible handle.fs_watchbinds a native watch to a new handle; the core then submits one multishotOperation::WatchFs. Each nonterminalOutcome::Watchcarries a pooled lease of records (seeWatchEvents). Pool exhaustion retains events in bounded per-watch storage; lost events setoverflow. No backend thread is created: native watch sources are registered with the loop’s own wait, except where the OS delivers on its own threads (FSEvents dispatch queues).
Re-exports§
pub use unix::Unix as Platform;
Modules§
- unix
- Shared completion engine for kqueue and epoll. Each direction has an intrusive FIFO of operations; readiness remains cached until an actual EAGAIN.
Structs§
- Budget
- What the core can house from one
poll(see the module documentation). - Event
- Backend completion awaiting translation into a host-visible completion.
- Poll
Info - Instrumentation for the single bounded backend wait.
- Request
- One accepted backend operation, keyed by a full generational operation ID.
Enums§
- Filesystem
- Where typed filesystem requests execute (see the module documentation).
- FsOutput
- Backend- or worker-side typed result; the core attaches leases and handles.
Metadata is inline: results travel through fixed, preallocated event storage,
and boxing would allocate per
stat. The pool path keeps metadata in its slot. - Operation
- Backend requests with completion-shaped ownership and cancellation semantics.
- Outcome
- Successful backend outcomes; accepted transports remain owned until attached.