mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix(ai-sessions): hold the catch-up gate across every await and cancel a stale retry
Two edges of the round-18 serialization, both in the same seam: - The superseded() probe ran after reloadChat but not after loadPastChat, which also awaits (image hydration). A catch-up overtaken during that await still settled the caught-up gate, unlocking the composer over a transcript missing the newer turn for as long as the queued catch-up was still reading. Re-probed before caughtUp is set, so a superseded catch-up leaves the gate to the one that owns it. Every await in applyTurnEnd is now followed by a probe before any write or release. - The unavailable-store retry re-queued carrying its own turn-end's chat id, and newest-queued-wins let that stale retry supersede a newer turn-end's catch-up — re-adopting and re-persisting the chat a /clear had rotated away from. A newly queued catch-up now cancels any pending retry; the new task schedules its own if it needs one, so a retry can no longer outlive its turn-end. Also trims a test comment that repeated the executor contract documented on createLatestWins. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019N8x51GqABUEFvg52V6aYo
This commit is contained in:
co-authored by
Claude Fable 5
parent
8789f5b9af
commit
3e25ae7b00
@@ -26,8 +26,6 @@ describe('createLatestWins', () => {
|
||||
order.push('second')
|
||||
})
|
||||
await tick()
|
||||
// The second task waits for the first however slow it is, so its writes
|
||||
// can never land between — or after — the first's.
|
||||
expect(order).toEqual(['first:start'])
|
||||
first.resolve()
|
||||
await tick()
|
||||
|
||||
@@ -1111,6 +1111,10 @@ function applyRunStatus(msg: RunStatusMsg): void {
|
||||
const catchUps = createLatestWins()
|
||||
|
||||
function queueCatchUp(sessionId: string, chatId: string, attempt = 0): void {
|
||||
// A pending unavailable-store retry carries its own turn-end's chat id; left
|
||||
// armed, it could fire after this newer catch-up queued and supersede it with
|
||||
// the stale id. This catch-up schedules its own retry if it needs one.
|
||||
cancelCatchUpRetry(sessionId)
|
||||
catchUps.run(sessionId, (superseded) => applyTurnEnd(sessionId, chatId, attempt, superseded))
|
||||
}
|
||||
|
||||
@@ -1177,6 +1181,11 @@ async function applyTurnEnd(
|
||||
m.adoptEmptyChat(id)
|
||||
setSessionChatId(sessionId, id)
|
||||
}
|
||||
// Re-probed because loadPastChat awaits too (image hydration): a turn-end
|
||||
// that arrived during it owns the gate now, and settling it here would
|
||||
// unlock the composer over a transcript missing that newer turn for as
|
||||
// long as the queued catch-up is still reading.
|
||||
if (superseded()) return
|
||||
caughtUp = true
|
||||
} catch (e) {
|
||||
// A read that threw leaves the same mismatched pair an 'unavailable' one
|
||||
|
||||
Reference in New Issue
Block a user