From 4574c9f904d1eab727fd6246bf66da9101a0c311 Mon Sep 17 00:00:00 2001 From: Fredrik Strand Oseberg Date: Tue, 8 Nov 2022 12:35:53 +0100 Subject: [PATCH] Fix/embedded proxy memory leak (#2345) * Fixes a memory leak where events would trigger the data polling to restart. Any event would setup another polling interval, which would strain our database. Separated the logic for fetching the data and the polling, and made sure that the polling was only initialized once. --- src/lib/proxy/proxy-repository.ts | 10 +++++--- src/test/e2e/api/proxy/proxy.e2e.test.ts | 31 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/lib/proxy/proxy-repository.ts b/src/lib/proxy/proxy-repository.ts index 8d348297bc..141374ddd9 100644 --- a/src/lib/proxy/proxy-repository.ts +++ b/src/lib/proxy/proxy-repository.ts @@ -73,7 +73,7 @@ export class ProxyRepository } async start(): Promise { - await this.loadDataForToken(); + await this.dataPolling(); // Reload cached token data whenever something relevant has changed. // For now, simply reload all the data on any EventStore event. @@ -88,11 +88,15 @@ export class ProxyRepository clearTimeout(this.timer); } - private async loadDataForToken() { + private async dataPolling() { this.timer = setTimeout(async () => { - await this.loadDataForToken(); + await this.dataPolling(); }, this.randomizeDelay(this.interval, this.interval * 2)).unref(); + await this.loadDataForToken(); + } + + private async loadDataForToken() { try { this.features = await this.featuresForToken(); this.segments = await this.segmentsForToken(); diff --git a/src/test/e2e/api/proxy/proxy.e2e.test.ts b/src/test/e2e/api/proxy/proxy.e2e.test.ts index aabcea615f..3147934957 100644 --- a/src/test/e2e/api/proxy/proxy.e2e.test.ts +++ b/src/test/e2e/api/proxy/proxy.e2e.test.ts @@ -10,6 +10,7 @@ import { import { startOfHour } from 'date-fns'; import { IConstraint, IStrategyConfig } from '../../../../lib/types/model'; import { ProxyRepository } from '../../../../lib/proxy/proxy-repository'; +import { FEATURE_UPDATED } from '../../../../lib/types/events'; let app: IUnleashTest; let db: ITestDb; @@ -898,3 +899,33 @@ test('Should change fetch interval', async () => { expect(spy.mock.calls.length > 30).toBe(true); jest.useRealTimers(); }); + +test('Should not recursively set off timers on events', async () => { + jest.useFakeTimers(); + + const frontendToken = await createApiToken(ApiTokenType.FRONTEND); + const user = await app.services.apiTokenService.getUserForToken( + frontendToken.secret, + ); + + const spy = jest.spyOn(ProxyRepository.prototype as any, 'dataPolling'); + const proxyRepository = new ProxyRepository( + { + getLogger, + frontendApi: { refreshIntervalInMs: 5000 }, + }, + db.stores, + app.services, + user, + ); + + await proxyRepository.start(); + + db.stores.eventStore.emit(FEATURE_UPDATED); + + jest.advanceTimersByTime(10000); + + proxyRepository.stop(); + expect(spy.mock.calls.length < 3).toBe(true); + jest.useRealTimers(); +});