Fix crash reusing connections when keepalive_timeout is None by ishan-1010 · Pull Request #13757 · aio-libs/aiohttp · GitHub
Skip to content

Fix crash reusing connections when keepalive_timeout is None - #13757

Merged
Dreamsorcerer merged 7 commits into
aio-libs:masterfrom
ishan-1010:fix/keepalive-timeout-none-connector-crash
Oct 2, 2026
Merged

Dreamsorcerer merged 7 commits into
aio-libs:masterfrom
ishan-1010:fix/keepalive-timeout-none-connector-crash

Conversation

@ishan-1010

Copy link
Copy Markdown
Contributor

Fixes #13756.

BaseConnector(keepalive_timeout=None) with the default force_close=False crashed on the first reused connection, since _get() compares idle time against None directly (float <= None). _cleanup() has the same bug in its deadline math, just masked because weakref_handle() silently skips scheduling when the delay is None.

Treats None as "never expire from idling" in both places, distinct from force_close=True which 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=True with an explicit keepalive_timeout) and never actually tested the case that got reported.

Added two regression tests that fail on the old code with the exact TypeError and pass with the fix, plus one confirming the constructor accepts and keeps None for this combination without raising.

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes
  • If you provide code modification, please add yourself to CONTRIBUTORS.txt
  • Add a new news fragment into the CHANGES folder

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.
@pyba-chronographer pyba-chronographer Bot added the bot:chronographer:provided There is a change note present in this PR label Sep 20, 2026
@greptile-apps

greptile-apps Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Comment thread aiohttp/connector.py Outdated
Comment thread CHANGES/13756.bugfix.rst
@codecov

codecov Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.06%. Comparing base (ce960dd) to head (ad48c7a).
⚠️ Report is 19 commits behind head on master.
✅ All tests successful. No failed tests found.

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     
Flag Coverage Δ
Autobahn 21.82% <11.11%> (-0.07%) ⬇️
CI-GHA 98.89% <100.00%> (+0.02%) ⬆️
OS-Linux 98.68% <100.00%> (+0.02%) ⬆️
OS-Windows 97.34% <100.00%> (+0.05%) ⬆️
OS-macOS 98.19% <100.00%> (+0.04%) ⬆️
Py-3.10 98.11% <100.00%> (+0.01%) ⬆️
Py-3.11 98.35% <100.00%> (+0.03%) ⬆️
Py-3.12 98.44% <100.00%> (+0.03%) ⬆️
Py-3.13 98.44% <100.00%> (+0.04%) ⬆️
Py-3.14 98.47% <100.00%> (+0.04%) ⬆️
Py-3.15 98.47% <100.00%> (+0.04%) ⬆️
Py-3.15t 97.85% <100.00%> (+0.04%) ⬆️
Py-pypy-3.12 96.47% <100.00%> (-0.04%) ⬇️
VM-macos 98.19% <100.00%> (+0.04%) ⬆️
VM-ubuntu 98.68% <100.00%> (+0.02%) ⬆️
VM-windows 97.34% <100.00%> (+0.05%) ⬆️
cython-coverage 83.71% <100.00%> (+0.40%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@codspeed

codspeed Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 97 untouched benchmarks
⏩ 83 skipped benchmarks1


Comparing ishan-1010:fix/keepalive-timeout-none-connector-crash (ad48c7a) with master (abde3a8)2

Open in CodSpeed

Footnotes

  1. 83 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

  2. No successful run was found on master (45520f0) during the generation of this report, so abde3a8 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

…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.
@Polandia94

Copy link
Copy Markdown
Contributor

On the linked issue, Asveltov mention:

keepalive_timeout=None should imply force_close=True

#786 (comment)
On this PR, as far as I understand, you are allowing force_close=False, and using None as never expiring. Probably is better to fail fast if keepalive_timeout=None and force_close=True, and disallow it on typing side with an overload?

@ishan-1010

ishan-1010 commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for raising this. But, Asvetlov's comment is from before None was even an option here. The type hints have said Optional[float] for keepalive_timeout for years now, completely separate from force_close.

Checked this more closely: force_close=True with an explicit keepalive_timeout=None doesn't raise today. Only an explicit non-None value does (if keepalive_timeout is not None and keepalive_timeout is not sentinel: raise ...), that check's been there since #907 in 2016 and this PR doesn't touch it either way. So force_close=True + keepalive_timeout=None currently passes through silently as a no-op, since keepalive_timeout is unused whenever force_close=True. Making that combo raise would be new, stricter validation, not something already enforced.

What actually broke is the other combo: force_close=False (the default) with keepalive_timeout=None passed explicitly. The constructor happily takes it since the types allow it, then it blows up on the first reused connection because _get() does t1 - t0 <= None under the hood.

Making the constructor reject None now, or silently flip it to force_close=True, would be a behavior change, not a bug fix, plus it'd break anyone already relying on None meaning "never expire from idling," since that's what the type signature has promised for a long time.

If you actually want to deprecate that meaning of None, that's worth its own issue with a proper deprecation path. Can open one if there's interest.

@Polandia94 Polandia94 added backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot backport-3.15 Trigger automatic backporting to the 3.15 release branch by Patchback robot labels Sep 24, 2026
@Polandia94

Copy link
Copy Markdown
Contributor

@ishan-1010 I think your argument is valid, and that keepalive_timeout=None should be interpreted as infinite time. But this is not documented anywhere AFAIK. I think we need a explicit decision on what keepalive_timeout=None mean between:

  • Same as infinite
  • Same as 0
  • Same as sentinel
  • As currently is not working, should be deprecated

@ishan-1010

ishan-1010 commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor Author

That's exactly what this PR documents, "infinite". See the docstring change on BaseConnector and the client_reference.rst update: "None means a reusable connection never expires from idling alone." That's the explicit decision being proposed.

It matches the existing type signature (Optional[float]), which has allowed None for years, this PR is what finally gives that value defined behavior instead of a crash.

Comment thread aiohttp/connector.py Outdated
@Dreamsorcerer

Copy link
Copy Markdown
Member

@aiolibsbot review

@Dreamsorcerer

Copy link
Copy Markdown
Member

@ishan-1010 I think your argument is valid, and that keepalive_timeout=None should be interpreted as infinite time. But this is not documented anywhere AFAIK. I think we need a explicit decision on what keepalive_timeout=None mean between:

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?).

@aiolibsbot

Copy link
Copy Markdown
Contributor

@Polandia94: On the linked issue, Asveltov mention:

Fail-fast would break existing behaviour. tests/test_connector.py:3836 already constructs BaseConnector(force_close=True, keepalive_timeout=None) and expects no error. Only explicit non-None values raise (connector.py:342).

Overloads would also contradict TCPConnector.__init__ signature at connector.py:1029, which accepts None | float | _SENTINEL today. Separate deprecation, separate PR.

@aiolibsbot

aiolibsbot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

PR Review — fix(connector): handle keepalive_timeout=None without force_close

Correct, well-scoped fix for a genuine crash — blocked only on @Dreamsorcerer's assert request in _cleanup(), which is still unaddressed at 4445187.

The diagnosis is right and the research behind it is unusually thorough: the PR identifies that _get() is the crash site, that _cleanup() carries the same latent bug masked by weakref_handle() no-op'ing on a None delay, and that the 2016 fix in #907 only ever covered the mirror-image combination. Removing cast(float, keepalive_timeout) rather than keeping it is the right instinct — that cast was the reason mypy never caught this. The two _get/_cleanup regression tests fail on old code for the exact reported TypeError, docs and changelog follow AGENTS.md (issue-numbered fragment plus the 13757 -> 13756 symlink, verified as a real symlink), and CONTRIBUTORS.txt is alphabetical.

  • 🟡 _cleanup() (connector.py:482-494) keeps a deadline is None guard where the maintainer asked for an assert. I verified his premise: both scheduling sites pass self._keepalive_timeout to weakref_handle(), which returns without scheduling for a None delay, and the attribute is never mutated after __init__ — so the branch is unreachable. Switching to the assert also retires test_cleanup_never_expires_with_keepalive_timeout_none; the invariant to test instead is conn._cleanup_handle is None after _release().
  • 🟢 The new docstring's "still dropped if the underlying socket disconnects" implies proactive reaping that does not happen under None — dead pooled protos linger until that key is next requested. Notably weakens enable_cleanup_closed=True on Python 3.13.x.
  • 🟢 docs/client_reference.rst:1103 still says :param float, and the replaced text's mention of 0 vanished — 0 means "pooled but never reused", distinct from both None and the default.
  • 🟢 Eight lines of comment around the two constructor assignments restate the docstring; the nested is sentinel check inside the force_close branch can collapse to an unconditional = None.

On the @Polandia94 thread: the author's position holds up against the code. tests/test_connector.py:3836 already asserts BaseConnector(force_close=True, keepalive_timeout=None) constructs cleanly today, so fail-fast there would be a breaking change, not this bugfix.

Process nit, not a finding: the commit subjects and PR title use conventional-commit prefixes (fix(connector):, docs(connector):) while AGENTS.md asks for plain imperative subjects matching recent history ("Bound websocket client buffers a peer can fill"). Since the PR title becomes the squash subject, worth renaming before merge.


🟢 Suggestions

1. Docstring overstates how a disconnected pooled connection is reaped under `keepalive_timeout=None`
aiohttp/connector.py:308-311

"it is still dropped if the underlying socket disconnects" reads as though something proactively reaps it. Nothing does: with keepalive_timeout=None, weakref_handle() never schedules _cleanup (helpers.py:595), so a pooled ResponseHandler whose peer went away stays in self._conns until either _get() is called for that same key (the proto.is_connected() check at connector.py:791 discards it) or the connector is closed.

Two practical consequences worth being honest about in the prose:

  • A long-lived session with keepalive_timeout=None that touches many hosts retains dead protocol/transport objects for every key it never revisits. Bounded by the number of distinct keys, not unbounded per key, but it is real retention that the 15s default never exhibits.
  • enable_cleanup_closed=True combined with keepalive_timeout=None loses most of its effect on Python 3.13.x (NEEDS_CLEANUP_CLOSED, connector.py:77): dead SSL transports are only pushed onto _cleanup_closed_transports from _cleanup() and _get(), and _cleanup() never runs.

Suggested rewording of the parenthetical: "(a disconnected connection is discarded the next time the pool is consulted for that host, not proactively)". Same edit applies to the three copies at lines 990, 1704 and 1763.

    keepalive_timeout - (optional) Keep-alive timeout. None means a
        reusable connection never expires from idling alone (it is
        still dropped if the underlying socket disconnects). This is
        different from force_close=True, which disables reuse entirely.
2. Param is still typed `float` after `None` became meaningful, and the `0` mention was dropped
docs/client_reference.rst:1103-1109

The prose now documents None, which resolves greptile's point, but two smaller things are left over:

  • The role is still :param float keepalive_timeout:, so the rendered signature says float while None is now a documented, supported value. :param keepalive_timeout: with the type spelled in the text (or an explicit :type keepalive_timeout: float | None) matches what the constructor accepts.
  • The replaced sentence mentioned 0 (garbled as "Values 0."), and the new text drops it entirely. 0 is not equivalent to None or to the default: weakref_handle() skips scheduling for a non-positive delay and t1 - t0 <= 0 is essentially never true, so keepalive_timeout=0 means "pooled but never reused". Since this PR is the one pinning down the semantics of the parameter's edge values, saying that in a clause is cheap and stops the next reader from rediscovering it.

Also worth checking the class signature line at 1096, which still advertises keepalive_timeout=15 — not introduced here, just adjacent.

   :param float keepalive_timeout: timeout for connection reusing
                                   after releasing (optional). Set to
                                   ``None`` to reuse connections
                                   indefinitely, regardless of how long
                                   they've been idle. For disabling
                                   *keep-alive* feature entirely use
                                   ``force_close=True`` flag instead.
3. Comment volume outweighs the logic; the nested `sentinel` check can collapse
aiohttp/connector.py:345-380

Eight lines of comment for two assignments, and both restate what the class docstring four lines up now says. AGENTS.md is explicit: "No docstrings or comments that just restate the code."

Inside the force_close branch the value is provably None or sentinel (the other cases raised two lines earlier), so the inner if is redundant:

        if force_close:
            if keepalive_timeout is not None and keepalive_timeout is not sentinel:
                raise ValueError(
                    "keepalive_timeout cannot be set if force_close is True"
                )
            # Unused when force_close is True; pin it so the attribute stays float | None.
            keepalive_timeout = None
        elif keepalive_timeout is sentinel:
            keepalive_timeout = 15.0

and at line 380 the annotation carries the intent on its own — self._keepalive_timeout: float | None = keepalive_timeout with at most a one-liner. Dropping the cast() is the right call and worth keeping; it is the five-line justification that has no reader left to serve once the docstring states the semantics.

            # Connections are never pooled when force_close is True, so the
            # value is unused at runtime; normalize it so the attribute has
            # a consistent float | None type.
            if keepalive_timeout is sentinel:
                keepalive_timeout = None

Checklist

  • Fix addresses the reported crash on both affected code paths
  • New behaviour covered by tests
  • Tests assert observable behaviour, not source inspection
  • Documentation reflects the user-visible semantics — suggestion #1, suggestion #2
  • Changelog fragment present with issue + PR symlink and :user: attribution
  • No backward-incompatible change to public API
  • No resource leaks introduced on the hot path — suggestion #1
  • Change leaves surrounding code no messier — suggestion #3
  • Diff matches the PR description, no scope creep

Silent Failure Analysis

🟡 **4. MEDIUM** — silently disabled background cleanup / unreaped resources
aiohttp/connector.py:482-514

Risk: With keepalive_timeout=None, weakref_handle() is a no-op (helpers.py:595 requires timeout is not None and timeout > 0), so _cleanup is never scheduled from _release() or rescheduled from itself: dead pooled protocols and their half-open SSL transports are never reaped in the background and only get closed lazily if another request targets the same key, with no warning or diagnostic that periodic cleanup is off.

deadline = None if timeout is None else now - timeout
...
if self._conns:
    self._cleanup_handle = helpers.weakref_handle(
        self, "_cleanup", timeout, self._loop, ...)  # returns None when timeout is None

Fix: When _keepalive_timeout is None, still schedule _cleanup on a fixed interval (e.g. reuse _cleanup_closed_period or a default poll interval) so disconnected connections are pruned and SSL transports reach _cleanup_closed_transports, and/or document the trade-off in the constructor docstring.


Automated review by Kōan (Claude) HEAD=4445187 6 min 9s

@ishan-1010 ishan-1010 changed the title fix(connector): handle keepalive_timeout=None without force_close Fix crash reusing connections when keepalive_timeout is None Sep 29, 2026
@ishan-1010

ishan-1010 commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor Author

thanks, pushed the assert and the other cleanups (88d9123). re history: None first shows up in #907 (2016) as "not set" in the force_close check, then the 2018 "Annotate connector" commit copied it into the type with a cast(float, ...) that hid it from mypy. so nobody designed it as infinite. I went with "never expires" since that's how you read it, easy to flip if you'd rather reject it. I left 0 out of the docs on purpose, whether it reuses depends on the clock (t1 - t0 <= 0).

_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.
@ishan-1010
ishan-1010 force-pushed the fix/keepalive-timeout-none-connector-crash branch from 88d9123 to d977f36 Compare September 29, 2026 09:42
…eout-none-connector-crash

# Conflicts:
#	CONTRIBUTORS.txt
@Polandia94

Copy link
Copy Markdown
Contributor

@ishan-1010 I think your argument is valid, and that keepalive_timeout=None should be interpreted as infinite time. But this is not documented anywhere AFAIK. I think we need a explicit decision on what keepalive_timeout=None mean between:

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?).

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

Comment thread aiohttp/connector.py Outdated
Comment thread tests/test_connector.py Outdated
Comment thread tests/test_connector.py Outdated
Co-authored-by: Sam Bull <aa6bs0@sambull.org>
@Dreamsorcerer
Dreamsorcerer merged commit 48f7b3d into aio-libs:master Oct 2, 2026
94 of 99 checks passed
@patchback

patchback Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

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

  1. Ensure you have a local repo clone of your fork. Unless you cloned it
    from the upstream, this would be your origin remote.
  2. Make sure you have an upstream repo added as a remote too. In these
    instructions you'll refer to it by the name upstream. If you don't
    have it, here's how you can add it:
    git remote add upstream https://github.com/aio-libs/aiohttp.git
    
  3. Ensure you have the latest copy of upstream and prepare a branch
    that will hold the backported code:
    git fetch upstream
    git checkout -b patchback/backports/3.15/48f7b3d20cd663a09d22305e55d1ce9f8833a9a1/pr-13757 upstream/3.15
    
  4. Now, cherry-pick PR Fix crash reusing connections when keepalive_timeout is None #13757 contents into that branch:
    git cherry-pick -x 48f7b3d20cd663a09d22305e55d1ce9f8833a9a1
    
    If it'll yell at you with something like fatal: Commit 48f7b3d20cd663a09d22305e55d1ce9f8833a9a1 is a merge but no -m option was given., add -m 1 as follows instead:
    git cherry-pick -m1 -x 48f7b3d20cd663a09d22305e55d1ce9f8833a9a1
    
  5. At this point, you'll encounter some merge conflicts. You must
    resolve them in order to preserve the patch from PR Fix crash reusing connections when keepalive_timeout is None #13757 as close to the
    original as possible.
  6. Once conflicts are resolved and git added, run:
    git cherry-pick --continue
    
  7. Push this branch to your fork on GitHub:
    git push origin patchback/backports/3.15/48f7b3d20cd663a09d22305e55d1ce9f8833a9a1/pr-13757
    
  8. Create a PR, ensure that the CI is green. If it's not — update it so that
    the tests and any other checks pass. This is it!
    Now relax and wait for the maintainers to process your pull request
    when they have some cycles to do reviews. Don't worry — they'll tell you if
    any improvements are necessary when the time comes!

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

@patchback

patchback Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

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

  1. Ensure you have a local repo clone of your fork. Unless you cloned it
    from the upstream, this would be your origin remote.
  2. Make sure you have an upstream repo added as a remote too. In these
    instructions you'll refer to it by the name upstream. If you don't
    have it, here's how you can add it:
    git remote add upstream https://github.com/aio-libs/aiohttp.git
    
  3. Ensure you have the latest copy of upstream and prepare a branch
    that will hold the backported code:
    git fetch upstream
    git checkout -b patchback/backports/3.14/48f7b3d20cd663a09d22305e55d1ce9f8833a9a1/pr-13757 upstream/3.14
    
  4. Now, cherry-pick PR Fix crash reusing connections when keepalive_timeout is None #13757 contents into that branch:
    git cherry-pick -x 48f7b3d20cd663a09d22305e55d1ce9f8833a9a1
    
    If it'll yell at you with something like fatal: Commit 48f7b3d20cd663a09d22305e55d1ce9f8833a9a1 is a merge but no -m option was given., add -m 1 as follows instead:
    git cherry-pick -m1 -x 48f7b3d20cd663a09d22305e55d1ce9f8833a9a1
    
  5. At this point, you'll encounter some merge conflicts. You must
    resolve them in order to preserve the patch from PR Fix crash reusing connections when keepalive_timeout is None #13757 as close to the
    original as possible.
  6. Once conflicts are resolved and git added, run:
    git cherry-pick --continue
    
  7. Push this branch to your fork on GitHub:
    git push origin patchback/backports/3.14/48f7b3d20cd663a09d22305e55d1ce9f8833a9a1/pr-13757
    
  8. Create a PR, ensure that the CI is green. If it's not — update it so that
    the tests and any other checks pass. This is it!
    Now relax and wait for the maintainers to process your pull request
    when they have some cycles to do reviews. Don't worry — they'll tell you if
    any improvements are necessary when the time comes!

🤖 @patchback
I'm built with octomachinery and
my source is open — https://github.com/sanitizers/patchback-github-app.

@Dreamsorcerer

Copy link
Copy Markdown
Member

Dreamsorcerer pushed a commit that referenced this pull request Oct 4, 2026
…n keepalive_timeout is None (#13922)

**This is a backport of PR #13757 as merged into master
(48f7b3d).**
Dreamsorcerer pushed a commit that referenced this pull request Oct 4, 2026
…n keepalive_timeout is None (#13923)

**This is a backport of PR #13757 as merged into master
(48f7b3d).**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot backport-3.15 Trigger automatic backporting to the 3.15 release branch by Patchback robot bot:chronographer:provided There is a change note present in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BaseConnector: keepalive_timeout=None crashes on connection reuse (TypeError comparing float and NoneType)

4 participants