UNPKG

@pgxsinkit/pgwasm

Version:
84 lines • 4.77 kB
/** * `RepackedSyncBroker` — the single owner of one `RepackedVfs`, answering synchronous file requests * that arrive over `SharedArrayBuffer` channels. * * The broker is meant to be the only thing on its thread. A backend running wasm parks in futexes and * cannot await anything, so it publishes a request and blocks; the broker must therefore never block on * a promise between reading a request and writing its answer. Every handler here is synchronous, and * the store's own API is synchronous end to end. * * Three loop shapes are offered: * * - `serveForever()` parks the thread in `Atomics.wait` between requests. It is the shape for a * dedicated coordinator worker, and while it runs the thread never reaches its event loop — so * `postMessage` cannot reach it and every channel must be attached BEFORE entering it. Ask it to * return with `doorbell.requestStop()` from any thread holding the doorbell. * - `serve()` is the same loop built on `Atomics.waitAsync`, for a host that must keep its event loop * alive. Channels may be attached and detached at any time. * - `serveOnce()` scans every attached channel once and returns without waiting, for a host that * drives its own loop. * * Failure separation is the point of the class. A file rejection (`FsError`) becomes an errno in the * reply header and nothing else happens. A store failure becomes `FAULT_STORE` in the reply, and the * loop keeps running because every later request will fail the same way and the client decides what to * do. A protocol violation detaches only the offending client, with a logged reason. */ import type { RepackedFileSystem } from "../core/repacked-vfs"; import type { RepackedChannel, RepackedDoorbell } from "./protocol"; export interface RepackedSyncBrokerOptions { /** * The store this broker owns. Nothing else may hold it. A `RepackedVfs` is the one-store form; a * `MountedRepackedVfs` serves several stores as one tree, and the broker cannot tell them apart. */ readonly vfs: RepackedFileSystem; /** The shared doorbell every attached channel rings. */ readonly doorbell: RepackedDoorbell; /** The timestamp the broker supplies to every core call. Defaults to the wall clock. */ readonly now?: () => bigint; /** Where a detach reason goes. Defaults to `console.warn`. */ readonly log?: (message: string) => void; /** * How long one blocking iteration parks before re-scanning anyway. Defaults to 250 ms. The protocol * has no missed-wakeup window — the server observes the doorbell ticket BEFORE it scans, so a * request published during the scan makes the wait return `not-equal` at once — so this is purely a * heartbeat: it costs four idle wakeups a second and turns any future slip into a latency blip * instead of a permanently parked backend. `Infinity` makes the loop a pure park. */ readonly pollIntervalMs?: number; } export declare class RepackedSyncBroker { #private; readonly doorbell: RepackedDoorbell; constructor(options: RepackedSyncBrokerOptions); /** Channel ids the broker currently serves. */ attachedIds(): number[]; /** Descriptors the broker currently holds for one client — the leak check after a detach. */ openFdCount(channelId?: number): number; attach(channel: RepackedChannel): void; /** * Drop a client and close every descriptor it still holds. A backend that dies mid-query never leaks * a descriptor into the store, and the store's exclusive handles are released the moment the LAST * owner goes away rather than whenever a stale fd happens to be noticed. */ detach(channel: RepackedChannel | number, reason?: string): void; /** Detach every client, closing all descriptors. The store itself is NOT closed. */ detachAll(reason?: string): void; /** * Scan every attached channel once and answer whatever is pending. Never blocks, never throws for a * file or store error. Returns how many requests were answered. */ serveOnce(): number; /** * The blocking coordinator loop. Parks the thread in `Atomics.wait` on the doorbell between * requests, so this thread's event loop never runs: attach every channel before calling it, and stop * it with `doorbell.requestStop()` from another thread. */ serveForever(): void; /** * The same loop on `Atomics.waitAsync`, for a host that must not park its thread — the tab's main * thread, or a worker that also has to receive `postMessage`. Channels may be attached and detached * while it runs. Resolves when `doorbell.requestStop()` is called. */ serve(): Promise<void>; } //# sourceMappingURL=server.d.ts.map