Skip to content

Fix a deadlock condition in short term memory - #1517

Merged
malatewang merged 1 commit into
mainfrom
deadlock_fix
Aug 20, 2026
Merged

malatewang merged 1 commit into
mainfrom
deadlock_fix

Conversation

@malatewang

Copy link
Copy Markdown
Contributor

Purpose of the change

Fix a potential deadlock in short term memory.

Description

In short term memory, if query and add episode at the same time and the memory is being summarized, the query function may get into deadlock with the add episode because of the current RWLock implementation. This PR removes the second read lock acquire to prevent deadlock.

Fixes/Closes

Fixes #1514

Type of change

[Please delete options that are not relevant.]

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (does not change functionality, e.g., code style improvements, linting)
  • Documentation update
  • Project Maintenance (updates to build scripts, CI, etc., that do not affect the main project)
  • Security (improves security without changing functionality)

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.

[Please delete options that are not relevant.]

  • Unit Test
  • Integration Test
  • End-to-end Test
  • Test Script (please provide)
  • Manual verification (list step-by-step instructions)

Test Results: [Attach logs, screenshots, or relevant output]

Checklist

[Please delete options that are not relevant.]

  • I have signed the commit(s) within this pull request
  • My code follows the style guidelines of this project (See STYLE_GUIDE.md)
  • I have performed a self-review of my own code
  • I have commented my code
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added unit tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules
  • I have checked my code and corrected any misspellings

Maintainer Checklist

  • Confirmed all checks passed
  • Contributor has signed the commit(s)
  • Reviewed the code
  • Run, Tested, and Verified the change(s) work as expected

Screenshots/Gifs

[If applicable, add screenshots or GIFs that show the changes in action. This is especially helpful for API responses. Otherwise, delete this section or type "N/A".]

Further comments

[Add any other relevant information here, such as potential side effects, future considerations, or any specific questions for the reviewer. Otherwise, type "None".]

@malatewang
malatewang requested a review from edwinyyyu August 19, 2026 18:44

@edwinyyyu edwinyyyu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test waits without timeouts could be expensive but that's an existing problem.

@malatewang
malatewang merged commit 231ce17 into main Aug 20, 2026
36 of 52 checks passed
@malatewang
malatewang deleted the deadlock_fix branch August 20, 2026 22:45
xiongzubiao added a commit that referenced this pull request Sep 30, 2026
Three defects in the ShortTermMemory lifecycle (#1719):

- A cancelled query cancelled summarization for every waiter:
  wait_until_done() awaited the shared worker directly, so one
  cancelled query cancelled the worker and lost its batch. The worker
  is now shielded.

- Clearing kept the summary of the cleared content, in memory and in
  the persisted copy, so after delete_session_episodes() it still
  reached QueryResponse and a new instance for the session restored
  it. clear_memory() now cancels in-flight summarization, drops the
  queued episodes and the summary, and overwrites the persisted copy.
  If a query is already waiting on that summarization, the query holds
  the lock, so the clear waits for it and then discards the result.
  The clear runs in a shielded task and holds the write lock until the
  cancelled worker has stopped, so a cancelled clear still finishes
  and no query or new batch meets the stopping worker. The episodes are
  cleared before the persisted copy is overwritten, so a failed save
  still leaves memory cleared, and the error still propagates. close()
  drops the in-memory summary too, but keeps the persisted copy for
  the next instance.

- A cancelled close() left the instance open and uncleared. close()
  now marks the instance closed before awaiting anything and runs its
  cleanup in a shielded task, so a cancelled close still completes and
  a repeated close waits for the same cleanup. add_episodes(),
  clear_memory() and both query methods check for a close before
  taking the lock, so calls made before close() still complete, and
  get_summary() waits for summarization and reads the summary under
  one hold of the lock.

close() keeps waiting for in-flight summarization rather than
cancelling it: with instance_cache_size at 0, an instance is closed at
the end of nearly every request, including the one whose
add_episodes() started summarization, so a close() that cancelled it
would cancel summarization in nearly every request that starts one.

The #1517 regression test moves onto a shared gate_summarization()
helper with bounded waits and a 30 s deadlock timeout, and a wrong
comment in the message-length test is corrected.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013zM4AyQyFwwUv3RNmMKwgY
Signed-off-by: Zubiao Xiong <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Deadlock: concurrent search + add on the same session permanently wedges it (non-reentrant read lock)

2 participants