UNPKG

execa

Version:
120 lines (99 loc) • 3.75 kB
import {once} from 'node:events'; import {createDeferred} from '../utils/deferred.js'; import {incrementMaxListeners} from '../utils/max-listeners.js'; import {sendMessage} from './send.js'; import {throwOnMissingStrict, throwOnStrictDisconnect, throwOnStrictDeadlockError} from './validation.js'; import {getIpcEmitter} from './forward.js'; import {hasMessageListeners} from './outgoing.js'; // When using the `strict` option, wrap the message with metadata during `sendMessage()` export const handleSendStrict = ({anyProcess, channel, isSubprocess, message, strict}) => { if (!strict) { return message; } const ipcEmitter = getIpcEmitter(anyProcess, channel, isSubprocess); const hasListeners = hasMessageListeners(anyProcess, ipcEmitter); return { id: count++, type: REQUEST_TYPE, message, hasListeners, }; }; // This is a plain number, not a `BigInt`, since it must be serializable with `serialization: 'json'` let count = 0; // Handles when both processes are calling `sendMessage()` with `strict` at the same time. // If neither process is listening, this would create a deadlock. We detect it and throw. export const validateStrictDeadlock = (outgoingMessages, wrappedMessage) => { if (wrappedMessage?.type !== REQUEST_TYPE || wrappedMessage.hasListeners) { return; } for (const {id} of outgoingMessages) { if (id !== undefined) { STRICT_RESPONSES[id].deferred.resolve({isDeadlock: true, hasListeners: false}); } } }; // The other process then sends the acknowledgment back as a response export const handleStrictRequest = async ({wrappedMessage, anyProcess, channel, isSubprocess, ipcEmitter}) => { if (wrappedMessage?.type !== REQUEST_TYPE || !anyProcess.connected) { return wrappedMessage; } const {id, message} = wrappedMessage; const response = {id, type: RESPONSE_TYPE, message: hasMessageListeners(anyProcess, ipcEmitter)}; try { await sendMessage({ anyProcess, channel, isSubprocess, ipc: true, }, response); } catch (error) { ipcEmitter.emit('strict:error', error); } return message; }; // Reception of the acknowledgment response. // Ids are shared by all processes, so the response must come from the process the message was sent to. Otherwise, a subprocess could acknowledge a message sent to another one. export const handleStrictResponse = (wrappedMessage, anyProcess) => { if (wrappedMessage?.type !== RESPONSE_TYPE) { return false; } const {id, message: hasListeners} = wrappedMessage; const strictResponse = STRICT_RESPONSES[id]; if (strictResponse?.anyProcess === anyProcess) { strictResponse.deferred.resolve({isDeadlock: false, hasListeners}); } return true; }; // Wait for the other process to receive the message from `sendMessage()` export const waitForStrictResponse = async (wrappedMessage, anyProcess, isSubprocess) => { if (wrappedMessage?.type !== REQUEST_TYPE) { return; } const deferred = createDeferred(); STRICT_RESPONSES[wrappedMessage.id] = {deferred, anyProcess}; const controller = new AbortController(); try { const {isDeadlock, hasListeners} = await Promise.race([ deferred, throwOnDisconnect(anyProcess, isSubprocess, controller), ]); if (isDeadlock) { throwOnStrictDeadlockError(isSubprocess); } if (!hasListeners) { throwOnMissingStrict(isSubprocess); } } finally { controller.abort(); delete STRICT_RESPONSES[wrappedMessage.id]; } }; const STRICT_RESPONSES = {__proto__: null}; const throwOnDisconnect = async (anyProcess, isSubprocess, {signal}) => { incrementMaxListeners(anyProcess, 1, signal); await once(anyProcess, 'disconnect', {signal}); throwOnStrictDisconnect(isSubprocess); }; const REQUEST_TYPE = 'execa:ipc:request'; const RESPONSE_TYPE = 'execa:ipc:response';