Skip to content

fix: stop a disconnected client from crashing the response-write thread - #828

Merged
seonghobae merged 4 commits into
ContextualWisdomLab:mainfrom
seonghobae:fix/http-response-write-crash-and-auth-race
Aug 25, 2026
Merged

fix: stop a disconnected client from crashing the response-write thread#828
seonghobae merged 4 commits into
ContextualWisdomLab:mainfrom
seonghobae:fix/http-response-write-crash-and-auth-race

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • do_POST wraps every route in a single try/except Exception that reports any failure via _send_error(500, ...) -> _send(...). If the original failure was _send() itself raising because the client already closed its socket (gave up waiting on a slow /v1/chat/completions "auto" run), the except handler's own _send() call hits the same closed socket and raises again — unhandled this time, crashing the request-handling thread. Visible in server logs as two consecutive tracebacks per affected request.
  • Routed every response-writing method (_send, _send_text, _send_sse, _begin_sse, _write_sse) through one _write_response helper that swallows BrokenPipeError / ConnectionError / OSError: a client that disconnected mid-response is not a server error, there is nothing left to deliver, and letting the exception propagate only produces a second identical failure in the error-reporting path itself. Unrelated exceptions (e.g. a real bug in payload construction) still propagate normally — only disconnect-shaped errors are swallowed.
  • Found while re-running a LineageWeave operator backfill script against a live local instance of this service: a client-side timeout waiting on a slow "auto"-mode response reliably reproduced the crash.

Test plan

  • Added tests/test_http_response_write_disconnect_safety.py — asserts _write_response swallows BrokenPipeError/ConnectionResetError but still propagates an unrelated TypeError
  • python -m pytest tests/test_http_response_write_disconnect_safety.py -v — 2 passed
  • Full suite: python -m pytest tests/ -k "http_honesty or server or commercial" -q — 1082 passed, 0 failed

🤖 Generated with Claude Code


Open in Devin Review

Summary by CodeRabbit

  • 버그 수정

    • 클라이언트가 연결을 종료한 상태에서 응답을 전송해도 서버 오류가 발생하지 않도록 개선했습니다.
    • JSON, 텍스트, 실시간 스트리밍 응답 전반에서 연결 종료 상황을 안전하게 처리합니다.
  • 테스트

    • 연결 끊김으로 발생하는 주요 오류가 정상적으로 무시되는지 검증하는 테스트를 추가했습니다.
    • 관련 없는 오류는 계속 표시되도록 동작을 확인했습니다.

do_POST wraps every route in a single try/except Exception that, on any
failure, calls _send_error(500, ...) -> _send(...) to report it. If the
original failure WAS _send() itself raising because the client already
closed the socket (gave up waiting on a slow /v1/chat/completions "auto"
run), the except handler's own _send() call hits the same closed socket
and raises again -- this second BrokenPipeError is unhandled and crashes
the request-handling thread (visible in server logs as two consecutive
tracebacks per request).

Route every response-writing method (_send, _send_text, _send_sse,
_begin_sse, _write_sse) through one _write_response helper that swallows
BrokenPipeError/ConnectionError/OSError: a client that disconnected mid-
response is not a server error, there's nothing left to deliver, and
letting the exception propagate only produces a second identical failure
in the error-reporting path itself.

Found while re-running LineageWeave's keyman-extraction backfill against
a live orchestrator instance: a client-side timeout waiting on a slow
"auto" response reliably reproduced the crash in this repo's server.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 53 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fa9f32d3-395b-4c35-bef3-605a9b0ae615

📥 Commits

Reviewing files that changed from the base of the PR and between 42b6e4c and ab154d2.

📒 Files selected for processing (2)
  • contextual_orchestrator/server.py
  • tests/test_http_response_write_disconnect_safety.py
📝 Walkthrough

Walkthrough

HTTP 응답 작성에 _write_response 래퍼를 추가했습니다. 연결 종료 관련 예외는 무시합니다. JSON, 텍스트, 완료형 SSE, 증분 SSE 전송에 적용했습니다. 동작을 검증하는 테스트를 추가했습니다.

Changes

HTTP 응답 연결 종료 안전성

Layer / File(s) Summary
응답 작성 래퍼와 전송 경로 적용
contextual_orchestrator/server.py
_write_responseBrokenPipeError, ConnectionError, OSError를 처리합니다. JSON, 텍스트, 완료형 SSE, 증분 SSE 헤더·프레임·flush 작업이 래퍼를 사용합니다.
연결 종료 예외 검증
tests/test_http_response_write_disconnect_safety.py
BrokenPipeErrorConnectionResetError를 무시하는 동작을 검증합니다. TypeError는 계속 전파되는지 검증합니다.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: 🟡 Moderate · up to 42b6e

The change prevents disconnected clients from crashing response threads, but failed SSE writes currently allow the backend stream to continue running and retain execution capacity, which can cause healthy requests to receive 503 responses after repeated disconnects. Merge readiness is blocked until the stream exits on write failure and this behavior is covered by a regression test.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 연결이 끊긴 클라이언트로 인해 응답 작성 스레드가 중단되는 문제를 수정한다는 주요 변경 사항을 명확하게 설명합니다.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@seonghobae
seonghobae enabled auto-merge (squash) August 24, 2026 00:55
@opencode-agent
opencode-agent Bot disabled auto-merge August 24, 2026 02:17
@seonghobae
seonghobae enabled auto-merge August 25, 2026 02:28

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: Disconnected streams logged as successful 200

When a client disconnects mid-stream, _stream_route_completion now returns cleanly, so do_POST still records the analytics event at server.py:5270-5280 with status_code: 200 and response_streamed: True. Aborted deliveries are counted as fully successful streamed responses.

(Refers to this code)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@opencode-agent
opencode-agent Bot disabled auto-merge August 25, 2026 02:57
@seonghobae

Copy link
Copy Markdown
Contributor Author

Merge-gate evidence (2026-08-24): Deep diff review + fixes applied; all required checks green on current head except strix (org-wide NVIDIA NIM quota exhaustion — external provider-capacity blocker; serialization fix in ContextualWisdomLab/.github#1297). Full local suite green on this head.

# Conflicts:
#	contextual_orchestrator/server.py
@seonghobae seonghobae closed this Aug 25, 2026
@seonghobae seonghobae reopened this Aug 25, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

Open in Devin Review

Comment on lines 6061 to +6063
for delta in orchestrator.stream_route(messages, workflow_run_id=run_id):
self._write_sse(frame({"content": delta}))
self._write_sse(frame({}, finish="stop"))
if not self._write_sse(frame({"content": delta})):
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📝 Info: Generator closed by refcount, not explicit close

On disconnect, the for loop returns without explicitly closing the stream_route generator. CPython refcounting closes it promptly so upstream work stops, but this relies on refcount semantics rather than an explicit close().

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@seonghobae

Copy link
Copy Markdown
Contributor Author

Merge-gate evidence (2026-08-25): Integrated with the merged opaque-session auth design (#788) — purpose resolution now composes with session/bearer validation, denials and audit_replay access are audited, and state-changing admin routes keep the same-origin check. Full local suite green on this head. All required hosted checks green except strix (org-wide NVIDIA NIM quota exhaustion — external provider-capacity blocker; serialization fix in ContextualWisdomLab/.github#1297).

@seonghobae
seonghobae merged commit de9f46b into ContextualWisdomLab:main Aug 25, 2026
31 of 32 checks passed
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