1'use strict';
2
3// TODO(joyeecheung): merge ongc.js and gcUntil from common/index.js
4// into this.
5
6// This function can be used to check if an object factor leaks or not,
7// but it needs to be used with care:
8// 1. The test should be set up with an ideally small
9//   --max-old-space-size or --max-heap-size, which combined with
10//   the maxCount parameter can reproduce a leak of the objects
11//   created by fn().
12// 2. This works under the assumption that if *none* of the objects
13//   created by fn() can be garbage-collected, the test would crash due
14//   to OOM.
15// 3. If *any* of the objects created by fn() can be garbage-collected,
16//   it is considered leak-free. The FinalizationRegistry is used to
17//   terminate the test early once we detect any of the object is
18//   garbage-collected to make the test less prone to false positives.
19//   This may be especially important for memory management relying on
20//   emphemeron GC which can be inefficient to deal with extremely fast
21//   heap growth.
22// Note that this can still produce false positives. When the test using
23// this function still crashes due to OOM, inspect the heap to confirm
24// if a leak is present (e.g. using heap snapshots).
25// The generateSnapshotAt parameter can be used to specify a count
26// interval to create the heap snapshot which may enforce a more thorough GC.
27// This can be tried for code paths that require it for the GC to catch up
28// with heap growth. However this type of forced GC can be in conflict with
29// other logic in V8 such as bytecode aging, and it can slow down the test
30// significantly, so it should be used scarcely and only as a last resort.
31async function checkIfCollectable(
32  fn, maxCount = 4096, generateSnapshotAt = Infinity, logEvery = 128) {
33  let anyFinalized = false;
34  let count = 0;
35
36  const f = new FinalizationRegistry(() => {
37    anyFinalized = true;
38  });
39
40  async function createObject() {
41    const obj = await fn();
42    f.register(obj);
43    if (count++ < maxCount && !anyFinalized) {
44      setImmediate(createObject, 1);
45    }
46    // This can force a more thorough GC, but can slow the test down
47    // significantly in a big heap. Use it with care.
48    if (count % generateSnapshotAt === 0) {
49      // XXX(joyeecheung): This itself can consume a bit of JS heap memory,
50      // but the other alternative writeHeapSnapshot can run into disk space
51      // not enough problems in the CI & be slower depending on file system.
52      // Just do this for now as long as it works and only invent some
53      // internal voodoo when we absolutely have no other choice.
54      require('v8').getHeapSnapshot().pause().read();
55      console.log(`Generated heap snapshot at ${count}`);
56    }
57    if (count % logEvery === 0) {
58      console.log(`Created ${count} objects`);
59    }
60    if (anyFinalized) {
61      console.log(`Found finalized object at ${count}, stop testing`);
62    }
63  }
64
65  createObject();
66}
67
68module.exports = {
69  checkIfCollectable,
70};
71