Skip to content

Detect event-loop blocking in tests - #3510

Merged
Kludex merged 9 commits into
mainfrom
worktree-bright-badger-tds7
Sep 16, 2026
Merged

Kludex merged 9 commits into
mainfrom
worktree-bright-badger-tds7

Conversation

@Kludex

@Kludex Kludex commented Sep 16, 2026

Copy link
Copy Markdown
Member

Summary

  • add BlockBuster to detect blocking calls from mcp and mcp_types
  • exempt coverage internals, jsonschema lazy imports, and synchronous media conversion
  • resolve Windows executables off the event loop

Tests

  • ./scripts/test
  • uv run --frozen ruff format --check .
  • uv run --frozen ruff check .
  • uv run --frozen pyright

AI Disclaimer

This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-16T08:36:03.335459Z dcc8afa PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dcc8afae72

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/mcp/client/session.py Outdated
@github-actions

github-actions Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Preview https://pr-3510.mcp-python-docs.pages.dev
Deployment https://55348ba4.mcp-python-docs.pages.dev
Commit 4cd5f41
Triggered by @Kludex
Updated 2026-09-16 09:58:37 UTC

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/mcp/client/session.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All reported issues were addressed across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/mcp/client/stdio.py Outdated

@claude claude Bot left a comment

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.

Beyond the inline findings, I also checked two things in validate_tool_result and ruled them out: a concurrent list_tools during the off-loop compile cannot poison the cache (the _same_schema guard skips caching, and the in-flight call still validates against the schema it read before the await), and moving the structured_content is None check ahead of compilation only changes which RuntimeError wins when a tool has both an invalid schema and no structured content — same exception type, so callers catching it are unaffected.

Extended reasoning...

Findings are being posted inline, so approval is not on the table. This note records two concrete concerns examined from the diff at src/mcp/client/session.py:1137-1144 and ruled out: the stale-validator cache race is closed by the _same_schema check after the thread hop, and the reordering of the structured-content check versus schema compilation only affects error precedence in the double-failure case, with the same exception type either way. The removed in-function cache hit in _output_schema_validator also means the earlier coverage concern about an unreachable branch is moot.

2 verified lower-impact observations (convention, logging or cleanup points) were not posted.

Comment thread tests/conftest.py Outdated
Comment thread src/mcp/client/session.py Outdated
Comment thread src/mcp/client/session.py Outdated

@claude claude Bot left a comment

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.

Code review found no new issues

No new issues were found in this update; 2 findings from earlier reviews are still open above.

Still open from earlier reviews (2):

  • 🔴 src/mcp/client/session.py:1142If the client runs on a platform without worker threads (Pyodide/emscripten, which pyproject.toml:142 deliberately keep…
  • 🔴 src/mcp/client/session.py:1142Servers whose sync handlers call a downstream call_tool from their worker thread can hang forever under a burst of requ…

If you have decided not to act on one of these findings, resolve its thread (a reply alone leaves it open) and the next review stops counting it. To review this commit again now, use Re-run on its "Claude Code Review" check.

@claude claude Bot left a comment

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.

No new blocking issues. 1 optional suggestion (a nit or a note on pre-existing code) was found and not posted.

maxisbey
maxisbey previously approved these changes Sep 16, 2026
maxisbey
maxisbey previously approved these changes Sep 16, 2026

@maxisbey maxisbey left a comment

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.

approved.... again

Comment thread src/mcp/client/session.py Outdated

@claude claude Bot left a comment

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.

Code review found no issues

No high-confidence issues detected in this change.

@claude claude Bot left a comment

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.

LGTM — since the last review the scope shrank to the BlockBuster fixture plus the Windows command-resolution offload, and the session.py validator offload that the earlier open threads were about is no longer in the diff.

What was reviewed:

  • _get_executable_command offload: cancellation at stdio.py:124 happens before the spawn, so no cleanup path runs against the monkeypatched sys; get_windows_executable_command already swallows OSError, so the thread hop changes no observable stdio_client behaviour.
  • conftest allowlist: checked the os.scandir/os.listdir concern for the jsonschema_specifications lazy import on Python 3.10-3.12 — the final commit adds the os.scandir entry and bumps the floor to blockbuster>=1.5.27, so both walkers are covered; CI on those cells is the remaining confirmation since the suite could not be executed here.
  • New cancellation test: the resolver signals via anyio.from_thread.run_sync and is released/awaited only in worker threads, so it neither blocks the loop nor leaks the abandoned thread past the test.
Extended reasoning...

Overview

The diff against the base now touches five files: pyproject.toml/uv.lock add blockbuster>=1.5.27 to the dev group, tests/conftest.py adds an autouse BlockBuster(["mcp", "mcp_types"]) fixture with six targeted can_block_in exemptions (coverage source reads, jsonschema_specifications._schemas discovery, and the intentionally synchronous to_image_content/to_audio_content reads) plus an eager httpcore2 import, src/mcp/client/stdio.py makes _get_executable_command async and offloads the Windows shutil.which probing via anyio.to_thread.run_sync(..., abandon_on_cancel=True), and tests/client/test_stdio.py stubs the resolver in install_fake_process and adds a cancellation test. Commits 36fc996 and 4cd5f41 since the previous review removed the src/mcp/client/session.py validator-compilation offload entirely (reverting it to the base behaviour and dropping its promotion tests) in favour of allowlisting the jsonschema lazy import in conftest, and added the os.scandir exemption with the matching version floor.

Security risks

None. The runtime change is confined to moving an existing shutil.which lookup onto a worker thread on Windows; it does not alter which command is resolved, how the subprocess is spawned, or the environment passed to it. Everything else is test-only tooling and a dev-group dependency.

Level of scrutiny

Moderate. The runtime change is small and I traced its failure modes: get_windows_executable_command already catches OSError and returns the bare command, so exceptions crossing the thread boundary are unchanged; cancellation at the new await raises before _create_platform_compatible_process is called, so nothing is leaked and none of the later sys.platform checks in stdio.py run under the test's SimpleNamespace patch. The abandon_on_cancel=True thread is short-lived in production (shutil.which on a handful of candidates). For the conftest, the main risk is a BlockingError or KeyError on platforms I cannot exercise here (Windows, Python 3.10-3.12); the final commit's os.scandir entry addresses the version-dependent importlib.resources walker, and any remaining miss would surface as an immediately visible red CI cell rather than a latent production defect.

Other factors

The two session.py threads I left open earlier (thread-less runtimes, shared worker-pool starvation) are moot because that code is no longer in the diff. No third-party CHANGES_REQUESTED review is outstanding; the other reviewer's reviews are recorded as dismissed, consistent with superseded-by-push. The PR is authored by a maintainer, the dependency is dev-only per DEPENDENCY_POLICY.md, the conftest follows the Starlette fixture pattern with brief comments rather than a wall of suppressions, and the new test uses anyio.Event/threading.Event coordination under anyio.fail_after(5) per the repository's test conventions. I could not execute the suite in this environment (no venv, no permission to install), so CI is the final gate for the allowlist coverage across the matrix.

@Kludex
Kludex merged commit 6affe5c into main Sep 16, 2026
39 checks passed
@Kludex
Kludex deleted the worktree-bright-badger-tds7 branch September 16, 2026 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants