SSE shutdown verification
Keep a server-sent events connection open and verify its terminal event during shutdown.
Draincheck can keep one server-sent events connection open alongside ordinary request traffic and verify its shutdown behavior in the same CI lifecycle run. The probe is optional and disabled by default.
streaming:
sse:
enabled: true
path: /events
headers:
Authorization: Bearer ci-only-token
initial_event: ready
terminal_event: shutdown
establish_timeout: 2s
close_timeout: 5sinitial_event and terminal_event match the value of the SSE event: field. Draincheck does not
interpret the event's data: payload. Set terminal_event to an empty string only when clean EOF,
without a named final event, is the application's intentional contract.
Lifecycle order
Draincheck performs these steps:
- Wait for the configured HTTP or gRPC readiness check.
- Open
streaming.sse.pathwithGETandAccept: text/event-stream. - Require HTTP
200–299, thetext/event-streammedia type, andinitial_eventbeforeestablish_timeout. - Start the normal configured request traffic.
- At the termination-signal request boundary, record whether the same SSE connection is active.
- Require
terminal_eventwhen configured, followed by a complete event delimiter and clean EOF. - Require that closure to occur after the signal request and within
close_timeout.
The SSE close budget starts when Draincheck requests the signal, not when the runtime later confirms
delivery. It must not exceed shutdown.deadline. The stream does not count as ordinary traffic and
does not change request success or in-flight counts.
Assertions and reports
An enabled probe adds three JUnit and JSON assertions:
stream.established: the valid response and initial event arrived in time.stream.active_at_signal: the established connection was open at the signal request.stream.closed_gracefully: the terminal event requirement, clean EOF, ordering, and close budget all passed.
The JSON and debug timeline expose bounded evidence under streaming.sse, including status,
content type, event count, initial and terminal observations, signal-boundary activity, EOF and
close-timing booleans, and a classified error. The report never includes the request URL or header
values. All configured SSE header values are redacted from debug bundles; do not place credentials
in the path or query string because the resolved configuration contains the path.
Application behavior
The stream handler should flush the initial event immediately, remain blocked while the service is running, and react to the application's shared shutdown state. A typical order is:
receive SIGTERM
-> mark readiness unhealthy
-> emit and flush the SSE terminal event
-> return from the stream handler
-> finish other in-flight handlers
-> flush telemetry and close dependencies
-> exitDo not rely on client reconnect behavior for this check. Draincheck observes one connection and
does not reconnect or send Last-Event-ID.
Protocol limits
- Plaintext HTTP through
streaming.sse.container_port, falling back totarget.container_portwhen omitted. - One SSE stream per run; HTTP redirects follow Go's standard client behavior.
- A maximum of 64 KiB per SSE line and bounded error messages.
- Comments are ignored. Events must end with a blank line; EOF in the middle of an event is truncated rather than clean.
- WebSockets use the separate WebSocket shutdown adapter. Unary and server-streaming
gRPC use the gRPC lifecycle adapter. Client-streaming, bidirectional RPCs, TLS, and
application-specific
data:payload assertions remain outside the SSE adapter.