From 51fa61964f0b8f4d06db93a014b49b21ec53ea71 Mon Sep 17 00:00:00 2001 From: enrilzhou <49245495+enrilzhou@users.noreply.github.com> Date: Fri, 1 Jul 2022 05:13:59 +0800 Subject: [PATCH] feat(jest-leak-detector): use FinalizationRegistry when it exists to get rid of external dependency (#12973) --- CHANGELOG.md | 2 ++ packages/jest-leak-detector/src/index.ts | 39 ++++++++++++++++-------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ec5b8b40551..680ec6a667ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- `[jest-leak-detector]` Use native `FinalizationRegistry` when it exists to get rid of external C dependency ([#12973](https://github.com/facebook/jest/pull/12973)) + ### Fixes - `[jest-changed-files]` Fix a lock-up after repeated invocations ([#12757](https://github.com/facebook/jest/issues/12757)) diff --git a/packages/jest-leak-detector/src/index.ts b/packages/jest-leak-detector/src/index.ts index 33a5e8cefe97..f315fb592e11 100644 --- a/packages/jest-leak-detector/src/index.ts +++ b/packages/jest-leak-detector/src/index.ts @@ -4,6 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +/// import {promisify} from 'util'; import {setFlagsFromString} from 'v8'; @@ -15,6 +16,7 @@ const tick = promisify(setImmediate); export default class LeakDetector { private _isReferenceBeingHeld: boolean; + private _finalizationRegistry?: FinalizationRegistry; constructor(value: unknown) { if (isPrimitive(value)) { @@ -26,23 +28,36 @@ export default class LeakDetector { ); } - let weak: typeof import('weak-napi'); + // TODO: Remove the `if` and `weak-napi` when we drop node 12, as v14 supports FinalizationRegistry + if (globalThis.FinalizationRegistry) { + // When `_finalizationRegistry` is GCed the callback we set will no longer be called, + // so we need to assign it to `this` to keep it referenced + this._finalizationRegistry = new FinalizationRegistry(() => { + this._isReferenceBeingHeld = false; + }); - try { - // eslint-disable-next-line import/no-extraneous-dependencies - weak = require('weak-napi'); - } catch (err: any) { - if (!err || err.code !== 'MODULE_NOT_FOUND') { - throw err; + this._finalizationRegistry.register(value as object, undefined); + } else { + let weak: typeof import('weak-napi'); + + try { + // eslint-disable-next-line import/no-extraneous-dependencies + weak = require('weak-napi'); + } catch (err: any) { + if (!err || err.code !== 'MODULE_NOT_FOUND') { + throw err; + } + + throw new Error( + 'The leaking detection mechanism requires newer version of node that supports ' + + 'FinalizationRegistry, update your node or install the "weak-napi" package' + + 'which support current node version as a dependency on your main project.', + ); } - throw new Error( - 'The leaking detection mechanism requires the "weak-napi" package to be installed and work. ' + - 'Please install it as a dependency on your main project', - ); + weak(value as object, () => (this._isReferenceBeingHeld = false)); } - weak(value as object, () => (this._isReferenceBeingHeld = false)); this._isReferenceBeingHeld = true; // Ensure value is not leaked by the closure created by the "weak" callback.