Skip to content

Commit 1956ff6

Browse files
Gordon LGordon L
authored andcommitted
test: correct docstring per review measurements; assert exact PARSE_ERROR below 3.14
Review measurements on #3147 (and re-measured here on arm64 macOS) show the stack-headroom-based recursion guard in the C json scanner starts in CPython 3.14, not 3.12 - on 3.12/3.13 the flip depth is identical across 8/16/64 MB stacks. Docstring updated accordingly. The macOS mechanism was also backwards in the old docstring: it is not a smaller thread stack but a larger one. CPython pins non-main threads to a 16 MiB stack on macOS (THREAD_STACK_SIZE in Python/thread_pthread.h); measured default-thread flip on arm64 macOS 3.14 is ~149,641 levels - identical to an explicit 16 MiB stack - so the 100k-deep body parses and fails validation (INVALID_REQUEST) instead of raising RecursionError. Since the outcome is deterministic below 3.14, the test now asserts the exact PARSE_ERROR code there and accepts either rejection only on 3.14+.
1 parent 580c3cd commit 1956ff6

1 file changed

Lines changed: 18 additions & 7 deletions

File tree

tests/server/test_streamable_http_modern.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import json
1010
import logging
11+
import sys
1112
from collections.abc import Callable
1213
from typing import Any
1314

@@ -1011,18 +1012,28 @@ async def broken_list(ctx: ServerRequestContext, params: PaginatedRequestParams
10111012
async def test_modern_post_with_deeply_nested_body_is_rejected_not_a_crash() -> None:
10121013
"""A deeply nested body is rejected with a 400, never a crash.
10131014
1014-
Which JSON-RPC code it gets is platform-dependent: since CPython 3.12 the C
1015-
json scanner guards recursion by actual C-stack headroom, so depending on
1016-
the thread's stack size the body either fails to parse (RecursionError ->
1017-
PARSE_ERROR) or parses into a giant list that then fails request
1018-
validation (-> INVALID_REQUEST). Both are correct rejections; the
1019-
deterministic PARSE_ERROR mapping is covered by the monkeypatch test below.
1015+
Which JSON-RPC code it gets is platform-dependent on CPython 3.14+: the C
1016+
json scanner there guards recursion by actual C-stack headroom, so whether
1017+
a 100k-deep body parses depends on the parsing thread's stack size and the
1018+
per-level cost on that architecture (~112 bytes/level measured on arm64,
1019+
~129 on x86_64). It either fails to parse (RecursionError -> PARSE_ERROR)
1020+
or parses into a giant list that then fails request validation
1021+
(-> INVALID_REQUEST). CPython gives non-main threads a 16 MiB stack on
1022+
macOS (THREAD_STACK_SIZE in thread_pthread.h) vs 8-ish MiB defaults
1023+
elsewhere, which is why macOS parses the body and answers INVALID_REQUEST.
1024+
Below 3.14 the recursion guard ignores stack size and the 100k-deep body
1025+
always fails to parse, so the exact PARSE_ERROR code is asserted there.
1026+
Both codes are correct rejections; the deterministic RecursionError ->
1027+
PARSE_ERROR mapping is covered by the monkeypatch test below.
10201028
"""
10211029
body = b"[" * 100_000 + b"]" * 100_000
10221030
async with _asgi_client(_x_mcp_server()) as http:
10231031
response = await http.post("/mcp", content=body, headers={"content-type": "application/json"})
10241032
assert response.status_code == 400
1025-
assert response.json()["error"]["code"] in (PARSE_ERROR, INVALID_REQUEST)
1033+
if sys.version_info >= (3, 14):
1034+
assert response.json()["error"]["code"] in (PARSE_ERROR, INVALID_REQUEST)
1035+
else:
1036+
assert response.json()["error"]["code"] == PARSE_ERROR
10261037

10271038

10281039
async def test_modern_post_recursion_error_during_parse_is_parse_error(monkeypatch: pytest.MonkeyPatch) -> None:

0 commit comments

Comments
 (0)