%PDF- %PDF-
Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/test/common/ |
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/test/common/gc.js |
'use strict'; const wait = require('timers/promises').setTimeout; // TODO(joyeecheung): merge ongc.js and gcUntil from common/index.js // into this. // This function can be used to check if an object factor leaks or not, // but it needs to be used with care: // 1. The test should be set up with an ideally small // --max-old-space-size or --max-heap-size, which combined with // the maxCount parameter can reproduce a leak of the objects // created by fn(). // 2. This works under the assumption that if *none* of the objects // created by fn() can be garbage-collected, the test would crash due // to OOM. // 3. If *any* of the objects created by fn() can be garbage-collected, // it is considered leak-free. The FinalizationRegistry is used to // terminate the test early once we detect any of the object is // garbage-collected to make the test less prone to false positives. // This may be especially important for memory management relying on // emphemeron GC which can be inefficient to deal with extremely fast // heap growth. // Note that this can still produce false positives. When the test using // this function still crashes due to OOM, inspect the heap to confirm // if a leak is present (e.g. using heap snapshots). // The generateSnapshotAt parameter can be used to specify a count // interval to create the heap snapshot which may enforce a more thorough GC. // This can be tried for code paths that require it for the GC to catch up // with heap growth. However this type of forced GC can be in conflict with // other logic in V8 such as bytecode aging, and it can slow down the test // significantly, so it should be used scarcely and only as a last resort. async function checkIfCollectable( fn, maxCount = 4096, generateSnapshotAt = Infinity, logEvery = 128) { let anyFinalized = false; let count = 0; const f = new FinalizationRegistry(() => { anyFinalized = true; }); async function createObject() { const obj = await fn(); f.register(obj); if (count++ < maxCount && !anyFinalized) { setImmediate(createObject, 1); } // This can force a more thorough GC, but can slow the test down // significantly in a big heap. Use it with care. if (count % generateSnapshotAt === 0) { // XXX(joyeecheung): This itself can consume a bit of JS heap memory, // but the other alternative writeHeapSnapshot can run into disk space // not enough problems in the CI & be slower depending on file system. // Just do this for now as long as it works and only invent some // internal voodoo when we absolutely have no other choice. require('v8').getHeapSnapshot().pause().read(); console.log(`Generated heap snapshot at ${count}`); } if (count % logEvery === 0) { console.log(`Created ${count} objects`); } if (anyFinalized) { console.log(`Found finalized object at ${count}, stop testing`); } } createObject(); } // Repeat an operation and give GC some breathing room at every iteration. async function runAndBreathe(fn, repeat, waitTime = 20) { for (let i = 0; i < repeat; i++) { await fn(); await wait(waitTime); } } /** * This requires --expose-internals. * This function can be used to check if an object factory leaks or not by * iterating over the heap and count objects with the specified class * (which is checked by looking up the prototype chain). * @param {(i: number) => number} fn The factory receiving iteration count * and returning number of objects created. The return value should be * precise otherwise false negatives can be produced. * @param {Function} klass The class whose object is used to count the objects * @param {number} count Number of iterations that this check should be done * @param {number} waitTime Optional breathing time for GC. */ async function checkIfCollectableByCounting(fn, klass, count, waitTime = 20) { const { internalBinding } = require('internal/test/binding'); const { countObjectsWithPrototype } = internalBinding('heap_utils'); const { prototype, name } = klass; const initialCount = countObjectsWithPrototype(prototype); console.log(`Initial count of ${name}: ${initialCount}`); let totalCreated = 0; for (let i = 0; i < count; ++i) { const created = await fn(i); totalCreated += created; console.log(`#${i}: created ${created} ${name}, total ${totalCreated}`); await wait(waitTime); // give GC some breathing room. const currentCount = countObjectsWithPrototype(prototype); const collected = totalCreated - (currentCount - initialCount); console.log(`#${i}: counted ${currentCount} ${name}, collected ${collected}`); if (collected > 0) { console.log(`Detected ${collected} collected ${name}, finish early`); return; } } await wait(waitTime); // give GC some breathing room. const currentCount = countObjectsWithPrototype(prototype); const collected = totalCreated - (currentCount - initialCount); console.log(`Last count: counted ${currentCount} ${name}, collected ${collected}`); // Some objects with the prototype can be collected. if (collected > 0) { console.log(`Detected ${collected} collected ${name}`); return; } throw new Error(`${name} cannot be collected`); } module.exports = { checkIfCollectable, runAndBreathe, checkIfCollectableByCounting, };