Merge pull request #33030 from backstage/sennyeya/mem-leak

fix(openapi-jest-utils): memory leak in proxy clean up
This commit is contained in:
Fredrik Adelöw
2026-04-14 10:23:29 +02:00
committed by GitHub
3 changed files with 43 additions and 10 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-openapi-utils': patch
---
Fixes a memory leak during `wrapServer` where stopped servers weren't removed from the clean up list.
@@ -58,14 +58,40 @@ export class Proxy {
this.express.server = server;
}
stop() {
async stop() {
if (Object.keys(this.#openRequests).length > 0) {
throw new Error('There are still open requests');
}
this.server.stop();
if (!this.express.server) {
throw new Error(
'Proxy server was not initialized with an express server',
);
}
const results = await Promise.allSettled([
this.server.stop(),
// If this isn't expressly closed, it will cause a jest memory leak warning.
this.express.server?.close();
// If this isn't expressly closed, it will cause a jest memory leak warning.
new Promise((resolve, reject) => {
this.express.server?.close(err => {
if (err) {
reject(err);
} else {
resolve(undefined);
}
});
}),
]);
if (results.some(result => result.status === 'rejected')) {
const errors = results
.filter(
(result): result is PromiseRejectedResult =>
result.status === 'rejected',
)
.map(result => result.reason);
throw new Error(
`Failed to stop proxy server: ${errors.map(e => e.message).join(', ')}`,
);
}
}
get url() {
@@ -17,7 +17,7 @@ import { Express } from 'express';
import { Server } from 'node:http';
import { Proxy } from './proxy/setup';
const proxiesToCleanup: Proxy[] = [];
const proxiesToCleanup: Set<Proxy> = new Set();
/**
* !!! THIS CURRENTLY ONLY SUPPORTS SUPERTEST !!!
@@ -29,7 +29,7 @@ const proxiesToCleanup: Proxy[] = [];
*/
export async function wrapServer(app: Express): Promise<Server> {
const proxy = new Proxy();
proxiesToCleanup.push(proxy);
proxiesToCleanup.add(proxy);
await proxy.setup();
const server = app.listen(proxy.forwardTo.port);
@@ -48,10 +48,12 @@ function registerHooks() {
}
registered = true;
afterAll(() => {
for (const proxy of proxiesToCleanup) {
proxy.stop();
}
afterAll(async () => {
const stopPromises = Array.from(proxiesToCleanup).map(proxy =>
proxy.stop(),
);
await Promise.allSettled(stopPromises);
proxiesToCleanup.clear();
});
}