Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPC helper #900

Open
ehmicky opened this issue Mar 10, 2024 · 1 comment
Open

IPC helper #900

ehmicky opened this issue Mar 10, 2024 · 1 comment

Comments

@ehmicky
Copy link
Collaborator

ehmicky commented Mar 10, 2024

Proposal

// main.js
const subprocess = execa('./subprocess.js', {ipc: true});
for await (const message of subprocess.ipc) {
  ... // Can use subprocess.send()
}
// subprocess.js
import {ipc} from 'execa';

for await (const message of ipc) {
  ... // Can use process.send()
}

Returning (with return or break) from either the current process or the subprocess' loop automatically returns from the other loop, disconnecting the IPC.

Throwing from either loop throws the same error in the other loop, also disconnecting the IPC. The error is serialized/parsed, so only its message and stack are kept.

The parent loop's waits for the subprocess to resolve. Also, it throws if the subprocess promise is rejected.

Rationale

The above provides with a simpler syntax for IPC, using async iterables instead of events.

It also enforces best practices:

Disconnection

Ensures .disconnect() is called to prevent processes hanging, and allows graceful exits.

Otherwise, by default, the subprocess hangs until calling .disconnect(), which users might forget. Many might call process.exit() in the subprocess, but that's a forceful exit which might interrupt ongoing logic.

Error handling

Proper error handling. Errors during either .send() or message handlers are correctly handled and propagated.

Otherwise, by default, an exception in either case in the subprocess would result in an uncaught exception. The current process would need to check stderr to see the error.

The above proposal propagates the error programatically instead, allowing the current process to catch it and ensuring proper propagation.

Subprocess completion/error

Ensures the subprocess promise is awaited and its errors are propagated.

Usually, if IPC is used, it tends to be during the whole lifetime of the subprocess. I.e. the ipc and subprocess promises overlap and can be combined. subprocess does not need to be awaited if subprocess.ipc is.


What do you think?


Edit: some implementation notes:

  • The loop should iterate using on(subprocess, 'message') under-the-hood.
  • Listen to process's and subprocess's error events, making the async iterable throw. This means users do not need to use an error callback on process.send() and subprocess.send().
  • Users use process.send() and subprocess.send() to send messages, or process.disconnect()/subprocess.disconnect(). We keep those as is.
  • Call .disconnect() when either loops returns or throws. Only if .disconnected is false. Should wait for the disconnect event.
  • The parent's iterable.return() and iterable.throw() should return a promise that waits for the subprocess to complete.
  • Serialize the error using v8.serialize().
  • Requires ipc: true option.
  • Parent/subprocess initial messages are buffered by Node.js, so we don't need to wait for the subprocess to start.
  • for await processes serially. However, users can achieve parallelism by calling async functions without await, although it would make error handling harder (although not impossible).
@sindresorhus
Copy link
Owner

Sounds useful 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants