From bfbdc1f65a4d346dede4f5d046e218b548a0a076 Mon Sep 17 00:00:00 2001 From: sjaanus Date: Thu, 3 Jul 2025 11:17:51 +0300 Subject: [PATCH] fix: tear down also event handlers in addon service --- src/lib/features/events/event-service.ts | 7 ++ src/lib/services/addon-service.ts | 94 ++++++++++++++++-------- 2 files changed, 70 insertions(+), 31 deletions(-) diff --git a/src/lib/features/events/event-service.ts b/src/lib/features/events/event-service.ts index b6acb84642..89141ff00a 100644 --- a/src/lib/features/events/event-service.ts +++ b/src/lib/features/events/event-service.ts @@ -110,6 +110,13 @@ export default class EventService { return this.eventStore.on(eventName, listener); } + off( + eventName: string | symbol, + listener: (...args: any[]) => void, + ): EventEmitter { + return this.eventStore.off(eventName, listener); + } + private async enhanceEventsWithTags( events: IBaseEvent[], ): Promise { diff --git a/src/lib/services/addon-service.ts b/src/lib/services/addon-service.ts index 130919d880..fec3599a42 100644 --- a/src/lib/services/addon-service.ts +++ b/src/lib/services/addon-service.ts @@ -59,6 +59,8 @@ export default class AddonService { fetchAddonConfigs: (() => Promise) & memoizee.Memoized<() => Promise>; + private eventHandlers: Map void>; + constructor( { addonStore, @@ -83,6 +85,7 @@ export default class AddonService { this.logger = getLogger('services/addon-service.js'); this.tagTypeService = tagTypeService; this.eventService = eventService; + this.eventHandlers = new Map(); this.addonProviders = addons || @@ -124,42 +127,57 @@ export default class AddonService { } registerEventHandler(): void { - SUPPORTED_EVENTS.forEach((eventName) => - this.eventService.onEvent(eventName, this.handleEvent(eventName)), - ); + SUPPORTED_EVENTS.forEach((eventName) => { + const handler = this.handleEvent(eventName); + this.eventHandlers.set(eventName, handler); + this.eventService.onEvent(eventName, handler); + }); } handleEvent(eventName: string): (event: IEvent) => void { const { addonProviders } = this; return (event) => { - this.fetchAddonConfigs().then((addonInstances) => { - addonInstances - .filter((addon) => addon.events.includes(eventName)) - .filter( - (addon) => - !event.project || - !addon.projects || - addon.projects.length === 0 || - addon.projects[0] === WILDCARD_OPTION || - addon.projects.includes(event.project), - ) - .filter( - (addon) => - !event.environment || - !addon.environments || - addon.environments.length === 0 || - addon.environments[0] === WILDCARD_OPTION || - addon.environments.includes(event.environment), - ) - .filter((addon) => addonProviders[addon.provider]) - .forEach((addon) => - addonProviders[addon.provider].handleEvent( - event, - addon.parameters, - addon.id, - ), - ); - }); + this.fetchAddonConfigs() + .then((addonInstances) => { + addonInstances + .filter((addon) => addon.events.includes(eventName)) + .filter( + (addon) => + !event.project || + !addon.projects || + addon.projects.length === 0 || + addon.projects[0] === WILDCARD_OPTION || + addon.projects.includes(event.project), + ) + .filter( + (addon) => + !event.environment || + !addon.environments || + addon.environments.length === 0 || + addon.environments[0] === WILDCARD_OPTION || + addon.environments.includes(event.environment), + ) + .filter((addon) => addonProviders[addon.provider]) + .forEach((addon) => + addonProviders[addon.provider].handleEvent( + event, + addon.parameters, + addon.id, + ), + ); + }) + .catch((error) => { + if (error.message === 'aborted') { + this.logger.debug( + `Addon event handling aborted during shutdown for event ${eventName}`, + ); + } else { + this.logger.warn( + `Failed to handle addon event ${eventName}:`, + error, + ); + } + }); }; } @@ -349,6 +367,20 @@ export default class AddonService { } destroy(): void { + // Clean up event handlers first to prevent race conditions during teardown + this.eventHandlers.forEach((handler, eventName) => { + try { + this.eventService.off(eventName, handler); + } catch (error) { + this.logger.debug( + `Failed to remove event handler for ${eventName}:`, + error, + ); + } + }); + this.eventHandlers.clear(); + + // Then destroy addon providers Object.values(this.addonProviders).forEach((addon) => addon.destroy?.(), );