Skip to content

Conversation

@marzipankaiser
Copy link
Contributor

@marzipankaiser marzipankaiser commented Oct 2, 2025

Low-level-ish process api. Allows to:

  • spawn a process
  • with stdin potentially provided in a callback that writes to it
  • stdout and stderr potentially redirected to a callback
  • returning the exit code after completion

The API uses a (mutable) options object which allows to set those callbacks.

Fixes #1080.

@marzipankaiser marzipankaiser force-pushed the feature/subprocess branch 2 times, most recently from 76e0e94 to 2987f2a Compare October 2, 2025 15:46
@marzipankaiser

This comment was marked as resolved.

@marzipankaiser

This comment was marked as resolved.

@marzipankaiser

This comment was marked as resolved.

@marzipankaiser marzipankaiser marked this pull request as ready for review November 11, 2025 10:14
@marzipankaiser marzipankaiser added the blocked Blocked on other issues / PRs label Nov 11, 2025
@marzipankaiser

This comment was marked as resolved.

@marzipankaiser marzipankaiser removed the blocked Blocked on other issues / PRs label Nov 11, 2025
Copy link
Contributor

@jiribenes jiribenes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot attest to the LLVM version, but I think I sort-of understand the JS version and it looks alright.
I am fine with this being merged as-is and figuring out a more user-friendly API later (once I actually try to use it, that is). In general, I think it's a good idea to merge stdlib stuff earlier, so that we (and the students) can use it and see what's good and what's not good. :)

Copy link
Contributor

@jiribenes jiribenes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's just my general questions / comments for the JS version

Comment on lines +37 to +38
let old = ${opts};
old.stdio[1] = 'pipe';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks mutable to me.
Couldn't there be an issue when we try to reuse existing SpawnOptions?

Should we instead copy opts here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is mutable. I'm still unsure how an API that is both safe and does not extensively copy things on the C/JS side.
Having SpawnOptions be a extern interface (and then explicitly mutated) would be more ideal but I'm not sure how to pass extern interfaces over the FFI boundary.
I.e. something like spawn(...){ options: {SpawnOptions} => Unit }: ...

old.onSpawn = function(p) {
oldSpawn(p);
p.stdout.on('data', (chunk) => {
$effekt.runToplevel((ks, k) => (${callback})(chunk, ks, k));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this runTopLevel or run? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a callback called from the JS side, so from somewhere in the event loop.
AFAICT, this means runToplevel

Comment on lines +75 to +77
p.on('spawn', () => {
$effekt.runToplevel((ks, k) => (${callback})(p.stdin, ks, k));
});
Copy link
Contributor

@jiribenes jiribenes Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the process fail to spawn at all? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might. This is not currently handeled (but could, and probably should, be)

ret %Pos %opts
"""
extern def write(to: WritableStream, s: String) at io: Unit =
jsNode """${to}.write(${s})"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This likely works now, but NodeJS can (and will) sometimes return false, and then we should be more async and wait for the 'drain' event.
https://nodejs.org/api/stream.html#writablewritechunk-encoding-callback

jsNode """$effekt.capture(k => {
let p = spawn(${cmd}, ${args}, ${options});
${options}.onSpawn(p);
p.on('close', (exitcode, sig) => { k(exitcode) });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ignores situations in which the child process is terminated.

I thought we had a string->number map for this somewhere in io/error, but I couldn't find it...
Anyways, here's something like what could be there (but I don't like the implementation)

  p.on('close', (exitcode, sig) => { 
    if (sig) {
      const sigMap = {
        'SIGHUP':1, 'SIGINT':2, 'SIGQUIT':3, 'SIGILL':4, 'SIGTRAP':5,
        'SIGABRT':6, 'SIGBUS':7, 'SIGFPE':8, 'SIGKILL':9, 'SIGUSR1':10,
        'SIGSEGV':11, 'SIGUSR2':12, 'SIGPIPE':13, 'SIGALRM':14, 'SIGTERM':15
      };
      k(128 + (sigMap[sig] || 15)); // '15' is default, this is obviously weird
    } else {
      k(exitcode || 0); // the `|| 0` is just defensive here
    }
  });

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we can reasonably from FFI, it would also be nice to just do signal('SIGHUP') or sim. instead of encoding it into an int 🤔

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

Successfully merging this pull request may close these issues.

API for processes

3 participants