Repository navigation
Fix crash reusing connections when keepalive_timeout is None - #13757
Dreamsorcerer merged 7 commits into
Conversation
BaseConnector's own type signature allows keepalive_timeout=None as a value distinct from the sentinel default, and only rejects it when force_close=True is also set. With the default force_close=False, TCPConnector(keepalive_timeout=None) was accepted with no error, then crashed the first time a pooled connection got reused, since _get() compared the idle time against None directly. _cleanup()'s deadline math had the same bug, just never triggered because weakref_handle() happens to skip scheduling when the delay is None. Treat None as "never expire from idling" in both places, distinct from force_close=True which disables reuse entirely. Also stopped casting the constructor's keepalive_timeout to float, since that cast was hiding a real mypy error for this exact None case instead of handling it. Fixes aio-libs#13756. This is the same crash as aio-libs#786 from 2016, closed as fixed by aio-libs#907, but aio-libs#907 only validated the opposite combination (force_close=True with an explicit keepalive_timeout) and never touched force_close=False with keepalive_timeout=None, which is what was actually reported.
for more information, see https://pre-commit.ci
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #13757 +/- ##
==========================================
+ Coverage 99.04% 99.06% +0.01%
==========================================
Files 135 135
Lines 51602 52467 +865
Branches 2697 2733 +36
==========================================
+ Hits 51108 51975 +867
+ Misses 371 370 -1
+ Partials 123 122 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
Merging this PR will not alter performance
Comparing Footnotes
|
…mlink Document the None behavior in the client reference and in each connector class's docstring, and add the PR-numbered changelog symlink alongside the issue-numbered fragment per this repo's towncrier convention.
|
On the linked issue, Asveltov mention:
#786 (comment) |
|
Thanks for raising this. But, Asvetlov's comment is from before Checked this more closely: What actually broke is the other combo: Making the constructor reject If you actually want to deprecate that meaning of |
|
@ishan-1010 I think your argument is valid, and that
|
|
That's exactly what this PR documents, "infinite". See the docstring change on It matches the existing type signature ( |
|
@aiolibsbot review |
Do you think we shouldn't support an infinite timeout? I'd read keepalive_timeout=None as there being no timeout for keepalive, so that's the most sensible option for it to mean. But, I'm less clear whether such a feature should be supported or deprecated (maybe there's git history identifying the reason None was introduced?). |
Fail-fast would break existing behaviour. Overloads would also contradict |
PR Review — fix(connector): handle keepalive_timeout=None without force_closeCorrect, well-scoped fix for a genuine crash — blocked only on @Dreamsorcerer's The diagnosis is right and the research behind it is unusually thorough: the PR identifies that
On the @Polandia94 thread: the author's position holds up against the code. Process nit, not a finding: the commit subjects and PR title use conventional-commit prefixes ( 🟢 Suggestions
1. Docstring overstates how a disconnected pooled connection is reaped under `keepalive_timeout=None`
|
|
thanks, pushed the assert and the other cleanups (88d9123). re history: |
_cleanup() is only scheduled through weakref_handle(), which skips a None timeout, so assert instead of guarding a branch that cannot run. Replace the test of that impossible state with one checking that releasing a connection with keepalive_timeout=None schedules no cleanup. Reword the docstrings so they do not imply disconnected pooled connections are reaped proactively, spell the docs type as float | None, and drop comments that restated the docstring.
88d9123 to
d977f36
Compare
…eout-none-connector-crash # Conflicts: # CONTRIBUTORS.txt
I think it's the mose sensible option to mean, and I'm in favour of being supported. I was just trying to be sure that this is not a behaviour change dressed as a bugfix. I'm a +1 on merge this |
Co-authored-by: Sam Bull <aa6bs0@sambull.org>
Backport to 3.15: 💔 cherry-picking failed — conflicts found❌ Failed to cleanly apply 48f7b3d on top of patchback/backports/3.15/48f7b3d20cd663a09d22305e55d1ce9f8833a9a1/pr-13757 Backporting merged PR #13757 into master
🤖 @patchback |
Backport to 3.14: 💔 cherry-picking failed — conflicts found❌ Failed to cleanly apply 48f7b3d on top of patchback/backports/3.14/48f7b3d20cd663a09d22305e55d1ce9f8833a9a1/pr-13757 Backporting merged PR #13757 into master
🤖 @patchback |

Fixes #13756.
BaseConnector(keepalive_timeout=None)with the defaultforce_close=Falsecrashed on the first reused connection, since_get()compares idle time againstNonedirectly (float <= None)._cleanup()has the same bug in its deadline math, just masked becauseweakref_handle()silently skips scheduling when the delay isNone.Treats
Noneas "never expire from idling" in both places, distinct fromforce_close=Truewhich turns off reuse entirely, matching what the constructor's own type signature already implies.Also dropped the
cast(float, keepalive_timeout)on the attribute assignment, it was hiding a real mypy error for this exact case instead of handling it. There was a previous attempt at this in #907 for #786 back in 2016, but it only covered the opposite combination (force_close=Truewith an explicitkeepalive_timeout) and never actually tested the case that got reported.Added two regression tests that fail on the old code with the exact
TypeErrorand pass with the fix, plus one confirming the constructor accepts and keepsNonefor this combination without raising.Checklist
CONTRIBUTORS.txtCHANGESfolder