-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Release context.lock before the protected request is sent #3252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |||||||
| from unittest import mock | ||||||||
| from urllib.parse import parse_qs, quote, unquote, urlparse | ||||||||
|
|
||||||||
| import anyio | ||||||||
| import httpx2 | ||||||||
| import pytest | ||||||||
| from inline_snapshot import Is, snapshot | ||||||||
|
|
@@ -3253,3 +3254,44 @@ async def echo_callback() -> AuthorizationCodeResult: | |||||||
| await auth_flow.asend(httpx2.Response(200, request=final_req)) | ||||||||
| except StopAsyncIteration: | ||||||||
| pass | ||||||||
|
|
||||||||
|
|
||||||||
| @pytest.mark.anyio | ||||||||
| async def test_in_flight_request_does_not_block_a_concurrent_request( | ||||||||
| oauth_provider: OAuthClientProvider, valid_tokens: OAuthToken | ||||||||
| ): | ||||||||
| """A request still in flight must not hold up the next one on the same provider. | ||||||||
|
|
||||||||
| The standalone GET SSE stream lives as long as the server keeps it open, so holding | ||||||||
| ``context.lock`` until its response arrived stalled the first ``tools/call`` for that | ||||||||
| whole time (#3209). | ||||||||
| """ | ||||||||
| oauth_provider.context.current_tokens = valid_tokens | ||||||||
| oauth_provider.context.token_expiry_time = time.time() + 1800 | ||||||||
| oauth_provider._initialized = True | ||||||||
|
|
||||||||
| sse_sent = anyio.Event() | ||||||||
| call_done = anyio.Event() | ||||||||
|
|
||||||||
| async def get_sse_stream() -> None: | ||||||||
| flow = oauth_provider.async_auth_flow(httpx2.Request("GET", "https://api.example.com/v1/mcp")) | ||||||||
| request = await flow.__anext__() | ||||||||
| sse_sent.set() | ||||||||
| # The server holds the stream open, so the response lands after the call is answered. | ||||||||
| await call_done.wait() | ||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: This new concurrency test can hang indefinitely because it waits on Prompt for AI agents
Suggested change
|
||||||||
| with pytest.raises(StopAsyncIteration): | ||||||||
| await flow.asend(httpx2.Response(200, request=request)) | ||||||||
|
|
||||||||
| async def call_tool() -> None: | ||||||||
| await sse_sent.wait() | ||||||||
| flow = oauth_provider.async_auth_flow(httpx2.Request("POST", "https://api.example.com/v1/mcp")) | ||||||||
| with anyio.fail_after(5): | ||||||||
| request = await flow.__anext__() | ||||||||
| assert request.headers["Authorization"] == "Bearer test_access_token" | ||||||||
| with pytest.raises(StopAsyncIteration): | ||||||||
| await flow.asend(httpx2.Response(200, request=request)) | ||||||||
| call_done.set() | ||||||||
|
|
||||||||
| async with anyio.create_task_group() as tg: | ||||||||
| tg.start_soon(get_sse_stream) | ||||||||
| tg.start_soon(call_tool) | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Concurrent requests can make a 401/403 re-authorization use another request’s
MCP-Protocol-Version, which can flipresourceparameter inclusion and build incorrect OAuth requests. This comes from dropping the lock beforeyield requestwhile keepingprotocol_versionas shared mutable context; a per-request version should be carried through re-auth.Prompt for AI agents