Skip to content

[Python] Fix AsyncIO Server maximum_concurrent_rpcs enforcement preventing negative active_rpcs count#41532

Closed
robinvd wants to merge 8 commits intogrpc:masterfrom
robinvd:fix-concurrent-rpc-limiter-underflow
Closed

[Python] Fix AsyncIO Server maximum_concurrent_rpcs enforcement preventing negative active_rpcs count#41532
robinvd wants to merge 8 commits intogrpc:masterfrom
robinvd:fix-concurrent-rpc-limiter-underflow

Conversation

@robinvd
Copy link
Copy Markdown
Contributor

@robinvd robinvd commented Feb 2, 2026

Summary

The _ConcurrentRpcLimiter only increments _active_rpcs when a request is accepted, but decrease_once_finished() was always called for all requests. This caused the counter to go negative when rejected requests finished, effectively disabling the maximum_concurrent_rpcs limit.

Fix

Only register the decrement callback for requests that were actually counted (when concurrency_exceeded is False).

Fixes #41531

@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla Bot commented Feb 2, 2026

CLA Signed

The committers listed above are authorized under a signed CLA.

@robinvd robinvd force-pushed the fix-concurrent-rpc-limiter-underflow branch from c3055e0 to e60a413 Compare February 2, 2026 17:30
…rent RPC limit

The _ConcurrentRpcLimiter only increments _active_rpcs when a request
is accepted, but decrease_once_finished() was always called for all
requests. This caused the counter to go negative when rejected requests
finished, effectively disabling the maximum_concurrent_rpcs limit.

Only register the decrement callback for requests that were actually
counted (when concurrency_exceeded is False).
@robinvd robinvd force-pushed the fix-concurrent-rpc-limiter-underflow branch from e60a413 to 5d5e25c Compare February 2, 2026 18:10
@sergiitk sergiitk added kokoro:run release notes: yes Indicates if PR needs to be in release notes labels Feb 3, 2026
Copy link
Copy Markdown
Member

@asheshvidyut asheshvidyut left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

rpc_task.add_done_callback(rpc_tasks.discard)

if self._limiter is not None:
if self._limiter is not None and not concurrency_exceeded:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for other reviewers -

If concurrency_exceeded is True above, that means active_rps would not have incremented.
concurrency_exceeded - False means - acitve_rpcs incremented -> so we need to decrement it as well.

If it has not incremented, we should not decrement the active_rpcs count for the current rpc.

So this change looks correct to me.

Also another point is that we could also add a check inside _decrease_active_rpcs_count but that would be incorrect because - rpcs can finish at different times, and the one rpc which caused the active_rpcs count to increase should be the one which should decrease it on finish.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i agree that this is a slightly confusing setup, likely why the original bug was there. But without significant overhaul this seems to be the best diff

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, this makes sense.

Comment thread src/python/grpcio_tests/tests_aio/unit/server_test.py Outdated
@sergiitk
Copy link
Copy Markdown
Member

sergiitk commented Feb 4, 2026

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly fixes a counter underflow bug in _ConcurrentRpcLimiter that could bypass the concurrent RPC limit. The change ensures the counter is only decremented for requests that were actually accepted, which is the right approach. The newly added regression test is well-designed and effectively verifies the fix. I've included a couple of suggestions to enhance the clarity and maintainability of the new test code.

Comment thread src/python/grpcio_tests/tests_aio/unit/server_test.py
Comment thread src/python/grpcio_tests/tests_aio/unit/server_test.py Outdated
Co-authored-by: Ashesh Vidyut <134911583+asheshvidyut@users.noreply.github.com>
@robinvd
Copy link
Copy Markdown
Contributor Author

robinvd commented Feb 5, 2026

The gemini suggestions seem fine to me, ive enabled Allow edits by maintainers so feel free to apply the suggestions if you agree :)

asheshvidyut and others added 2 commits February 6, 2026 15:04
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@asheshvidyut
Copy link
Copy Markdown
Member

@robinvd pytype checks are failing. Kindly fix or let us know if you want us to fix it in this branch.

…ate_task

asyncio.create_task() expects a coroutine, but UnaryUnaryCall is only
awaitable. Restore the coro_wrapper usage that was removed by a previous
suggestion.
@robinvd robinvd force-pushed the fix-concurrent-rpc-limiter-underflow branch from 204657a to 85572c4 Compare February 9, 2026 08:53
@robinvd
Copy link
Copy Markdown
Contributor Author

robinvd commented Feb 9, 2026

@robinvd pytype checks are failing. Kindly fix or let us know if you want us to fix it in this branch.

@asheshvidyut ive update the code, and the type err should now be fixed! if there is anything else small feel free to edit yourself!

@asheshvidyut asheshvidyut changed the title [Python] Fix _ConcurrentRpcLimiter counter underflow bypassing concurrent RPC limit [Python] Fix AsyncIO Server maximum_concurrent_rpcs enforcement preventing negative active_rpcs count Feb 10, 2026
Copy link
Copy Markdown
Member

@sergiitk sergiitk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@robinvd - thanks for the contribution, good catch!

I'm approving this, but please take a look at the comment about sleeping in the test. Unless I'm missing something, we don't need it.

Comment thread src/python/grpcio_tests/tests_aio/unit/server_test.py Outdated
Comment thread src/python/grpcio_tests/tests_aio/unit/server_test.py
Comment on lines +668 to +670
# Wait for all requests to fully complete
await asyncio.sleep(test_constants.SHORT_TIMEOUT)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Wait for all requests to fully complete
await asyncio.sleep(test_constants.SHORT_TIMEOUT)

Is there a reason we need to sleep here? Didn't we wait on all requests to complete and even counted their statuses?

This just adds 4 seconds of wait time:

# with
Ran 1 test in 8.042s
# without
Ran 1 test in 4.044s

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergiitk I think this is fine. If we remove the sleep, then the test might go flaky.

Reason being when client receives the OK status, then at that moment, server might not have completed the scheduled job of decrementing the active RPC counter.

rpc_task.add_done_callback(self._decrease_active_rpcs_count)

The above line is scheduled on the event loop, but might not neccessarily had run already, when client receives the reply.

The sleep is to make sure wave 2 is successful. Without sleep wave 2 test will start immediately, and will impact the test flow.

Co-authored-by: Sergii Tkachenko <sergiitk@google.com>
Zgoda91 pushed a commit to Zgoda91/grpc that referenced this pull request Mar 22, 2026
…nting negative active_rpcs count (grpc#41532)

## Summary

The `_ConcurrentRpcLimiter` only increments `_active_rpcs` when a request is accepted, but `decrease_once_finished()` was always called for all requests. This caused the counter to go negative when rejected requests finished, effectively disabling the `maximum_concurrent_rpcs` limit.

## Fix

Only register the decrement callback for requests that were actually counted (when `concurrency_exceeded` is `False`).

Fixes grpc#41531

Closes grpc#41532

COPYBARA_INTEGRATE_REVIEW=grpc#41532 from robinvd:fix-concurrent-rpc-limiter-underflow 5047e25
PiperOrigin-RevId: 876177526
asheshvidyut pushed a commit to asheshvidyut/grpc that referenced this pull request Mar 26, 2026
…nting negative active_rpcs count (grpc#41532)

## Summary

The `_ConcurrentRpcLimiter` only increments `_active_rpcs` when a request is accepted, but `decrease_once_finished()` was always called for all requests. This caused the counter to go negative when rejected requests finished, effectively disabling the `maximum_concurrent_rpcs` limit.

## Fix

Only register the decrement callback for requests that were actually counted (when `concurrency_exceeded` is `False`).

Fixes grpc#41531

Closes grpc#41532

COPYBARA_INTEGRATE_REVIEW=grpc#41532 from robinvd:fix-concurrent-rpc-limiter-underflow 5047e25
PiperOrigin-RevId: 876177526
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lang/Python release notes: yes Indicates if PR needs to be in release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Python aio] maximum_concurrent_rpcs limit bypassed due to underflow in _ConcurrentRpcLimiter

5 participants