Skip to content

Conversation

@shaunpatterson
Copy link
Contributor

Summary

Fixes a deadlock in the async client when an error occurs during do_request().

Root Cause: When an error occurs in do_request(), the code calls self.discard() while still holding self._lock. Since discard() also tries to acquire self._lock and asyncio.Lock is not reentrant, this causes a deadlock.

The deadlock scenario:

  1. do_request() acquires self._lock (line 171)
  2. A gRPC error occurs (e.g., schema error, missing index)
  3. Error handler calls await self.discard() (line 221)
  4. discard() tries to acquire self._lock (line 460)
  5. DEADLOCK - the lock is already held by do_request()

Fix

  • Extract discard logic into _discard_internal() which doesn't acquire the lock
  • Update do_request() error handler to call _discard_internal() directly
  • Update public discard() to acquire lock then delegate to _discard_internal()
  • Add defensive assertion to verify lock is held when calling internal method

Testing

Tested by reproducing the deadlock scenario:

  • Query with variables against a predicate without an index triggers a schema error
  • Error handler previously deadlocked; now properly discards and raises the error

Validation

This fix was reviewed by GPT-5 and Gemini 2.5 Pro, both confirmed:

  1. The fix correctly addresses the asyncio.Lock deadlock
  2. No new race conditions are introduced
  3. The naming convention (_discard_internal) is idiomatic Python
  4. The defensive assertion is good practice

Generated with Claude Code

When an error occurs in do_request(), the code was calling self.discard()
while still holding self._lock. Since discard() also tries to acquire
self._lock and asyncio.Lock is not reentrant, this caused a deadlock.

The fix:
- Extract discard logic into _discard_internal() which doesn't acquire lock
- Update do_request() error handler to call _discard_internal() directly
- Update public discard() to acquire lock then delegate to _discard_internal()
- Add defensive assertion to verify lock is held when calling internal method

This was discovered when using the async client with queries that triggered
schema errors - the error handling path would deadlock indefinitely.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@shaunpatterson shaunpatterson requested a review from a team as a code owner February 2, 2026 22:46
Minor formatting fixes:
- Add missing blank lines after docstrings
- Consolidate multi-line function signature to single line

Co-Authored-By: Claude Opus 4.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant