From 1b6db3da22b24cc026c5234ccbf6f3dd05e9e9b7 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Wed, 21 Aug 2024 15:07:01 +0200 Subject: [PATCH] Loki language provider: don't cache empty array while querying (#92092) * Loki language provider: don't cache empty array while querying * Prettier * Unfocus test * chore: add extra assertion --- .../datasource/loki/LanguageProvider.test.ts | 18 +++++++++ .../datasource/loki/LanguageProvider.ts | 37 ++++++++++++++----- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/public/app/plugins/datasource/loki/LanguageProvider.test.ts b/public/app/plugins/datasource/loki/LanguageProvider.test.ts index 5a63f43c2e2..9371a451e37 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.test.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.test.ts @@ -268,6 +268,24 @@ describe('Language completion provider', () => { end: expect.any(Number), }); }); + + it('should use a single promise to resolve values', async () => { + const datasource = setup({ testkey: ['label1_val1', 'label1_val2'], label2: [] }); + const provider = await getLanguageProvider(datasource); + const requestSpy = jest.spyOn(provider, 'request'); + const promise1 = provider.fetchLabelValues('testkey'); + const promise2 = provider.fetchLabelValues('testkey'); + const promise3 = provider.fetchLabelValues('testkeyNOPE'); + expect(requestSpy).toHaveBeenCalledTimes(2); + + const values1 = await promise1; + const values2 = await promise2; + const values3 = await promise3; + + expect(values1).toStrictEqual(values2); + expect(values2).not.toStrictEqual(values3); + expect(requestSpy).toHaveBeenCalledTimes(2); + }); }); describe('fetchLabels', () => { diff --git a/public/app/plugins/datasource/loki/LanguageProvider.ts b/public/app/plugins/datasource/loki/LanguageProvider.ts index b833308422d..20f79dfa375 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.ts @@ -31,6 +31,7 @@ export default class LokiLanguageProvider extends LanguageProvider { */ private seriesCache = new LRUCache>({ max: 10 }); private labelsCache = new LRUCache({ max: 10 }); + private labelsPromisesCache = new LRUCache>({ max: 10 }); constructor(datasource: LokiDatasource, initialValues?: any) { super(); @@ -272,18 +273,34 @@ export default class LokiLanguageProvider extends LanguageProvider { const cacheKey = this.generateCacheKey(url, start, end, paramCacheKey); - let labelValues = this.labelsCache.get(cacheKey); - if (!labelValues) { - // Clear value when requesting new one. Empty object being truthy also makes sure we don't request twice. - this.labelsCache.set(cacheKey, []); - const res = await this.request(url, params); - if (Array.isArray(res)) { - labelValues = res.slice().sort(); - this.labelsCache.set(cacheKey, labelValues); - } + // Values in cache, return + const labelValues = this.labelsCache.get(cacheKey); + if (labelValues) { + return labelValues; } - return labelValues ?? []; + // Promise in cache, return + let labelValuesPromise = this.labelsPromisesCache.get(cacheKey); + if (labelValuesPromise) { + return labelValuesPromise; + } + + labelValuesPromise = new Promise(async (resolve) => { + try { + const data = await this.request(url, params); + if (Array.isArray(data)) { + const labelValues = data.slice().sort(); + this.labelsCache.set(cacheKey, labelValues); + this.labelsPromisesCache.delete(cacheKey); + resolve(labelValues); + } + } catch (error) { + console.error(error); + resolve([]); + } + }); + this.labelsPromisesCache.set(cacheKey, labelValuesPromise); + return labelValuesPromise; } /**