fix: trois defauts trouves par la revue adverse sur le registre derive

Chacun reproduit avant d'etre corrige, chacun epingle par un test qui
echoue si la correction est retiree.

1. Une barriere pouvait survivre a la tentative qui l'avait produite.
`getSyncState` repondait `"synced"` sur le seul `barrierCrossed`, alors
que son propre commentaire disait « sur le canal qui tient MAINTENANT » :
`reportSetupFailure` efface `establishing` et laisse `lastState`, donc un
`doc_subscribe` qui poussait un `State` puis echouait laissait une
autorite - et `noRowsMeansAbsent` prend une lecture a zero ligne pour une
absence definitive. Le registre precedent repondait `"timed-out"` la ;
strict est la lecture de cette famille, son cout est la latence, jamais
la perte.

2. `release` ne repondait pas « est-ce que j'ecoute encore ».
`resetDocSubscriptions()` largue les auditeurs et supprime l'entree sans
rien dire, donc le handle continuait de paraitre vivant ; si autre chose
souscrivait d'abord au meme document, le court-circuit sautait l'ouverture
due et le document restait tenu par un auditeur libre de partir. Le
fan-out numerote desormais ses incarnations, et l'ouverture compare.

3. Une tentative sans reponse TIENT quand meme. Elle ne comptait pas, si
bien que chaque lecture d'un document dont le `doc_subscribe` ne repond
jamais repayait le repli borne - 8 s en production, sur le chemin de la
lecture ancree. Un second `doc_subscribe` est de toute facon impossible
tant qu'un est en vol, donc la tentative fraiche n'achetait rien.

Les trois questions sont maintenant une fonction nommee, `stillStanding`,
et chacune est posee a qui detient la reponse.
This commit is contained in:
Sylvain Duchesne
2026-09-24 13:17:10 +02:00
parent 2cf42a244b
commit dcf3814446
3 changed files with 191 additions and 15 deletions
+26 -2
View File
@@ -209,6 +209,18 @@ interface DocFanOut {
* Its own `doc_subscribe` would have pushed one; the fan-out owes it the same.
*/
lastState: { resp: DocChange; type: DocChangeType } | null;
/**
* WHICH fan-out this is for this NURI - a new number for every entry ever created, so a
* caller holding a listener can tell whether the entry it joined is still the one in the map.
*
* It exists because {@link resetDocSubscriptions} drops every listener WITHOUT telling the
* callers who took them out, and a caller cannot see that from its own unsubscribe function:
* `emulated-verifier/open-repo.ts` held a listener handle that went on reading as "I am still
* listening" after the entry it belonged to had been deleted, and skipped an open it owed. The
* entry OBJECT already answers this inside the module ({@link isCurrentAttempt}); a number is
* what can leave it.
*/
readonly incarnation: number;
}
/**
@@ -222,6 +234,9 @@ interface DocFanOut {
*/
const fanOuts = new Map<Nuri, DocFanOut>();
/** How many fan-outs have ever been created - see {@link DocFanOut.incarnation}. */
let fanOutsEverCreated = 0;
/**
* What the one real subscription for a document IS, right now.
*
@@ -259,6 +274,14 @@ export interface DocChannel {
* channel being released, and the session changing under it ({@link resubscribeDocs}).
*/
readonly barrierCrossed: boolean;
/**
* WHICH fan-out is standing for this document, or `0` when none is.
*
* A caller that took a listener out compares this with what it saw then: a different number
* means its listener was dropped from under it and the entry replaced, which
* {@link resetDocSubscriptions} does without a word. See {@link DocFanOut.incarnation}.
*/
readonly incarnation: number;
}
/**
@@ -270,13 +293,13 @@ export interface DocChannel {
// @provenance docChannel kind=invention level=none ref=none — a question about this module's own fan-out, which exists because a branch upstream holds a single subscriber; there is nothing to align it with
export function docChannel(nuri: Nuri): DocChannel {
const entry = fanOuts.get(nuri);
if (!entry) return { state: "none", barrierCrossed: false };
if (!entry) return { state: "none", barrierCrossed: false, incarnation: 0 };
// `establishing` says an attempt STANDS (it is left true once one has succeeded, see
// {@link DocFanOut.establishing}); `realUnsub` says that attempt's call has answered. The
// two together are the only thing that separates "asked" from "open" - and recording the
// first as if it were the second is the defect this accessor exists to make impossible.
const state: DocChannelState = !entry.establishing ? "none" : entry.realUnsub !== null ? "open" : "opening";
return { state, barrierCrossed: entry.lastState !== null };
return { state, barrierCrossed: entry.lastState !== null, incarnation: entry.incarnation };
}
/** Hand one push to one listener, isolating a throwing handler from the others. */
@@ -633,6 +656,7 @@ export function subscribeDocUnguarded(
establishing: false,
attempt: 0,
lastState: null,
incarnation: ++fanOutsEverCreated,
};
fanOuts.set(nuri, entry);
}