fix(flush): the gate settles its waiter from the machine, never from a chain
The single-flight gate queued its follow-up as `leader.catch().then(() => this.flush())`. That waiter is settled ONLY by resolving the very promise the leader is being awaited through, so the moment anything inside a flush body awaits flush(), the promise graph closes on itself and nobody resolves — an unbounded hang, not a slow flush, presenting exactly like a bulk write timing out. No current call site awaits a flush from inside one, so this is a latent cycle rather than an observed one; the gate should not depend on that staying true. The queue is now a bare deferred. The leader's finally opens the gate and PROMOTES the waiter to a new leader, settling the deferred from that run; the finally returns nothing, so the leader never awaits its own follower. Every exit runs the same promotion — the leader resolving, the leader rejecting, the promoted run rejecting — so a queued caller is settled exactly once on every path, and a synchronous failure starting the promoted run is reported to the waiter instead of thrown into the leader's finally. close() drains both handles. tests/unit/brainy/flush-single-flight.test.ts pins the invariant on each path that must settle a waiter: many callers during one flush all resolve within a bound (one body, one follow-up, peak concurrency 1); a REJECTING leader still runs and settles the queued waiter; a rejecting follow-up settles its waiter and leaves the gate open; and the leader returns without waiting for a deliberately slower follower.
This commit is contained in:
parent
ebb3a4bf13
commit
dea3ec2031
3 changed files with 243 additions and 18 deletions
|
|
@ -362,7 +362,7 @@ describe('shutdown has exactly one owner', () => {
|
|||
_flushBodyRuns: number
|
||||
_flushConcurrencyPeak: number
|
||||
_flushInFlight: Promise<void> | null
|
||||
_flushFollowUp: Promise<void> | null
|
||||
_flushQueued: Promise<void> | null
|
||||
_persistBackgroundFlight: Promise<void> | null
|
||||
metadataIndex: { flush: () => Promise<void> }
|
||||
kickBackgroundFlush: (reason: 'threshold' | 'idle') => void
|
||||
|
|
@ -389,7 +389,7 @@ describe('shutdown has exactly one owner', () => {
|
|||
const direct = [brain.flush(), brain.flush(), brain.flush()]
|
||||
|
||||
// EXACTLY ONE follow-up is armed, however many callers arrived.
|
||||
expect(inner._flushFollowUp, 'the eight kicks armed one follow-up').not.toBeNull()
|
||||
expect(inner._flushQueued, 'the eight kicks armed one follow-up').not.toBeNull()
|
||||
|
||||
await Promise.all([leader, ...direct, inner._persistBackgroundFlight ?? Promise.resolve()])
|
||||
|
||||
|
|
@ -397,7 +397,7 @@ describe('shutdown has exactly one owner', () => {
|
|||
expect(inner._flushBodyRuns - runsBefore).toBe(2)
|
||||
expect(inner._flushConcurrencyPeak).toBe(1)
|
||||
expect(inner._flushInFlight).toBeNull()
|
||||
expect(inner._flushFollowUp).toBeNull()
|
||||
expect(inner._flushQueued).toBeNull()
|
||||
|
||||
inner.metadataIndex.flush = metaFlush
|
||||
await brain.close()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue