Re-read the watcher swap while shutting down
Shutdown captured the swap promise once, so it waited for whichever swap was current when stdin closed. A rebuild already in flight can start its own swap after that point, and the queue was then closed while the later one was still flushing — the same dropped changes, one interleaving further out. Read the current swap each time round instead, until it stops changing. The regression test drives that interleaving directly: it replaces the swap while shutdown is already waiting, and asserts the queue stays open until the later one has flushed. Against capture-by-value the queue is already closed with only the first swap's work landed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lcj4iQ3fBxMwAu2rf4zLbC
This commit is contained in:
parent
0bbf270efc
commit
26fcae0e1a
2 changed files with 47 additions and 4 deletions
|
|
@ -133,7 +133,7 @@ it('does not close the queue while a watcher swap is still flushing', async () =
|
|||
flushed.push('collected-during-swap')
|
||||
})
|
||||
|
||||
let shutdown = shutdownWatchMode(swap, [], {
|
||||
let shutdown = shutdownWatchMode(() => swap, [], {
|
||||
async close() {
|
||||
closed = true
|
||||
},
|
||||
|
|
@ -147,3 +147,38 @@ it('does not close the queue while a watcher swap is still flushing', async () =
|
|||
expect(flushed).toEqual(['collected-during-swap'])
|
||||
expect(closed).toBe(true)
|
||||
})
|
||||
|
||||
it('waits for a watcher swap that starts after shutdown begins', async () => {
|
||||
// A rebuild already in flight can swap watchers while we are shutting down.
|
||||
// Reading the swap once captures whichever was current when stdin closed, and
|
||||
// closes the queue while the later one is still flushing.
|
||||
let closed = false
|
||||
let flushed: string[] = []
|
||||
let finishFirstSwap!: () => void
|
||||
let firstSwap = new Promise<void>((resolve) => (finishFirstSwap = resolve)).then(() => {
|
||||
flushed.push('first-swap')
|
||||
})
|
||||
let finishSecondSwap!: () => void
|
||||
let secondSwap = new Promise<void>((resolve) => (finishSecondSwap = resolve)).then(() => {
|
||||
flushed.push('second-swap')
|
||||
})
|
||||
|
||||
let currentSwap: Promise<unknown> = firstSwap
|
||||
let shutdown = shutdownWatchMode(() => currentSwap, [], {
|
||||
async close() {
|
||||
closed = true
|
||||
},
|
||||
})
|
||||
|
||||
// The in-flight rebuild starts its own swap while shutdown is already waiting.
|
||||
currentSwap = secondSwap
|
||||
finishFirstSwap()
|
||||
await nextTask()
|
||||
expect(closed).toBe(false)
|
||||
|
||||
finishSecondSwap()
|
||||
await shutdown
|
||||
|
||||
expect(flushed).toEqual(['first-swap', 'second-swap'])
|
||||
expect(closed).toBe(true)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -515,7 +515,7 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
// disable this behavior with `--watch=always`.
|
||||
if (args['--watch'] !== 'always') {
|
||||
process.stdin.on('end', () => {
|
||||
shutdownWatchMode(watcherSwap, cleanupWatchers, eventBatches).then(
|
||||
shutdownWatchMode(() => watcherSwap, cleanupWatchers, eventBatches).then(
|
||||
() => process.exit(0),
|
||||
() => process.exit(1),
|
||||
)
|
||||
|
|
@ -718,11 +718,19 @@ export async function handle(args: Result<ReturnType<typeof options>>) {
|
|||
/// with stale CSS. Wait for an in-flight swap first, then the current generation, and
|
||||
/// only then close.
|
||||
export async function shutdownWatchMode(
|
||||
watcherSwap: Promise<unknown>,
|
||||
pendingSwap: () => Promise<unknown>,
|
||||
cleanups: (() => Promise<void>)[],
|
||||
batches: { close(): Promise<void> } | null,
|
||||
) {
|
||||
await watcherSwap
|
||||
// A rebuild already in flight can start its own swap while we are shutting
|
||||
// down, so read the current one each time round rather than capturing it
|
||||
// once. Capturing it once waits for the swap that happened to be current when
|
||||
// stdin closed and closes the queue while a later one is still flushing.
|
||||
let awaited: Promise<unknown> | undefined
|
||||
while (awaited !== pendingSwap()) {
|
||||
awaited = pendingSwap()
|
||||
await awaited
|
||||
}
|
||||
await Promise.all(cleanups.map((cleanup) => cleanup()))
|
||||
await batches?.close()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue