fix: la barriere n'est retenue que tant que la tentative tient

Trouve en enumerant moi-meme les ecritures de `establishing` et de
`lastState` plutot qu'en croyant l'argument du commit precedent, qui
affirmait que le garde retire etait inatteignable. Il ne l'etait pas.

`isCurrentAttempt` dit que cette entree et cet appel sont encore les
bons ; il ne dit rien sur le fait que l'appel ait REUSSI. Le courtier
expedie avant de repondre, donc un `State` peut arriver apres que
l'appel a deja ete rapporte en echec - et ce module tient lui-meme pour
atteignable qu'une poussee survive a son demontage, c'est la raison
d'etre du garde sur ce callback. Mesure : la poussee tardive recreait la
barriere morte que `reportSetupFailure` venait d'oublier, sans canal
derriere, et `getSyncState` y repondait `"synced"` - donc autorite.

`lastState !== null` implique desormais qu'une tentative tient, a
l'ecriture comme a l'effacement, donc les deux ne peuvent plus diverger
et aucun lecteur n'a a le reimposer. La poussee reste diffusee aux
auditeurs : seule la BARRIERE est une affirmation sur le document.
This commit is contained in:
Sylvain Duchesne
2026-09-24 13:35:30 +02:00
parent 226db18054
commit 56072df2af
2 changed files with 45 additions and 2 deletions
+14 -2
View File
@@ -513,8 +513,20 @@ async function establish(
// this fan-out's listeners nor its barrier are this call's to touch any more.
if (!isCurrentAttempt(nuri, entry, attempt)) return;
const type = docChangeType(resp);
// Remember the barrier for whoever joins next; a later `State` replaces it.
if (type === "State") entry.lastState = { resp, type };
// Remember the barrier for whoever joins next; a later `State` replaces it - but ONLY
// while this attempt still stands. `isCurrentAttempt` above says this entry and this
// call are still the ones, and says nothing about whether the call SUCCEEDED: a broker
// dispatches before it answers, so a `State` can land after the call has already been
// reported as failed, and this module's own doctrine is that a push outliving its
// teardown is reachable (see the header, and the guard on this very callback). Recording
// it then re-created the dead barrier {@link reportSetupFailure} had just forgotten, with
// no channel behind it - and `getSyncState` answers `"synced"` to that, which is AUTHORITY.
//
// With this, `lastState !== null` IMPLIES an attempt stands, at every write as well as at
// every clear, so the two cannot disagree and no reader has to re-impose it. The push is
// still fanned out below: a listener may want the change signal, and only the BARRIER is
// a claim about the document.
if (type === "State" && entry.establishing) entry.lastState = { resp, type };
fanOut(nuri, entry, resp, type);
})) as (() => void) | undefined;
if (!isCurrentAttempt(nuri, entry, attempt)) {