Skip to content

fix(sdk): raise asyncio StreamReader buffer in Python AsyncHostTransport#2760

Open
michaelreavant wants to merge 1 commit intosuperdoc-dev:mainfrom
michaelreavant:fix/python-sdk-async-transport-large-response-buffer
Open

fix(sdk): raise asyncio StreamReader buffer in Python AsyncHostTransport#2760
michaelreavant wants to merge 1 commit intosuperdoc-dev:mainfrom
michaelreavant:fix/python-sdk-async-transport-large-response-buffer

Conversation

@michaelreavant
Copy link
Copy Markdown

Summary

superdoc_get_content on larger documents fails in the Python async SDK with a misleading HOST_DISCONNECTED error. Root cause is a missing limit= on the subprocess spawn β€” the stdout StreamReader inherits asyncio's 64 KiB default buffer, which any non-trivial response trips. This PR raises the buffer ceiling and exposes it as a configurable option.

Problem

The Python async transport spawned the host CLI without passing a limit= to asyncio.create_subprocess_exec, so its stdout StreamReader inherited asyncio's default 64 KiB buffer. Every host response is written as a single newline-delimited JSON line, so any cli.invoke whose serialized result exceeds 64 KiB (e.g. superdoc_get_content on larger documents) caused readline() to raise ValueError: Separator is not found, and chunk exceed the limit inside _reader_loop. The exception was caught by the generic reader-loop handler and pending requests were rejected with the misleading HOST_DISCONNECTED error β€” even though the host process was still alive and healthy.

Fix

Pass limit= to create_subprocess_exec and expose it as a new stdout_buffer_limit_bytes constructor option on AsyncHostTransport, threaded through SuperDocAsyncRuntime and AsyncSuperDocClient. The default of 64 MiB safely covers the host's own 32 MiB DEFAULT_MAX_STDIN_BYTES input cap with room for ~2x JSON expansion.

Scope

SyncHostTransport is unaffected β€” it uses raw blocking subprocess.Popen which has no asyncio buffer limit. No changes to the Node SDK or the host server.

Tests

Adds a TestAsyncLargeResponse regression suite that:

  1. Round-trips a 200 KB response through the default-configured transport.
  2. Pins that an explicitly tightened stdout_buffer_limit_bytes still reproduces the original failure mode, guaranteeing the option is wired through to create_subprocess_exec.

Bug reproduction was verified by stashing the fix and running the new test against the unmodified transport.py β€” it raised the exact SuperDocError: Host process disconnected. seen in production. With the fix in place, the full Python SDK test suite passes (90 tests, including 26 transport tests).

Test plan

  • pytest packages/sdk/langs/python/tests/ β€” 90 passed
  • Verified regression test reproduces the original HOST_DISCONNECTED failure when the fix is reverted
  • pnpm run generate:all clean (no codegen drift in this branch)

The Python async transport spawned the host CLI without passing a `limit=`
to `asyncio.create_subprocess_exec`, so its stdout `StreamReader` inherited
asyncio's default 64 KiB buffer. Every host response is written as a single
newline-delimited JSON line, so any `cli.invoke` whose serialized result
exceeds 64 KiB (e.g. `superdoc_get_content` on larger documents) caused
`readline()` to raise `ValueError: Separator is not found, and chunk
exceed the limit` inside `_reader_loop`. The exception was caught by the
generic reader-loop handler and pending requests were rejected with the
misleading `HOST_DISCONNECTED` error β€” even though the host process was
still alive and healthy.

Pass `limit=` to `create_subprocess_exec` and expose it as a new
`stdout_buffer_limit_bytes` constructor option on `AsyncHostTransport`,
threaded through `SuperDocAsyncRuntime` and `AsyncSuperDocClient`. The
default of 64 MiB safely covers the host's own 32 MiB
`DEFAULT_MAX_STDIN_BYTES` input cap with room for ~2x JSON expansion.

`SyncHostTransport` is unaffected β€” it uses raw blocking `subprocess.Popen`
which has no asyncio buffer limit.

Adds a `TestAsyncLargeResponse` regression suite that:
  1. Round-trips a 200 KB response through the default-configured transport.
  2. Pins that an explicitly tightened `stdout_buffer_limit_bytes` still
     reproduces the original failure mode, guaranteeing the option is
     wired through to `create_subprocess_exec`.
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.

1 participant