From b0a3e7195412712a304013ba8ea0d89dd93adad8 Mon Sep 17 00:00:00 2001 From: Sergey Tatarintsev Date: Wed, 7 Dec 2022 10:39:44 +0100 Subject: [PATCH] ci: Fix a couple of memory leaks in test setups (#16655) This fixes 2 memory leaks in our test setup: 1. Jest passes every file through transform and caches the results on disk and in memory. That included every generated client as well, so they were kept in memory from the time they were first loaded until process finishes, even though they are needed only for 1 test suite. Fixed by using `transformIgnorePatterns` options in jest config. 2. `debug` library keeps a list of last 100 logs that would be used for panic error messages later. If error object gets into this list, it will keep whole jest sandbox alive until process finishes. Fixed by clearing logs on `$disconnect` Fix #16594 --- .../client/src/runtime/getPrismaClient.ts | 7 +++++- .../client/tests/functional/jest.config.js | 22 ++++++++++++++++++- packages/debug/src/index.ts | 4 ++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/packages/client/src/runtime/getPrismaClient.ts b/packages/client/src/runtime/getPrismaClient.ts index eddc9b8b36b5..b22cd39c880d 100644 --- a/packages/client/src/runtime/getPrismaClient.ts +++ b/packages/client/src/runtime/getPrismaClient.ts @@ -1,5 +1,5 @@ import { Context, context } from '@opentelemetry/api' -import Debug from '@prisma/debug' +import Debug, { clearLogs } from '@prisma/debug' import { BatchTransactionOptions, BinaryEngine, @@ -591,6 +591,11 @@ export function getPrismaClient(config: GetPrismaClientConfig) { e.clientVersion = this._clientVersion throw e } finally { + // Debug module keeps a list of last 100 logs regardless of environment variables. + // This can cause a memory leak. It's especially bad in jest environment where keeping an + // error in this list will prevent jest sandbox from being GCed. Clearing logs on disconnect + // helps to avoid that + clearLogs() if (!this._dataProxy) { this._dmmf = undefined } diff --git a/packages/client/tests/functional/jest.config.js b/packages/client/tests/functional/jest.config.js index 11f3bdc5fd9a..180a3e265d07 100644 --- a/packages/client/tests/functional/jest.config.js +++ b/packages/client/tests/functional/jest.config.js @@ -1,10 +1,21 @@ 'use strict' const os = require('os') +const path = require('path') + +const runtimeDir = path.dirname(require.resolve('../../runtime')) +const packagesDir = path.resolve('..', '..', '..') module.exports = () => { const configCommon = { testMatch: ['**/*.ts', '!(**/*.d.ts)', '!(**/_utils/**)', '!(**/_*.ts)', '!(**/.generated/**)'], - transformIgnorePatterns: [], + // By default, jest passes every file it loads thorough a transform and caches result both on disk and in memory + // That includes all generated clients as well. So, unless we ignore them, they'd be kept in memory until test process + // is finished, even though they are needed for 1 test only + transformIgnorePatterns: [ + '[\\/]node_modules[\\/]', + escapeRegex(runtimeDir), + `${escapeRegex(packagesDir)}[\\/][^\\/]+[\\/]dist[\\/]`, + ], reporters: [ 'default', [ @@ -47,3 +58,12 @@ module.exports = () => { }, } } + +/** + * https://stackoverflow.com/a/6969486 + * @param {string} str + * @returns {string} + */ +function escapeRegex(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} diff --git a/packages/debug/src/index.ts b/packages/debug/src/index.ts index 1b02e99b1154..4b5137c12b1d 100644 --- a/packages/debug/src/index.ts +++ b/packages/debug/src/index.ts @@ -81,5 +81,9 @@ export function getLogs(numChars = 7500): string { return output.slice(-numChars) } +export function clearLogs() { + debugArgsHistory.length = 0 +} + export { Debug } export default Debug