Skip to content

feat(sse): add ping_interval for keepalive pings#4623

Open
TanayK07 wants to merge 12 commits intolitestar-org:mainfrom
TanayK07:feat/sse-ping-interval
Open

feat(sse): add ping_interval for keepalive pings#4623
TanayK07 wants to merge 12 commits intolitestar-org:mainfrom
TanayK07:feat/sse-ping-interval

Conversation

@TanayK07
Copy link
Copy Markdown

@TanayK07 TanayK07 commented Mar 9, 2026

Description

Add optional ping_interval parameter to ServerSentEvent that sends SSE comment keepalive pings at the specified interval to prevent connection timeouts from reverse proxies or clients.

Closes #4082

Approach

This PR incorporates all reviewer feedback from the stalled #4098:

  • No intermediate SSEStream class — overrides to_asgi_response() directly on ServerSentEvent
  • ASGIStreamingSSEResponse lives in sse.py, not streaming.py — keeps SSE concerns out of generic streaming
  • Uses SSE comments for pings (: ping\r\n\r\n), not event: ping — comments are invisible to JS EventSource clients
  • anyio.Lock on send() — ping and stream tasks run concurrently, lock prevents response corruption
  • Sleep-first, then ping — no immediate ping on connect
  • anyio.Event to signal shutdown — clean task cancellation when stream ends

Usage

@get("/stream")
async def stream_handler() -> ServerSentEvent:
    async def generator():
        while True:
            data = await get_data()
            yield data

    return ServerSentEvent(generator(), ping_interval=15)

This is fully opt-in — ping_interval defaults to None, so existing behavior is unchanged.

Changes

File Change
litestar/response/sse.py Add ASGIStreamingSSEResponse, add ping_interval to ServerSentEvent, override to_asgi_response()
tests/unit/test_response/test_sse.py Add 5 test cases for ping behavior
docs/usage/responses.rst Add keepalive ping documentation section
docs/release-notes/changelog.rst Add changelog entry

Test Plan

  • SSE without ping_interval works unchanged (regression test)
  • SSE with ping_interval sends keepalive comments during idle periods
  • Pings are SSE comments (: ping), not events
  • Ping stops cleanly when stream ends (no task leaks)
  • Concurrent ping and data don't corrupt the response

📚 Documentation preview 📚: https://litestar-org.github.io/litestar-docs-preview/4623

@TanayK07 TanayK07 requested review from a team as code owners March 9, 2026 06:26
@github-actions github-actions bot added area/docs This PR involves changes to the documentation area/response size: small type/feat pr/external Triage Required 🏥 This requires triage labels Mar 9, 2026
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 9, 2026

Codecov Report

❌ Patch coverage is 70.58824% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.36%. Comparing base (69a1ad9) to head (7b2804e).

Files with missing lines Patch % Lines
litestar/response/sse.py 70.58% 15 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4623      +/-   ##
==========================================
+ Coverage   67.33%   67.36%   +0.02%     
==========================================
  Files         292      292              
  Lines       14941    14990      +49     
  Branches     1676     1684       +8     
==========================================
+ Hits        10061    10098      +37     
- Misses       4743     4756      +13     
+ Partials      137      136       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Tanay10x
Copy link
Copy Markdown

Hey @provinzkraut @euri10 — friendly ping! This PR has been open for ~10 days. CI is green on all SSE tests (5527 passed) — the only failures are 3 Postgres channel pub/sub timeouts which also fail on main branch (unrelated flaky tests).

This incorporates all the review feedback from the stalled #4098:

  • SSE comments for pings (invisible to EventSource clients)
  • anyio.Lock for concurrent send safety
  • No intermediate SSEStream class
  • 100% coverage on modified lines

Would love a review when you get a chance. Happy to make any changes needed. Thanks!

@TanayK07
Copy link
Copy Markdown
Author

Just pushed a few improvements based on self-review:

  • Added input validation for ping_interval (rejects zero/negative values with ImproperlyConfiguredException)
  • to_asgi_response() now delegates to the parent when ping_interval is None, so the non-ping code path is completely unchanged
  • Added __all__ to sse.py to match streaming.py conventions
  • A few more edge case tests (empty generator, large interval)
  • Docs now explicitly mention units (seconds) and typical values

CI is green across 3.9–3.13 — the one red check is a Postgres channel timeout that also happens on main (not related to SSE).

@provinzkraut @sobolevn would really appreciate a look when you have a moment — happy to adjust anything.

@TanayK07
Copy link
Copy Markdown
Author

Update : Codecov/patch is now green (was the last remaining check). All 27 CI checks pass. This PR is ready for
review whenever you get a chance , Appreciato

Add optional `ping_interval` parameter to `ServerSentEvent` that sends
SSE comment keepalive pings (`: ping`) at the specified interval to
prevent connection timeouts from reverse proxies or clients.

Closes litestar-org#4082
- Fix RST heading level for "Keepalive Pings" section (~~~ → ^^^)
- Remove dead else branch in _send() to achieve 100% patch coverage
Remove the "Keepalive Pings" subsection heading that caused a
"Title level inconsistent" RST error. The content is integrated
directly into the "Server Sent Event Responses" section instead.
- Remove dead callable iterator check in to_asgi_response (SSE
  iterator is always _ServerSentEventIterator, never a callable)
- Add test for str chunk handling in ASGIStreamingSSEResponse
  to cover both branches of the bytes/str encoding path
- Use proper ASGI Message/HTTPDisconnectEvent types for mock functions
- Add isinstance narrowing for message body to satisfy mypy
- Add from __future__ import annotations and TYPE_CHECKING imports
With from __future__ import annotations, string-quoting type
hints is redundant. Ruff format removes them automatically.
…or ping_interval

- Validate ping_interval > 0 (reject zero/negative to prevent tight loops)
- Delegate to parent to_asgi_response when ping_interval is None (cleaner backward compat)
- Add __all__ export to sse.py for consistency with streaming.py
- Add tests: validation errors, empty generator shutdown, large interval no-ping
- Docs: clarify units (seconds), typical values, sleep-first behavior
Fixes ruff TC001 lint error — ASGIResponse is only used in a type
annotation, not at runtime.
@TanayK07 TanayK07 force-pushed the feat/sse-ping-interval branch from f8ac564 to 7c1ec86 Compare March 29, 2026 14:22
Copy link
Copy Markdown
Member

@cofin cofin left a comment

Choose a reason for hiding this comment

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

Thanks for the PR. I added a couple of minor comments, but it otherwise looks good.

TanayK07 and others added 2 commits April 5, 2026 19:10
- Replace assert guards with conditional raises in _send and _ping
- Reorder docstring args to list request first
- Add :pr: 4623 to changelog entry
@TanayK07
Copy link
Copy Markdown
Author

TanayK07 commented Apr 5, 2026

Thanks for the PR. I added a couple of minor comments, but it otherwise looks good.

Thanks, I have addressed the comments, made the updates and added 3 small tests for codecov coverage.

Whom else can I tag to review such that we can merge this? Appreciate it @cofin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs This PR involves changes to the documentation area/response pr/external size: small Triage Required 🏥 This requires triage type/feat

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: recurring ping in sse response

3 participants