fix: EventEmitter memory leak in development utilities

Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi>
This commit is contained in:
Heikki Hellgren
2024-08-09 14:18:22 +03:00
parent 963d677945
commit 3a35172d2e
2 changed files with 33 additions and 15 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-dev-utils': patch
---
Fix `EventEmitter` memory leak in the development utilities
+28 -15
View File
@@ -35,6 +35,8 @@ type Response =
error: Error;
};
type ResponseHandler = (response: Response) => void;
const requestType = '@backstage/cli/channel/request';
const responseType = '@backstage/cli/channel/response';
@@ -48,21 +50,40 @@ const IPC_TIMEOUT_MS = 5000;
export class BackstageIpcClient {
#messageId = 0;
#sendMessage: SendMessage;
#handlers: Map<number, ResponseHandler> = new Map();
/**
* Creates a new client if we're in a child process with IPC and BACKSTAGE_CLI_CHANNEL is set.
*/
static create(): BackstageIpcClient | undefined {
const sendMessage = process.send?.bind(process);
return sendMessage && process.env.BACKSTAGE_CLI_CHANNEL
? new BackstageIpcClient(sendMessage)
: undefined;
const client =
sendMessage && process.env.BACKSTAGE_CLI_CHANNEL
? new BackstageIpcClient(sendMessage)
: undefined;
if (client) {
process.on('message', (message, _sendHandle) =>
client.handleMessage(message, _sendHandle),
);
}
return client;
}
constructor(sendMessage: SendMessage) {
this.#sendMessage = sendMessage;
}
private handleMessage(message: unknown, _sendHandle: unknown) {
const isResponse = (msg: unknown): msg is Response =>
(msg as Response)?.type === responseType;
if (isResponse(message)) {
this.#handlers.get(message.id)?.(message);
}
}
/**
* Send a request to the parent process and wait for a response.
*/
@@ -82,17 +103,10 @@ export class BackstageIpcClient {
let timeout: NodeJS.Timeout | undefined = undefined;
const messageHandler = (response: Response) => {
if (response?.type !== responseType) {
return;
}
if (response.id !== id) {
return;
}
const responseHandler: ResponseHandler = (response: Response) => {
clearTimeout(timeout);
timeout = undefined;
process.removeListener('message', messageHandler);
this.#handlers.delete(id);
if ('error' in response) {
const error = new Error(response.error.message);
@@ -107,12 +121,11 @@ export class BackstageIpcClient {
timeout = setTimeout(() => {
reject(new Error(`IPC request '${method}' with ID ${id} timed out`));
process.removeListener('message', messageHandler);
this.#handlers.delete(id);
}, IPC_TIMEOUT_MS);
timeout.unref();
process.addListener('message', messageHandler as () => void);
this.#handlers.set(id, responseHandler);
this.#sendMessage(request, (e: Error) => {
if (e) {
reject(e);