Skip to content

fix: guard ensure_channel_ready() against a concurrent-caller race - #3776

Open
mittalpk wants to merge 1 commit into
milvus-io:masterfrom
mittalpk:fix/ensure-channel-ready-concurrency-race
Open

fix: guard ensure_channel_ready() against a concurrent-caller race#3776
mittalpk wants to merge 1 commit into
milvus-io:masterfrom
mittalpk:fix/ensure-channel-ready-concurrency-race

Conversation

@mittalpk

Copy link
Copy Markdown

Fixes #3030.

ensure_channel_ready() checked self._is_channel_ready and, if False, awaited _setup_identifier_interceptor_for_channel() before setting it — with no lock. Every concurrent caller racing in before the first one finished independently awaited the same setup call, which appends a new interceptor to the shared channel's interceptor chain on every call. Enough concurrent callers (the report's own repro: ~490 concurrent requests) stack enough interceptors to blow Python's default recursion limit on a later RPC, since gRPC's interceptor dispatch recurses one frame per interceptor — exactly matching the reported RecursionError and its concurrency threshold.

Guards the setup with the same _reconnect_lock that reconnect() and close() already use to serialize channel-state mutation, with a double-checked _is_channel_ready read so the common already-ready path never takes the lock.

Testing

  • New regression test spins up 20 concurrent ensure_channel_ready() calls against a mocked setup function and asserts it's only invoked once. Confirmed it fails against unpatched code (20 calls, not 1) via git stash isolation before confirming it passes here.
  • Full unit suite: 4720 passed, 3 skipped, 0 regressions.
  • ruff check / ruff format --check / black --check all clean.

@sre-ci-robot

Copy link
Copy Markdown

Welcome @mittalpk! It looks like this is your first PR to milvus-io/pymilvus 🎉

@mergify mergify Bot added the needs-dco label Aug 25, 2026
@mittalpk
mittalpk marked this pull request as ready for review August 25, 2026 20:28
@mittalpk

Copy link
Copy Markdown
Author

/assign @longjiquan

@mittalpk

Copy link
Copy Markdown
Author

/assign @longjiquan

# recursion limit on a later RPC (interceptor dispatch recurses one
# frame per interceptor). _reconnect_lock is the same lock reconnect()
# and close() already use to serialize channel-state mutation.
async with self._reconnect_lock:

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.

pymilvus/client/async_grpc_handler.py line:319
Low ---- The setup now runs while holding _reconnect_lock, so during a connection burst only the first caller's timeout applies to the shared Connect RPC and every other concurrent caller waits on the lock for up to that duration (default 10s) with their own timeout argument ignored on that wait; close() and reconnect() block for the same period because they share this lock. This is the intended trade-off of the fix, but a brief doc note on the method (or an overall deadline around the lock+setup) would make the queued-caller behavior explicit for users that pass small timeouts.

if self._is_channel_ready:
return

# Without the lock, many coroutines can race in here concurrently

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.

pymilvus/client/async_grpc_handler.py line:310
Low ---- Question: the linked issue #3030 is still open but carries the wontfix label (added by XuanYang-cn on 2026-01-05), while this PR says "Fixes #3030" and the commit says "Closes #3030". Please confirm with the maintainers that the wontfix decision is being reversed so the issue state and this fix are consistent at merge time.

@yhmo

yhmo commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

pymilvus/client/async_grpc_handler.py line:304
Medium ---- The DCO check is failing on this commit (the PR carries the needs-dco label and the DCO check-run at head 51d9489 reports action_required): the head commit has no Signed-off-by trailer. milvus-io requires a sign-off on every commit before merge, so please amend the commit (git commit --amend -s) and force-push to unblock merge.

ensure_channel_ready() checked self._is_channel_ready and, if False,
awaited _setup_identifier_interceptor_for_channel() before setting it --
with no lock. Every concurrent caller racing in before the first one
finished independently awaited the same setup call, which appends a new
interceptor to the shared channel's interceptor chain on every call.
Enough concurrent callers (a burst of ~490 requests reliably reproduced
it) stack enough interceptors to blow Python's default recursion limit on
a later RPC, since gRPC's interceptor dispatch recurses one frame per
interceptor.

Guard the setup with the same _reconnect_lock reconnect() and close()
already use to serialize channel-state mutation, with a double-checked
_is_channel_ready read so the common already-ready path never takes the
lock.

Closes milvus-io#3030

Signed-off-by: Praveen Mittal <pkmittal28@gmail.com>
@mittalpk
mittalpk force-pushed the fix/ensure-channel-ready-concurrency-race branch from 51d9489 to 38dcfec Compare August 27, 2026 08:04
@sre-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: mittalpk
To complete the pull request process, please ask for approval from longjiquan after the PR has been reviewed.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@mergify mergify Bot added dco-passed and removed needs-dco labels Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: High concurrent number of requests causes RecursionError

4 participants