@pgxsinkit/pgwasm
Version:
84 lines • 4.77 kB
TypeScript
/**
* `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