Skip to content

fix: prevent SIGSEGV when closing AsyncSession during active stream (#675) - #751

Open
minhduytran wants to merge 1 commit into
lexiforest:mainfrom
minhduytran:fix/async-session-close-crash
Open

fix: prevent SIGSEGV when closing AsyncSession during active stream (#675)#751
minhduytran wants to merge 1 commit into
lexiforest:mainfrom
minhduytran:fix/async-session-close-crash

Conversation

@minhduytran

@minhduytran minhduytran commented Apr 28, 2026

Copy link
Copy Markdown

Problem

Two race conditions cause segfaults/hangs when AsyncSession.close() or Response.aclose() is called during an active streaming request.

1. AsyncSession.close() ordering race (SIGSEGV / SIGABRT)

_closed is set AFTER await self.acurl.close(). Between these two lines, the release_curl callback fires, sees _closed == False, and calls acurl.remove_handle() on the already-destroyed multi-handle.

2. Response.aclose() missing abort signal (hang + crash)

aclose() never sets quit_now before awaiting astream_task. For SSE streams, await self.astream_task hangs forever, and the curl handle is left in a partially-initialized state that crashes on cleanup.

Fix

  • session.py: Set self._closed = True BEFORE await self.acurl.close()
  • models.py: Set quit_now event before awaiting stream task; suppress exceptions during await

Related

Fixes #675

The Curl.reset = lambda self: None monkey-patch workaround documented in the issue confirms this root cause.

Checklist

  • I have manually reviewed the changes and fully understand the code.

@minhduytran
minhduytran force-pushed the fix/async-session-close-crash branch from dc99b07 to d0f4e28 Compare April 30, 2026 07:40
When closing an async streaming response (SSE / long-lived chunked
stream) before the server ends it, aclose() must set quit_now before
awaiting the perform task. Without this:
1. The perform coroutine never receives the abort signal so aclose()
   hangs forever on long-lived connections.
2. The curl easy handle may be in a half-initialized streaming state
   when cleanup runs, causing curl_easy_reset() / curl_easy_cleanup()
   to free already-freed memory -> SIGSEGV / SIGABRT.
Also reorder AsyncSession.close() to set _closed = True before
await self.acurl.close(). release_curl() is called as a done-callback
on the stream task and checks self._closed to decide whether to call
acurl.remove_handle() or curl.close(). If _closed is set after the
multi-handle is destroyed, remove_handle() is called on a dead pointer.
Fixes lexiforest#675
@minhduytran
minhduytran force-pushed the fix/async-session-close-crash branch from d0f4e28 to 2568a09 Compare April 30, 2026 07:47
@lexiforest

Copy link
Copy Markdown
Owner

The original issue is about sync session, but this PR is about async session. Are they really related?

@lexiforest lexiforest added the needs more info This bug report is not complete label Jun 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs more info This bug report is not complete

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash when closing a session/response during an incomplete streaming request (SSE/stream mode)

3 participants