Skip to content

Add max_event_size to cap SSE event buffering - #1071

Merged
Kludex merged 13 commits into
mainfrom
add-sse-max-event-size
Aug 5, 2026
Merged

Add max_event_size to cap SSE event buffering#1071
Kludex merged 13 commits into
mainfrom
add-sse-max-event-size

Conversation

@Kludex

@Kludex Kludex commented Jul 16, 2026

Copy link
Copy Markdown
Member

EventSource buffers a server-sent event until its terminating blank line arrives, so a stream that keeps sending data without ever completing an event grows the decoder buffer proportionally to what the server sends. There's currently no way to bound that.

This adds an opt-in max_event_size to client.sse() / AsyncClient.sse() (threaded into EventSource). When set, iterating raises SSEError once a single event's buffered bytes exceed the limit. The check covers both shapes:

  • an unterminated line (bytes accumulating in the line decoder before any \n), and
  • accumulated data: fields across many lines before the blank-line terminator.

The counter resets after each event, so the limit is per event rather than per stream. Default is None (unbounded), so existing behaviour is unchanged.

This mirrors the bound Go SSE readers get for free from bufio.Scanner (MaxScanTokenSize, raised via Scanner.Buffer); the Rust eventsource-stream/reqwest-eventsource crates have no equivalent.

Docs: added a "Limiting event size" section to docs/sse.md. Coverage on _sse.py stays at 100%.

Review in cubic

Iterating an EventSource buffers a server-sent event until its
terminating blank line, so a stream that never completes an event grows
the decoder buffer without bound. Add an opt-in max_event_size to
client.sse() (and EventSource) that raises SSEError once a single
event's buffered bytes exceed the limit, checked both for an
unterminated line and for accumulated data: fields. The counter resets
per event.
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

@codspeed-hq

codspeed-hq Bot commented Jul 16, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 15 untouched benchmarks
⏩ 7 skipped benchmarks1


Comparing add-sse-max-event-size (052f589) with main (b36e77b)

Open in CodSpeed

Footnotes

  1. 7 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@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: 74895ce2c6

ℹ️ 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/httpx2/httpx2/_sse.py Outdated
Comment thread src/httpx2/httpx2/_sse.py Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files

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

Re-trigger cubic

Comment thread src/httpx2/httpx2/_sse.py Outdated
Comment thread src/httpx2/httpx2/_sse.py Outdated
Comment thread src/httpx2/httpx2/_sse.py Outdated

@cubic-dev-ai cubic-dev-ai 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.

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

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

Re-trigger cubic

Comment thread src/httpx2/httpx2/_sse.py Outdated
Bound SSE event buffering by default rather than only when the caller
opts in, mirroring the WebSocket max_message_size_bytes default. Callers
can raise the cap or pass None to disable it.
Comment lines (SSE keepalive heartbeats) are not buffered, so counting
them let a long idle stream trip max_event_size despite holding no event
data. Skip them before accounting, and reset the counter on every blank
line so comment bytes never carry into the next event.
- AsyncClient.sse() now defaults max_event_size to 1 MiB like the sync
  client; it was left at None, so async callers had no cap by default.
- Move the size check into EventSource so it counts the completed lines
  and the pending unterminated line against one limit, instead of each
  decoder checking its own buffer (which allowed nearly 2x the cap to
  buffer before a newline arrived).
- Attach the request to the size-limit SSEError, matching the
  content-type SSEError and RequestError behaviour.

@cubic-dev-ai cubic-dev-ai 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.

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

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

Re-trigger cubic

Comment thread src/httpx2/httpx2/_sse.py Outdated
Comment thread docs/sse.md
Comment thread tests/httpx2/test_sse.py
Summing the accumulated event bytes with the pending line buffer let a
partial next event, carried in the same chunk as a completed event,
count against the previous event and wrongly trip the limit. Check each
against the cap on its own instead.
Comment thread src/httpx2/httpx2/_client.py Outdated
Comment thread src/httpx2/httpx2/_client.py Outdated
Move the size check into the decoders, raising where each buffer grows,
instead of calling a helper after every decoded line, chunk, and flush.
The line and event buffers stay independent, and wrapping iteration in
request_context attaches the request to every SSEError.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 1 file (changes from recent commits).

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

Re-trigger cubic

Comment thread src/httpx2/httpx2/_sse.py Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 1 file (changes from recent commits).

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

Re-trigger cubic

Comment thread src/httpx2/httpx2/_sse.py
@Kludex
Kludex enabled auto-merge (squash) August 5, 2026 11:57
@Kludex
Kludex merged commit 5d9eedc into main Aug 5, 2026
17 checks passed
@Kludex
Kludex deleted the add-sse-max-event-size branch August 5, 2026 11:59
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.

1 participant