Normalize asyncio writer close races - #4217
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2132282bc4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| except (AttributeError, TypeError) as error: | ||
| raise ConnectionError("Connection closed while writing") from error |
There was a problem hiding this comment.
Preserve non-I/O TypeErrors from writes
When a caller passes a malformed packed iterable, such as a custom generator or list containing non-bytes, StreamWriter.writelines() raises TypeError before any writer-close race is involved; this broad catch now turns that caller/data error into ConnectionError("Connection closed while writing"), disconnects, and can let retry logic re-run a non-transient bad command instead of surfacing the original error. Please only translate the specific closed-writer failure and let unrelated TypeErrors propagate as before.
AGENTS.md reference: AGENTS.md:L120-L123
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in d7dd61c. The inner write wrapper now translates only AttributeError from a closed writer; caller/data TypeError values are allowed to propagate unchanged. Added a malformed packed-command regression test. The asyncio connection suite passes (67 passed, 1 skipped).
|
Please make sure resolve Codex comments first |
| try: | ||
| writer.writelines(command) | ||
| await writer.drain() | ||
| except (AttributeError, TypeError) as error: |
There was a problem hiding this comment.
the AttributeError/TypeError catch is broader than the None-writer case: pack_command aside, send_packed_command takes a caller-supplied iterable, so a bad element (str in the list) raises TypeError from writelines and now surfaces as ConnectionError, which retry.call_with_retry treats as retryable. that turns a programming error into a silent reconnect loop. since you already null-check writer above, is the except clause still buying anything beyond the writer-set-to-None-mid-await race?
There was a problem hiding this comment.
Addressed in d7dd61c. The broad TypeError catch was removed, so a caller-supplied invalid packed iterable is no longer reported as ConnectionError. The existing closed-writer coverage now models the AttributeError path explicitly.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Reviewed by Cursor Bugbot for commit d7dd61c. Configure here.
| writer.writelines(command) | ||
| await writer.drain() | ||
| except AttributeError as error: | ||
| raise ConnectionError("Connection closed while writing") from error |
There was a problem hiding this comment.
TypeError from transport close race not caught
High Severity
The PR's stated purpose is to convert writer-close TypeError failures into ConnectionError so the retry mechanism can handle them, but _send_packed_command only catches AttributeError, not TypeError. The transport-close-race TypeError (the core problem from issue #3546) passes through uncaught, hits the except BaseException handler in send_packed_command, and is re-raised as a raw TypeError. Since the retry mechanism's supported_errors only includes ConnectionError and TimeoutError, the TypeError is still not retryable — leaving the original bug unfixed.
Reviewed by Cursor Bugbot for commit d7dd61c. Configure here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d7dd61c5b1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| except AttributeError as error: | ||
| raise ConnectionError("Connection closed while writing") from error |
There was a problem hiding this comment.
Handle the actual closed-transport TypeError
For the reported Python 3.12 close race, the traceback in #3546 fails inside StreamWriter.writelines() as TypeError: 'NoneType' object is not callable, but this new handler only translates AttributeError; the actual failure still escapes as a raw TypeError and bypasses redis-py's ConnectionError retry path. Please classify that specific closed-transport TypeError without converting arbitrary caller/data TypeErrors.
Useful? React with 👍 / 👎.


Fixes #3546
Problem
When an asyncio transport is closed concurrently with a write, Python can raise a low-level
TypeErrorfromStreamWriter.writelines()after its internal_write_readycallback has been cleared. redis-py currently lets that rawTypeErrorescape, so the existing retry policy cannot recognize the broken connection as retryable.Solution
AttributeError/TypeErrorfailures into redis-py'sConnectionError.Tests
socket_timeout=Noneand finitesocket_timeoutpaths.invoke linters,compileall, andgit diff --checkpassed.The local integration skips are unchanged and are unrelated to this focused writer error path.
Note
Low Risk
Narrow change to asyncio write error normalization on the connection object, with targeted regression tests and no auth or data-path changes.
Overview
Fixes a race where concurrent transport close during an asyncio write could surface a raw
AttributeErrorfromStreamWriter.writelines()instead of a connection failure redis-py can treat as retryable._send_packed_commandnow holds a local writer reference, raisesConnectionError("Connection closed while writing")when the writer is already gone orwritelines/drainfails withAttributeError, andsend_packed_commandroutes both timed and untimed writes through that helper so behavior matches. Failures still go through the existingdisconnect(nowait=True)path via the outerBaseExceptionhandler.Adds async tests for the closed-writer case (with and without
socket_timeout) and confirms unrelatedTypeErrorvalues are not rewritten toConnectionError.Reviewed by Cursor Bugbot for commit d7dd61c. Bugbot is set up for automated code reviews on this repo. Configure here.