Skip to content

Commit f541cc0

Browse files
authored
fix potential memory leak in Concast, add tests (#11358)
1 parent 7ef700d commit f541cc0

File tree

3 files changed

+65
-0
lines changed

3 files changed

+65
-0
lines changed

src/testing/matchers/index.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ interface ApolloCustomMatchers<R = void, T = {}> {
4343
| ProfiledHook<any, any>
4444
? (count: number, options?: NextRenderOptions) => Promise<R>
4545
: { error: "matcher needs to be called on a ProfiledComponent instance" };
46+
47+
toBeGarbageCollected: T extends WeakRef<any>
48+
? () => Promise<R>
49+
: { error: "matcher needs to be called on a WeakRef instance" };
4650
}
4751

4852
declare global {

src/testing/matchers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { expect } from "@jest/globals";
22
import { toMatchDocument } from "./toMatchDocument.js";
33
import { toHaveSuspenseCacheEntryUsing } from "./toHaveSuspenseCacheEntryUsing.js";
44
import { toRerender, toRenderExactlyTimes } from "./ProfiledComponent.js";
5+
import { toBeGarbageCollected } from "./toBeGarbageCollected.js";
56

67
expect.extend({
78
toHaveSuspenseCacheEntryUsing,
89
toMatchDocument,
910
toRerender,
1011
toRenderExactlyTimes,
12+
toBeGarbageCollected,
1113
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { MatcherFunction } from "expect";
2+
3+
// this is necessary because this file is picked up by `tsc` (it's not a test),
4+
// but our main `tsconfig.json` doesn't include `"ES2021.WeakRef"` on purpose
5+
declare class WeakRef<T extends WeakKey> {
6+
constructor(target: T);
7+
deref(): T | undefined;
8+
}
9+
10+
export const toBeGarbageCollected: MatcherFunction<[weakRef: WeakRef<any>]> =
11+
async function (actual) {
12+
const hint = this.utils.matcherHint("toBeGarbageCollected");
13+
14+
if (!(actual instanceof WeakRef)) {
15+
throw new Error(
16+
hint +
17+
"\n\n" +
18+
`Expected value to be a WeakRef, but it was a ${typeof actual}.`
19+
);
20+
}
21+
22+
let pass = false;
23+
let interval: NodeJS.Timeout | undefined;
24+
let timeout: NodeJS.Timeout | undefined;
25+
await Promise.race([
26+
new Promise<void>((resolve) => {
27+
timeout = setTimeout(resolve, 1000);
28+
}),
29+
new Promise<void>((resolve) => {
30+
interval = setInterval(() => {
31+
global.gc!();
32+
pass = actual.deref() === undefined;
33+
if (pass) {
34+
resolve();
35+
}
36+
}, 1);
37+
}),
38+
]);
39+
40+
clearInterval(interval);
41+
clearTimeout(timeout);
42+
43+
return {
44+
pass,
45+
message: () => {
46+
if (pass) {
47+
return (
48+
hint +
49+
"\n\n" +
50+
"Expected value to not be cache-collected, but it was."
51+
);
52+
}
53+
54+
return (
55+
hint + "\n\n Expected value to be cache-collected, but it was not."
56+
);
57+
},
58+
};
59+
};

0 commit comments

Comments
 (0)