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 { loggerService } from '@logger'
|
||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import { setApiServerEnabled as setApiServerEnabledAction } from '@renderer/store/settings'
|
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'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
const logger = loggerService.withContext('useApiServer')
|
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 = () => {
|
export const useApiServer = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
// FIXME: We currently store two copies of the config data in both the renderer and the main processes,
|
// 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()
|
||||||
}, [checkApiServerStatus])
|
}, [checkApiServerStatus])
|
||||||
|
|
||||||
// Listen for API server ready event
|
// Use ref to keep the latest checkApiServerStatus without causing re-subscription
|
||||||
|
const checkStatusRef = useRef(checkApiServerStatus)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cleanup = window.api.apiServer.onReady(() => {
|
checkStatusRef.current = checkApiServerStatus
|
||||||
logger.info('API server ready event received, checking status')
|
})
|
||||||
checkApiServerStatus()
|
|
||||||
})
|
|
||||||
|
|
||||||
return cleanup
|
// Create stable callback for the single instance subscription
|
||||||
}, [checkApiServerStatus])
|
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 {
|
return {
|
||||||
apiServerConfig,
|
apiServerConfig,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user