Skip to content

fix(gateway): drop WebChat frames for closed sockets - #119

Merged
adriannoes merged 4 commits into
developmentfrom
fix/gateway-ws-frames
Oct 17, 2026
Merged

adriannoes merged 4 commits into
developmentfrom
fix/gateway-ws-frames

Conversation

@adriannoes

Copy link
Copy Markdown
Collaborator

Summary

Consolidates one cursor high-severity fix (originally cursor/high-severity-issues-9537, PR #115) as an operator-authored PR.

  • fix(gateway): drop WebChat frames for closed sockets

Replies for a disconnected webchat conn_id stayed on the shared outgoing queue. After 64 of those, every live client failed to send until restart. Discard frames on unregister, and do not enqueue when no socket is registered.

Testing

  • make test_ws — passed
  • CI=true make test — passed
  • make static — passed

Supersedes #115.

Replies for a disconnected webchat conn_id stayed on the shared outgoing
queue. After 64 of those, every live client failed to send until restart.
Discard frames on unregister, and do not enqueue when no socket is registered.

Supersedes #115.

@cursor cursor 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.

Summary

Solid, correctly scoped fix for a real availability bug: closed WebChat sessions could pin frames on the shared outgoing list until MAX_QUEUE (64) blocked every live client. The two-sided change (drop on unregister + refuse enqueue without a live wsi) matches the root cause, helpers are clear, and the new test_ws cases lock the regression. I rebuilt and ran ./build/test_ws locally (all passed).

Must Fix

None.

Should Fix

  1. CHANGELOG Unreleased — This is an operator-visible availability fix (WebChat dead until restart after ~64 abandoned replies). Other WebChat/gateway fixes already live under ## [Unreleased] ### Fixed. Add a short Fixed bullet so the behavior change is discoverable without reading the PR.

  2. ws_send_to contract in ws.h — The implementation now intentionally returns 0 while discarding the frame when no socket is registered (lines 312–315 in ws.c). That matches the prior “no wsi → 0” contract, but the header still says only “0 on success.” One sentence that absent/closed sockets drop the frame and still return 0 would keep webchat_send callers honest.

Nice to Have

  1. A third regression that fills the queue to MAX_QUEUE with a live conn, unregisters it, then asserts a sibling can still ws_send_to would prove g_outgoing_count recovery under the exact capacity cliff (the current 128-orphan cases already cover the practical failure mode).
  2. ws_push_incoming still uses the pre-helper malloc/strdup pattern; optional follow-up to share ws_msg_new / ws_free_outgoing for symmetry only.

Positive Highlights

  • Root cause analysis is accurate: old ws_send_to enqueued before the wsi check and returned success with a dead socket, so orphans were never dequeued.
  • ws_drop_outgoing_locked correctly splices mid-list and keeps sibling conn_id frames; the unregister test asserts that ordering.
  • Checking g_conns[i].wsi on unregister and find paths avoids treating a cleared slot as live.
  • Focused diff (gateway WS + tests only); good consolidation of #115.
Open in Web View Automation 

Sent by Cursor Automation: Code review

Comment thread src/gateway/ws.c
Comment on lines +311 to +315
/* No live socket: enqueueing would pin the shared queue until restart. */
if (!wsi) {
pthread_mutex_unlock(&g_mutex);
ws_free_outgoing(m);
return 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Should Fix (docs): This path returns 0 after freeing the frame when no live socket is registered. That is the right availability behavior and matches the old “no wsi → 0” contract, but ws.h still documents ws_send_to as “0 on success” only.

Please extend the ws_send_to docstring to state that a well-formed webchat:<id> with no registered socket drops the payload and still returns 0, so webchat_send / channel callers do not treat that as delivery confirmation.

Comment thread tests/test_ws.c
Comment on lines +316 to +333
/** Replies to a session that was never connected must not fill MAX_QUEUE. */
static int test_send_to_absent_conn_does_not_fill_queue(void)
{
char buf[64];
size_t len;
int i;

ws_cleanup();
for (i = 0; i < 128; i++)
ASSERT(ws_send_to("webchat:99", "ghost") == 0);
ASSERT(ws_register_conn(8, (ws_conn_t)(intptr_t)8) == 0);
ASSERT(ws_send_to("webchat:8", "ok") == 0);
ASSERT(ws_dequeue_outgoing(8, buf, sizeof(buf), &len) == 1);
ASSERT(strcmp(buf, "ok") == 0);
ASSERT(ws_has_pending_outgoing(99) == 0);
ws_cleanup();
return 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice to Have: Strong coverage for never-registered and post-unregister orphans. An extra case that enqueues until MAX_QUEUE on a live conn, unregisters it, then asserts a sibling ws_send_to still succeeds would pin the exact capacity cliff (g_outgoing_count recovery) in one shot. Not blocking; the 128-orphan loops already exercise the failure mode this PR fixes.

@adriannoes adriannoes changed the title fix(gateway): drop WebChat frames for closed sockets (supersedes #115) fix(gateway): drop WebChat frames for closed sockets Oct 16, 2026
@adriannoes adriannoes self-assigned this Oct 16, 2026
WebChat stalling every live client until restart after ~64 abandoned replies is an operator-visible availability fix, so give it a Fixed bullet under Unreleased alongside the other gateway/WebChat entries.
ws_send_to intentionally frees the frame and returns 0 when the target socket is absent, unregistered, or closed (the no-live-wsi path), but the header only promised '0 on success'. State the drop in the contract so webchat_send callers can rely on the actual behavior.
@adriannoes

adriannoes commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator Author

Review response — done in e741b7c and d9e7f45: changelog entry for the WebChat fix, and ws.h now says frames for closed sockets are dropped while ws_send_to still returns 0.

Deferred as follow-ups: a queue-full-at-capacity regression test (the 128-orphan tests already cover the failure mode) and sharing the msg_new/free helpers in ws_push_incoming.

@adriannoes
adriannoes merged commit 32843fe into development Oct 17, 2026
1 check passed
@adriannoes
adriannoes deleted the fix/gateway-ws-frames branch October 17, 2026 14:36
adriannoes added a commit that referenced this pull request Oct 17, 2026
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