Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/__tests__/utils/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Hooks } from 'taskcluster-client-web';

import { createStore } from '../../common/store';
import type { Store } from '../../common/store';
import { clearTreeherderCaches } from '../../logic/treeherder';
import { fftkde } from '../../utils/kde.js';

let store: Store;
Expand Down Expand Up @@ -98,6 +99,9 @@ afterEach(() => {
// Also restore the fetch mock
fetchMock.mockReset();

// Clear the treeherder memoization caches so that tests aren't polluted.
clearTreeherderCaches();

// Clear perfcompare cookies so they don't leak between tests.
for (const cookie of document.cookie.split(';')) {
const name = cookie.split('=')[0].trim();
Expand Down
4 changes: 2 additions & 2 deletions src/components/CompareResults/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
MANN_WHITNEY_U,
} from '../../common/constants';
import {
fetchCompareResults,
fetchFakeCompareResults,
memoizedFetchCompareResults,
memoizedFetchRevisionForRepository,
} from '../../logic/treeherder';
import {
Expand Down Expand Up @@ -147,7 +147,7 @@ async function fetchCompareResultsOnTreeherder({
testVersion?: TestVersion;
}) {
const promises = newRevs.map((newRev, i) =>
fetchCompareResults({
memoizedFetchCompareResults({
baseRev,
baseRepo,
newRev,
Expand Down
4 changes: 2 additions & 2 deletions src/components/CompareResults/overTimeLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
MANN_WHITNEY_U,
} from '../../common/constants';
import {
fetchCompareOverTimeResults,
memoizedFetchCompareOverTimeResults,
memoizedFetchRevisionForRepository,
} from '../../logic/treeherder';
import {
Expand Down Expand Up @@ -147,7 +147,7 @@ async function fetchCompareOverTimeResultsOnTreeherder({
testVersion: TestVersion;
}) {
const promises = newRevs.map((newRev, i) =>
fetchCompareOverTimeResults({
memoizedFetchCompareOverTimeResults({
baseRepo,
newRev,
newRepo: newRepos[i],
Expand Down
66 changes: 64 additions & 2 deletions src/logic/treeherder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,53 @@ export const memoizedFetchRevisionForRepository = moize(
{ isPromise: true, isShallowEqual: true, maxSize: 5 },
) as typeof fetchRevisionForRepository;

// Memoized versions of the compare results fetch functions.
// Going back and forth between the t-test and MWU views re-runs the route
// loader, which would refetch the results from Treeherder every time.
// We cache the value in memory which is keyed on the request parameters
const compareResultsCache = new Map<string, Promise<CompareResultsItem[]>>();
export function memoizedFetchCompareResults(
params: FetchProps,
): Promise<CompareResultsItem[]> {
const normalizedParams = {
...params,
testVersion: params.testVersion ?? STUDENT_T,
};
const key = JSON.stringify(normalizedParams);
const cached = compareResultsCache.get(key);
if (cached) return cached;
const promise = fetchCompareResults(normalizedParams).catch((error) => {
compareResultsCache.delete(key);
throw error;
});
compareResultsCache.set(key, promise);
return promise;
}

const compareOverTimeResultsCache = new Map<
string,
Promise<CompareResultsItem[]>
>();
export function memoizedFetchCompareOverTimeResults(
params: FetchOverTimeProps,
): Promise<CompareResultsItem[]> {
const normalizedParams = {
...params,
testVersion: params.testVersion ?? STUDENT_T,
};
const key = JSON.stringify(normalizedParams);
const cached = compareOverTimeResultsCache.get(key);
if (cached) return cached;
const promise = fetchCompareOverTimeResults(normalizedParams).catch(
(error) => {
compareOverTimeResultsCache.delete(key);
throw error;
},
);
compareOverTimeResultsCache.set(key, promise);
return promise;
}

// Memoized versions of the subtest fetch functions.
// Each RevisionRow with has_subtests fires its own fetch, so memoization
// prevents duplicate network calls when rows share the same signature IDs,
Expand All @@ -294,7 +341,10 @@ export function memoizedFetchSubtestsCompareResults(
const key = JSON.stringify(params);
const cached = subtestCompareResultsCache.get(key);
if (cached) return cached;
const promise = fetchSubtestsCompareResults(params);
const promise = fetchSubtestsCompareResults(params).catch((error) => {
subtestCompareResultsCache.delete(key);
throw error;
});
subtestCompareResultsCache.set(key, promise);
return promise;
}
Expand All @@ -309,11 +359,23 @@ export function memoizedFetchSubtestsCompareOverTimeResults(
const key = JSON.stringify(params);
const cached = subtestCompareOverTimeResultsCache.get(key);
if (cached) return cached;
const promise = fetchSubtestsCompareOverTimeResults(params);
const promise = fetchSubtestsCompareOverTimeResults(params).catch((error) => {
subtestCompareOverTimeResultsCache.delete(key);
throw error;
});
Comment on lines +362 to +365

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of these were poisoning the cache when the network request fails, so simple fix is to remove key from cache when it does so

subtestCompareOverTimeResultsCache.set(key, promise);
return promise;
}

// Clears all the memoization caches in this file. Only tests should need
// this, to isolate the module-level caches between test cases.
export function clearTreeherderCaches() {
compareResultsCache.clear();
compareOverTimeResultsCache.clear();
subtestCompareResultsCache.clear();
subtestCompareOverTimeResultsCache.clear();
}

export async function fetchJobInformationFromJobId(
repo: string,
jobId: number,
Expand Down