Skip to content

Commit

Permalink
Better error message on IPC EPIPE (#1088)
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed May 23, 2024
1 parent e8bab97 commit 97b17e6
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/ipc/send.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
validateIpcMethod,
handleEpipeError,
handleSerializationError,
disconnect,
} from './validation.js';
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions lib/ipc/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
26 changes: 26 additions & 0 deletions test/ipc/send.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
});

0 comments on commit 97b17e6

Please sign in to comment.