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