Skip to main content

Module backend

Module backend 

Source
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/attach bind 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). submit accepts 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.
  • cancel initiates 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_close starts 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 release only 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)

  • poll appends at most events.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, whatever has_work reports (web, whose poll never enters the OS, keeps draining cached host work). None means an unbounded wait. Durations must retain sub-millisecond precision.
  • Budget::accepts bounds the connections multishot accepts deliver in one poll: nonterminal Accepted/PipeAccepted events (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 for has_work nor 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.waits counts positive-timeout/infinite waits; discovery_polls counts zero-time native polls. Their sum is at most one. zero_event_waits counts 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_work covers queued completions and cached runnable I/O. wake is 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.
  • integration opts 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)

  • detach is called only when all operations are quiescent. It unregisters the source loop before returning an owning Send transport. attach re-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.
  • Detached may be an enum, not an integer: IOCP socket migration may require duplication; WASI/browser objects need not support transfer. Resource is intentionally absent: each backend owns its native tables keyed by Handle.
  • A Detached is also how a descriptor leaves turnloop for good: the platform types expose into_fd/into_socket/into_handle, which restore whatever the backend changed on adoption (status flags, terminal or console mode) and then release ownership. Because detach already proved quiescence, the loop has no operation, buffer or registration left for that transport.
  • Wake is 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_option are synchronous and produce no Event. They run entirely inside the call, touch no operation storage, and must not allocate.
  • A backend reports Unsupported for an option its platform lacks. It must never accept and ignore one: a host reads a lie back from get_option, and “the option is on” is a security- and latency-relevant claim.
  • ListenOpts::accept_defaults is applied by the accepting backend to each accepted transport before its Accepted/PipeAccepted Outcome is produced, so the core hands the host an already-configured connection. A backend that cannot apply a requested default rejects the listener in open.

§Filesystem (§7.6 Files)

  • FILESYSTEM selects where typed FsRequests run. Pool (native default): the core runs them on the shared blocking pool and never calls the backend. Backend (WASI): fs accepts them under the ordinary Request rules, with a FIFO per handle, and yields Outcome::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 calls release for that never-visible handle.
  • fs_watch binds a native watch to a new handle; the core then submits one multishot Operation::WatchFs. Each nonterminal Outcome::Watch carries a pooled lease of records (see WatchEvents). Pool exhaustion retains events in bounded per-watch storage; lost events set overflow. 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.
PollInfo
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.

Traits§

Backend
Safety
Wake
Only the notifier calls this method; implementations should expose syscall counts here, counting attempts (including failures), not guessed notifications.