Skip to content

feat(websocket): Add non-blocking stop request and bounded stop wait - #1122

Draft
wonderdog5 wants to merge 1 commit into
espressif:masterfrom
wonderdog5:feat/websocket-split-stop
Draft

feat(websocket): Add non-blocking stop request and bounded stop wait#1122
wonderdog5 wants to merge 1 commit into
espressif:masterfrom
wonderdog5:feat/websocket-split-stop

Conversation

@wonderdog5

Copy link
Copy Markdown

Problem

esp_websocket_client_stop() — and esp_websocket_client_destroy(), which calls it — joins the client task with portMAX_DELAY. That is fine for callers that can block, but there is a real deadlock-adjacent class it creates: a task that must not block unboundedly has no bounded way to tear a client down.

Concrete case (ESP32-S3 battery device, two websocket clients): a power-management task with a fixed sleep-readiness deadline tears down both clients back to back on the way into deep sleep. A link blip is exactly when sleep tends to get requested, so both client tasks are typically mid-reconnect — each join then waits out the full TLS/transport connect timeout before the task notices run == false, and the two unbounded joins stack. With a ~15 s connect timeout per client, the teardown can exceed a 30 s system deadline, and the caller can neither cancel nor bound it.

Change

Split the stop into the two halves it is already made of internally:

  • esp_websocket_client_request_stop() — the non-blocking half: sets run = false and REQUESTED_STOP_BIT (which also wakes a WEBSOCKET_STATE_WAIT_TIMEOUT reconnect wait early, so a task idling between retries exits immediately), then returns.
  • esp_websocket_client_wait_stopped(client, timeout) — a bounded wait on STOPPED_BIT. Only after it returns true is esp_websocket_client_destroy() guaranteed not to block on the task; on false the caller keeps the handle and retries later.

An application tearing down several clients can request all stops first — the task exits overlap — and then wait for each against a single shared deadline, instead of serializing every client's worst-case teardown.

esp_websocket_client_stop() is unchanged (still request + unbounded wait); no existing behavior is affected. No new state or bits are introduced — the split only exposes the existing REQUESTED_STOP_BIT / STOPPED_BIT machinery with a caller-chosen timeout.

Notes

  • request_stop mirrors stop_wait_task's guard: it cannot be called from the websocket task itself, and is a no-op ESP_OK when already stopped.
  • wait_stopped(portMAX_DELAY) reproduces the current unbounded join exactly.
  • Field-tested in production firmware (vendored copy of this component) since 2026-08: two clients' teardowns bounded by one shared 5 s budget; an abandoned (timed-out) join is reaped by a later stop/destroy once the task exits on its own.

I'm happy to adjust naming, add a test under components/esp_websocket_client/tests, or fold this into a different API shape if the maintainers prefer.

🤖 Generated with Claude Code

esp_websocket_client_stop() (and destroy(), which calls it) joins the
client task with portMAX_DELAY. A caller that must not block for long —
e.g. a power-management path with a fixed sleep-readiness deadline — has
no way to bound that join: with the task mid-reconnect, the join can
stack the TLS/transport connect timeout on top of the wait, and tearing
down two clients back to back doubles it.

Split the stop into its two halves:

- esp_websocket_client_request_stop(): flag the task to exit and wake a
  reconnect wait early (REQUESTED_STOP_BIT), return immediately.
- esp_websocket_client_wait_stopped(): wait for STOPPED_BIT with a
  caller-chosen timeout; only on success is destroy() guaranteed not to
  block on the task.

Requesting stop on several clients before waiting on any overlaps their
teardowns instead of serializing them. esp_websocket_client_stop()
behavior is unchanged (request + portMAX_DELAY wait).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@CLAassistant

CLAassistant commented Aug 9, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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

Labels

Status: Opened Issue is new

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants