fix(client): deferRegistration rolls its subscription back when construction throws

The immediate registration attempt runs after the subscription is
installed; a synchronous failure (declared slot already occupied) escaped
the constructor without returning a handle, leaving a subscription owned by
nothing — later ledger flushes would fire it against a failed fiber.
Construction failure now unsubscribes before rethrowing, with a dedicated
deferred.spec covering both arms (ds-review-bot on the flow registrations;
the effect-level rollback in the flow packages keeps covering earlier
successfully-constructed deferrals).
This commit is contained in:
creatixchu
2026-07-29 01:49:58 +08:00
parent b6762d20a2
commit 663ac4b50b
2 changed files with 55 additions and 1 deletions
+11 -1
View File
@@ -37,6 +37,8 @@ export interface DeferredRegistration {
* @param component - the component whose ledger presence marks "registered".
* @param register - performs the actual registration; returns its disposer.
* @returns the deferral handle (dispose in the owning effect's disposer).
* @throws the immediate registration's failure, after removing the
* just-installed subscription — a throwing construction leaves nothing live.
*/
export function deferRegistration(
registry: DeferralRegistry,
@@ -51,7 +53,15 @@ export function deferRegistration(
dispose = register()
}
const unsubscribe = registry.subscribe(name, () => { tryRegister() })
tryRegister()
try {
tryRegister()
} catch (error) {
// A synchronous registration failure (the declared slot is already
// occupied) must not leave the just-installed subscription behind: the
// caller receives no handle to dispose it through.
unsubscribe()
throw error
}
return {
refresh() {
dispose?.()