fix: un second doc_subscribe tuait le premier, et les inbox n'étaient lues qu'à la connexion
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* ONE drain at a time per inbox — the serializer both things that drain an inbox go through.
|
||||
*
|
||||
* Two of them exist in this package, and they are not coordinated by anything else: the
|
||||
* CONTINUOUS observation of the connected identity's own inboxes
|
||||
* (`emulated-verifier/inbox-observer.ts`) and the deferred timer that stands in for an
|
||||
* ABSENT owner (`emulated-verifier/inbox-processor.ts`). Both can be pointed at the same
|
||||
* inbox in the same page — a deposit into an inbox whose owner is also connected here arms
|
||||
* the timer AND pushes to the observation.
|
||||
*
|
||||
* Draining writes what it applies (`branch-registers.addLink`, on the owner's User branch),
|
||||
* so two passes over one queue interleave their reads and writes and apply the same records
|
||||
* twice. Sequencing them is the same reasoning `connect.connectedUser` drains its inboxes
|
||||
* one after another for, and it belongs HERE rather than in either caller: an invariant that
|
||||
* holds only while both callers remember it is not an invariant.
|
||||
*
|
||||
* It is a queue, not a lock: a run that arrives while another is going is not dropped, it
|
||||
* follows. Dropping would be wrong — the second run exists because something new landed.
|
||||
*/
|
||||
|
||||
import { accessLogPrefix, shortNuri } from "../shared-wallet/access-log";
|
||||
import type { Nuri } from "../model/types";
|
||||
|
||||
/** Drains in flight or queued, per inbox — the chain a new run appends itself to. */
|
||||
const running = new Map<Nuri, Promise<void>>();
|
||||
|
||||
/**
|
||||
* Where a drain's failure goes.
|
||||
*
|
||||
* It cannot throw: nothing awaits these — neither the timer nor a push is a caller — and an
|
||||
* unhandled rejection would take down whatever runtime it is in over work the application
|
||||
* never requested. But "nowhere" is not the alternative. A drain that swallows its failure
|
||||
* is indistinguishable from one that succeeded, which is precisely the defect family removed
|
||||
* from this package on 2026-08-13, and a CONTINUOUS drain that swallowed would repeat that
|
||||
* silence for as long as the session lasts. So it goes to the package's own log stream — the
|
||||
* same `console.error` with the same `[<identity>][polyfill]` prefix `startConnect` and
|
||||
* `inbox.watch` use — and it is NOT gated by the access-log flag: a diagnostic may be
|
||||
* opt-in, a failure may not.
|
||||
*
|
||||
* One failed item denies nothing, here as at connection: the inbox is not consumed by
|
||||
* failing, the deposit stays in it, and the next push or the next connection applies it.
|
||||
*/
|
||||
function reportFailure(inbox: Nuri, error: unknown): void {
|
||||
console.error(
|
||||
accessLogPrefix() + " could not apply what is in this inbox — its deposits stay in it " +
|
||||
"and the next attempt will try again: " + shortNuri(inbox) + ":",
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run `process` for `inbox`, behind any run of the same inbox still in flight.
|
||||
*
|
||||
* Resolves when THIS run has finished (or failed, having reported itself), so a caller
|
||||
* that wants to wait can — while a caller that does not simply drops the promise.
|
||||
*/
|
||||
// @provenance drainInboxSerially kind=aligned level=1 ref=engine/verifier/src/verifier.rs:inbox — upstream one verifier owns an inbox and applies its messages one at a time as they arrive; this is that serialization, for the two paths this package drains from
|
||||
export function drainInboxSerially(inbox: Nuri, process: () => Promise<void>): Promise<void> {
|
||||
const previous = running.get(inbox) ?? Promise.resolve();
|
||||
const next = previous
|
||||
// A previous run that FAILED must not cancel this one: its failure was reported where
|
||||
// failures go, and the deposits it did not apply are exactly what this run is for.
|
||||
.catch(() => undefined)
|
||||
.then(process)
|
||||
.catch((error: unknown) => reportFailure(inbox, error))
|
||||
.then(() => {
|
||||
if (running.get(inbox) === next) running.delete(inbox);
|
||||
});
|
||||
running.set(inbox, next);
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until no drain is in flight. The seam a suite needs to observe convergence without
|
||||
* waiting for it — and the one `runScheduledInboxProcessingNow` awaits after firing its
|
||||
* windows. Loops, because a drain can be chained behind the one being awaited.
|
||||
*/
|
||||
// @provenance drainsSettled kind=invention level=none ref=none — a test/lifecycle seam over the queue above; upstream nothing exposes "is the verifier done applying"
|
||||
export async function drainsSettled(): Promise<void> {
|
||||
while (running.size > 0) {
|
||||
await Promise.all([...running.values()]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user