From 97b17e613ba4a5c7c8ef643d02bb93a98226fa3a Mon Sep 17 00:00:00 2001 From: ehmicky Date: Thu, 23 May 2024 22:46:20 +0100 Subject: [PATCH] Better error message on IPC `EPIPE` (#1088) --- lib/ipc/send.js | 2 ++ lib/ipc/validation.js | 7 +++++++ test/ipc/send.js | 26 ++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/lib/ipc/send.js b/lib/ipc/send.js index 0ac79ea94f..597fa3602b 100644 --- a/lib/ipc/send.js +++ b/lib/ipc/send.js @@ -1,5 +1,6 @@ import { validateIpcMethod, + handleEpipeError, handleSerializationError, disconnect, } from './validation.js'; @@ -33,6 +34,7 @@ const sendMessageAsync = async ({anyProcess, anyProcessSend, isSubprocess, messa await anyProcessSend(message); } catch (error) { disconnect(anyProcess); + handleEpipeError(error, isSubprocess); handleSerializationError(error, isSubprocess, message); throw error; } finally { diff --git a/lib/ipc/validation.js b/lib/ipc/validation.js index f514ec65a6..5d02de6e4c 100644 --- a/lib/ipc/validation.js +++ b/lib/ipc/validation.js @@ -28,6 +28,13 @@ const getNamespaceName = isSubprocess => isSubprocess ? '' : 'subprocess.'; const getOtherProcessName = isSubprocess => isSubprocess ? 'parent process' : 'subprocess'; +// EPIPE can happen when sending a message to a subprocess that is closing but has not disconnected yet +export const handleEpipeError = (error, isSubprocess) => { + if (error.code === 'EPIPE') { + throw new Error(`${getNamespaceName(isSubprocess)}sendMessage() cannot be used: the ${getOtherProcessName(isSubprocess)} is disconnecting.`, {cause: error}); + } +}; + // Better error message when sending messages which cannot be serialized. // Works with both `serialization: 'advanced'` and `serialization: 'json'`. export const handleSerializationError = (error, isSubprocess, message) => { diff --git a/test/ipc/send.js b/test/ipc/send.js index 77f7ca07ba..6c45cf45d8 100644 --- a/test/ipc/send.js +++ b/test/ipc/send.js @@ -81,3 +81,29 @@ test('Disconnects IPC on subprocess.sendMessage() error', async t => { t.false(isTerminated); t.true(stderr.includes('sendMessage()\'s argument type is invalid')); }); + +// EPIPE happens based on timing conditions, so we must repeat it until it happens +const findEpipeError = async t => { + // eslint-disable-next-line no-constant-condition + while (true) { + // eslint-disable-next-line no-await-in-loop + const error = await t.throwsAsync(getEpipeError()); + if (error.cause?.code === 'EPIPE') { + return error; + } + } +}; + +const getEpipeError = async () => { + const subprocess = execa('empty.js', {ipc: true}); + // eslint-disable-next-line no-constant-condition + while (true) { + // eslint-disable-next-line no-await-in-loop + await subprocess.sendMessage('.'); + } +}; + +test.serial('Can send messages while the subprocess is closing', async t => { + const {message} = await findEpipeError(t); + t.is(message, 'subprocess.sendMessage() cannot be used: the subprocess is disconnecting.'); +});