Skip to content

Bound WebSocket and SSE client handshakes by read_timeout - #482

Merged
Coldwings merged 2 commits into
mainfrom
fix/client-handshake-read-timeout
Jul 11, 2026
Merged

Bound WebSocket and SSE client handshakes by read_timeout#482
Coldwings merged 2 commits into
mainfrom
fix/client-handshake-read-timeout

Conversation

@Coldwings

Copy link
Copy Markdown
Owner

Description

Fixes WebSocket and SSE client connect() paths so protocol response/header reads after TCP/TLS setup are bounded by read_timeout instead of waiting indefinitely for a silent peer.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Performance improvement (optimization that improves speed/memory usage)
  • Documentation (changes to documentation, comments, or examples)
  • Refactoring (code changes that neither fix bugs nor add features)
  • Tests (adding or modifying tests)
  • Build/CI (changes to build system, CI configuration, or dependencies)

Related Issues

Fixes #426

Changes Made

Core Changes

  • Added a shared fd-shutdown watchdog helper for HTTP-based clients.
  • Added TLS shutdown bookkeeping through net::stream::mark_externally_shut_down() for timeout-driven fd shutdowns.
  • Bounded WebSocket upgrade response reads by an absolute read_timeout deadline.
  • Bounded SSE response header reads by an absolute read_timeout deadline.
  • Preserve deterministic ETIMEDOUT on these timeout paths.

API Changes (if applicable)

No breaking API changes. Existing base_client_config::read_timeout now applies consistently to WebSocket upgrade responses and SSE response headers during connect().

Migration Guide (if breaking change)

Not applicable.

Testing

Unit Tests

  • Added new tests for the changes
  • Updated existing tests if needed
  • All tests pass locally

Integration Tests

  • Tested with existing examples
  • Tested in real-world scenarios (if applicable)

Sanitizer Testing

  • Tested with ASAN (AddressSanitizer)
  • Tested with TSAN (ThreadSanitizer)
  • No new warnings or errors

Test Results

cmake -S /tmp/elio-fix-426 -B /tmp/elio-build-426 -DELIO_BUILD_EXAMPLES=OFF -DELIO_ENABLE_HTTP2=OFF -DELIO_ENABLE_DEVELOPER_WARNINGS=ON -DELIO_WARNINGS_AS_ERRORS=ON
cmake --build /tmp/elio-build-426 --parallel 2
timeout --kill-after=10s 30s /tmp/elio-build-426/tests/elio_tests "[websocket][client][timeout],[sse][client][timeout]"
timeout --kill-after=10s 45s /tmp/elio-build-426/tests/elio_tests_asan "[websocket][client][timeout],[sse][client][timeout]"
timeout --kill-after=10s 45s /tmp/elio-build-426/tests/elio_tests_tsan "[websocket][client][timeout],[sse][client][timeout]"
timeout --kill-after=10s 60s /tmp/elio-build-426/tests/elio_tests "[http][client]"
git diff --check

Checklist

Code Quality

  • My code follows the project's coding standards
  • I have added/updated comments for complex logic
  • I have removed any debug code, TODOs, or commented-out code
  • My changes generate no new warnings

Documentation

  • I have updated documentation (wiki, README, code comments)
  • I have added examples for new features (if applicable)
  • I have updated API documentation (if applicable)

Testing

  • I have added tests that prove my fix is effective or my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested with ASAN and TSAN

Compatibility

  • My changes are backward compatible (or I've documented breaking changes)
  • I have considered the impact on existing users
  • I have updated CHANGELOG.md (if applicable)

Performance (if applicable)

  • I have considered the performance impact
  • I have added benchmarks for performance-critical changes

Screenshots / Diagrams

Not applicable.

Additional Notes

The configure/build were performed out of source in /tmp/elio-build-426; build parallelism was limited to 2. The build command was run with the configured HTTP(S) proxy for dependency fetches.

Reviewer Guidance

Areas requiring special attention:

  • Timeout/watchdog interaction with TLS stream destruction.
  • Whether read_timeout should remain an absolute deadline for the full protocol header read rather than a per-read timeout.

Questions for reviewers:

  • None.

Copilot AI review requested due to automatic review settings July 11, 2026 02:22

Copilot AI 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.

Pull request overview

This PR fixes a liveness bug in Elio’s HTTP-based clients by ensuring WebSocket and SSE connect() handshake/header reads are bounded by base_client_config::read_timeout, preventing indefinite hangs when a peer accepts TCP/TLS but stays silent.

Changes:

  • Added a shared fd-shutdown watchdog helper to enforce read deadlines during protocol header reads.
  • Applied an absolute read_timeout deadline to WebSocket upgrade response parsing and SSE response header reads (preserving ETIMEDOUT on timeout).
  • Added regression tests and updated wiki/API docs to clarify connect_timeout vs read_timeout behavior.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
wiki/WebSocket-SSE.md Documents that WebSocket/SSE connect() header/upgrade reads are bounded by read_timeout.
wiki/API-Reference.md Clarifies base_client_config timeout semantics for WebSocket/SSE clients.
tests/unit/test_http_client.cpp Adds regression tests that stalled WS/SSE handshakes fail with ETIMEDOUT.
include/elio/net/stream.hpp Adds net::stream::mark_externally_shut_down() for TLS-safe cleanup after watchdog shutdown.
include/elio/http/client_base.hpp Introduces shared arm_fd_shutdown_watchdog() helper for HTTP-based clients.
include/elio/http/websocket_client.hpp Enforces read_timeout for WS upgrade response reads and sets deterministic error codes.
include/elio/http/sse_client.hpp Enforces read_timeout for SSE response header reads and sets deterministic error codes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread include/elio/http/websocket_client.hpp
Comment thread include/elio/http/sse_client.hpp
Comment thread include/elio/http/sse_client.hpp
Comment thread include/elio/http/sse_client.hpp
Comment thread include/elio/http/sse_client.hpp
Comment thread include/elio/http/sse_client.hpp
@Coldwings

Copy link
Copy Markdown
Owner Author

Addressed the failed-handshake cleanup comments in 7b96d3c: WebSocket connect failures now close the stored stream and move to closed state, and SSE connect failure paths now release the stored stream while preserving errno. Re-ran local validation: incremental out-of-source build with --parallel 2, the WebSocket/SSE timeout tests on normal/ASAN/TSAN binaries, the existing [http][client] tests, and git diff --check.

@copilot please re-review only; do not make direct code modifications.

Copilot AI commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Addressed the failed-handshake cleanup comments in 7b96d3c: WebSocket connect failures now close the stored stream and move to closed state, and SSE connect failure paths now release the stored stream...

Re-reviewed the current branch. 7b96d3c addresses the failed-handshake cleanup paths I had flagged: the WebSocket handshake failure path now disconnects and closes state, and the SSE connect failure paths now funnel through cleanup that disconnects stream_, preserves errno, and leaves the client disconnected. I don't have further review comments on those points.

@Coldwings
Coldwings merged commit 0906f5a into main Jul 11, 2026
10 checks passed
@Coldwings
Coldwings deleted the fix/client-handshake-read-timeout branch July 11, 2026 02:49
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.

[Contract] WebSocket and SSE client handshakes ignore read_timeout

3 participants