mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 14:59:27 +08:00
* fix: add dispose method to prevent abort listener leak
Add dispose() method to StreamAbortController that explicitly removes
the abort event listener when stream ends normally. Previously, the
listener would only be removed when abort was triggered ({ once: true }),
but if the stream completed normally without abort, the listener would
remain attached until garbage collection.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* chore: format code
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
76 lines
1.6 KiB
TypeScript
76 lines
1.6 KiB
TypeScript
export type StreamAbortHandler = (reason: unknown) => void
|
|
|
|
export interface StreamAbortController {
|
|
abortController: AbortController
|
|
registerAbortHandler: (handler: StreamAbortHandler) => void
|
|
clearAbortTimeout: () => void
|
|
dispose: () => void
|
|
}
|
|
|
|
export const STREAM_TIMEOUT_REASON = 'stream timeout'
|
|
|
|
interface CreateStreamAbortControllerOptions {
|
|
timeoutMs: number
|
|
}
|
|
|
|
export const createStreamAbortController = (options: CreateStreamAbortControllerOptions): StreamAbortController => {
|
|
const { timeoutMs } = options
|
|
const abortController = new AbortController()
|
|
const signal = abortController.signal
|
|
|
|
let timeoutId: NodeJS.Timeout | undefined
|
|
let abortHandler: StreamAbortHandler | undefined
|
|
|
|
const clearAbortTimeout = () => {
|
|
if (!timeoutId) {
|
|
return
|
|
}
|
|
clearTimeout(timeoutId)
|
|
timeoutId = undefined
|
|
}
|
|
|
|
const handleAbort = () => {
|
|
clearAbortTimeout()
|
|
|
|
if (!abortHandler) {
|
|
return
|
|
}
|
|
|
|
abortHandler(signal.reason)
|
|
}
|
|
|
|
signal.addEventListener('abort', handleAbort, { once: true })
|
|
|
|
let disposed = false
|
|
|
|
const dispose = () => {
|
|
if (disposed) return
|
|
disposed = true
|
|
clearAbortTimeout()
|
|
signal.removeEventListener('abort', handleAbort)
|
|
}
|
|
|
|
const registerAbortHandler = (handler: StreamAbortHandler) => {
|
|
abortHandler = handler
|
|
|
|
if (signal.aborted) {
|
|
abortHandler(signal.reason)
|
|
}
|
|
}
|
|
|
|
if (timeoutMs > 0) {
|
|
timeoutId = setTimeout(() => {
|
|
if (!signal.aborted) {
|
|
abortController.abort(STREAM_TIMEOUT_REASON)
|
|
}
|
|
}, timeoutMs)
|
|
}
|
|
|
|
return {
|
|
abortController,
|
|
registerAbortHandler,
|
|
clearAbortTimeout,
|
|
dispose
|
|
}
|
|
}
|