mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
fix: prevent EventEmitter memory leak in useApiServer hook (#11385)
Implement single instance IPC subscription pattern to resolve MaxListenersExceededWarning. Previously, each component using useApiServer would register a separate 'api-server:ready' listener, and React strict mode double rendering would quickly exceed the 10 listener limit. Changes: - Add module-level subscription manager with onReadyCallbacks Set - Ensure only one IPC listener is registered regardless of component count - Use useRef to maintain stable callback references - Properly cleanup subscriptions when all components unmount This maintains existing behavior while keeping listener count constant at 1. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c48f222cdb
commit
62309ae1bf
@ -1,11 +1,31 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { setApiServerEnabled as setApiServerEnabledAction } from '@renderer/store/settings'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const logger = loggerService.withContext('useApiServer')
|
||||
|
||||
// Module-level single instance subscription to prevent EventEmitter memory leak
|
||||
// Only one IPC listener will be registered regardless of how many components use this hook
|
||||
const onReadyCallbacks = new Set<() => void>()
|
||||
let removeIpcListener: (() => void) | null = null
|
||||
|
||||
const ensureIpcSubscribed = () => {
|
||||
if (!removeIpcListener) {
|
||||
removeIpcListener = window.api.apiServer.onReady(() => {
|
||||
onReadyCallbacks.forEach((cb) => cb())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const cleanupIpcIfEmpty = () => {
|
||||
if (onReadyCallbacks.size === 0 && removeIpcListener) {
|
||||
removeIpcListener()
|
||||
removeIpcListener = null
|
||||
}
|
||||
}
|
||||
|
||||
export const useApiServer = () => {
|
||||
const { t } = useTranslation()
|
||||
// FIXME: We currently store two copies of the config data in both the renderer and the main processes,
|
||||
@ -102,15 +122,28 @@ export const useApiServer = () => {
|
||||
checkApiServerStatus()
|
||||
}, [checkApiServerStatus])
|
||||
|
||||
// Listen for API server ready event
|
||||
// Use ref to keep the latest checkApiServerStatus without causing re-subscription
|
||||
const checkStatusRef = useRef(checkApiServerStatus)
|
||||
useEffect(() => {
|
||||
const cleanup = window.api.apiServer.onReady(() => {
|
||||
logger.info('API server ready event received, checking status')
|
||||
checkApiServerStatus()
|
||||
checkStatusRef.current = checkApiServerStatus
|
||||
})
|
||||
|
||||
return cleanup
|
||||
}, [checkApiServerStatus])
|
||||
// Create stable callback for the single instance subscription
|
||||
const handleReady = useCallback(() => {
|
||||
logger.info('API server ready event received, checking status')
|
||||
checkStatusRef.current()
|
||||
}, [])
|
||||
|
||||
// Listen for API server ready event using single instance subscription
|
||||
useEffect(() => {
|
||||
ensureIpcSubscribed()
|
||||
onReadyCallbacks.add(handleReady)
|
||||
|
||||
return () => {
|
||||
onReadyCallbacks.delete(handleReady)
|
||||
cleanupIpcIfEmpty()
|
||||
}
|
||||
}, [handleReady])
|
||||
|
||||
return {
|
||||
apiServerConfig,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user