graceful.js 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import {scheduler} from 'node:timers/promises';
  2. import {sendOneMessage} from './send.js';
  3. import {getIpcEmitter} from './forward.js';
  4. import {validateConnection, getAbortDisconnectError, throwOnMissingParent} from './validation.js';
  5. // Send an IPC message so the subprocess performs a graceful termination
  6. export const sendAbort = (subprocess, message) => {
  7. const methodName = 'cancelSignal';
  8. validateConnection(methodName, false, subprocess.connected);
  9. return sendOneMessage({
  10. anyProcess: subprocess,
  11. methodName,
  12. isSubprocess: false,
  13. wrappedMessage: {type: GRACEFUL_CANCEL_TYPE, message},
  14. message,
  15. });
  16. };
  17. // When the signal is being used, start listening for incoming messages.
  18. // Unbuffering messages takes one microtask to complete, so this must be async.
  19. export const getCancelSignal = async ({anyProcess, channel, isSubprocess, ipc}) => {
  20. await startIpc({
  21. anyProcess,
  22. channel,
  23. isSubprocess,
  24. ipc,
  25. });
  26. return cancelController.signal;
  27. };
  28. const startIpc = async ({anyProcess, channel, isSubprocess, ipc}) => {
  29. if (cancelListening) {
  30. return;
  31. }
  32. cancelListening = true;
  33. if (!ipc) {
  34. throwOnMissingParent();
  35. return;
  36. }
  37. if (channel === null) {
  38. abortOnDisconnect();
  39. return;
  40. }
  41. getIpcEmitter(anyProcess, channel, isSubprocess);
  42. await scheduler.yield();
  43. };
  44. let cancelListening = false;
  45. // Reception of IPC message to perform a graceful termination
  46. export const handleAbort = wrappedMessage => {
  47. if (wrappedMessage?.type !== GRACEFUL_CANCEL_TYPE) {
  48. return false;
  49. }
  50. cancelController.abort(wrappedMessage.message);
  51. return true;
  52. };
  53. const GRACEFUL_CANCEL_TYPE = 'execa:ipc:cancel';
  54. // When the current process disconnects early, the subprocess `cancelSignal` is aborted.
  55. // Otherwise, the signal would never be able to be aborted later on.
  56. export const abortOnDisconnect = () => {
  57. cancelController.abort(getAbortDisconnectError());
  58. };
  59. const cancelController = new AbortController();