fix(memory): restore session history when compaction replacement is cancelled - #4298
fix(memory): restore session history when compaction replacement is cancelled#4298nothariharan wants to merge 2 commits into
Conversation
…ancelled CancelledError is a BaseException, so the Exception-only restore path left sessions empty after a successful clear during compaction replacement.
seratch
left a comment
There was a problem hiding this comment.
Thanks for the focused cancellation fix. The main path works, but the rollback currently has no ownership boundary over the session state it rewrites. I reproduced this with SQLiteSession: pause cancellation restore before its clear, append a newer item through the same OpenAIResponsesCompactionSession, then resume restore. The rollback deletes the newer item, while _session_items still retains it, so persistent history and the compaction cache diverge.
Before merge, please add one session-owned mutation lock covering the snapshot, clear/add replacement, and restore, and use the same lock for wrapper add_items(), pop_item(), and clear_session(). Add a controlled interleaving regression test proving the newer write waits and survives in chronological order after cancellation recovery.
|
Yeah cool will get on it, should have done that earlier mb. I can reproduce the failure mode you described: if cancellation restore is paused before its clear, a newer I'll add a session-owned mutation lock that covers:
I'll also add a controlled interleaving regression test with |
…writes Add a session-owned mutation lock around snapshot/replace/restore and wrapper mutators so cancel-restore cannot rewrite past a newer concurrent write.
Summary
This pull request fixes a data-loss path in
OpenAIResponsesCompactionSessionwhere cancelling a run during compaction replacement could permanently wipe the underlying session history.PR #3117 already restored previous history when replacement failed with a normal
Exception(for example, a failedadd_items()afterclear_session()). That restore path does not run forasyncio.CancelledError, because on supported Python versionsCancelledErroris aBaseException, not anException.What goes wrong today
Compaction replacement is intentionally destructive:
clear_session()— history is now empty.add_items(compacted_output)— write the compacted replacement.If cancellation lands after step 2 and before step 3 finishes, the
except Exceptionhandlers from #3117 do not run. The compacted write never lands, restore never runs, and the session remains empty.This is especially sharp for immediate cancel (
task.cancel()), which is delivered atawaitpoints. The post-clearadd_items()await is exactly such a point.What this PR changes
ExceptionandCancelledError.CancelledError, restore previous history with the same clear-failed vs post-clear routing used forException.CancelledErrorreaches the caller.Exceptionrestore behavior unchanged (no shield on that path).Restore helpers now accept
BaseExceptionso cancellation can be logged through the existing warning helpers.Relationship to nearby work
CancelledErrorgap left by Exception-only handlersTest plan
Added regression coverage next to the #3117 tests in
tests/memory/test_openai_responses_compaction_session.py:add_items()raisesCancelledErrorafter a successful clearclear_session()mutates then raisesCancelledErrortask.cancel()+ yield while restore is gated), asserting history is already restored whenCancelledErrorsurfacesLocal verification:
uv run ruff format/uv run ruff checkuv run pyright/uv run mypyon the touched compaction moduleuv run pytest -q tests/memory/test_openai_responses_compaction_session.py(51 passed)make testsvia the verification script could not be run here becausemakeis unavailable in this Windows environment; broaderpytestrun showed only unrelated local tracing environment failuresIssue number
N/A — discovered by code-path review against the #3117 restore contract; no open issue tracks the cancel-specific gap.
Checks
/reviewbefore submitting this PR