Skip to content

feat: Add execFile Support #949

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

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

nithinkjoy-tech
Copy link

Issue

Closes #818

Description of changes

Added execFile support to the child_process module.

Checklist

  • Created unit tests in tests/unit and/or in Rust for my feature if needed
  • Ran make fix to format JS and apply Clippy auto fixes
  • Made sure my code didn't add any additional warnings: make check
  • Updated documentation if needed (API.md/README.md/Other)

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Copy link
Collaborator

@Sytten Sytten left a comment

Choose a reason for hiding this comment

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

First pass on it

@@ -212,7 +215,7 @@ impl<'js> ChildProcess<'js> {
}

impl<'js> ChildProcess<'js> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This file is getting very large, we should start splitting it up.

@@ -351,6 +344,216 @@ impl<'js> ChildProcess<'js> {
}
Ok(instance)
}

fn exec_file(
Copy link
Collaborator

Choose a reason for hiding this comment

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

There is a lot of duplicate code with the spawn function, I am wondering if there are some missing helpers to add?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agree. execFile can build on exec which can build on spawn. They're just higher level APIs. execFile doesn't spawn a shell

ChildProcess::spawn(ctx.clone(), cmd, command_args, command.spawn())
}

fn exec_file<'js>(
Copy link
Collaborator

Choose a reason for hiding this comment

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

The refactor of spawn to accommodate exec_file is good. Let's split the helpers in another file (I would split functions together, the class on its own and the helpers together).


fn get_callback_fn<'js>(
ctx: &Ctx<'js>,
args: Vec<Option<&Value<'js>>>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should be able to avoid a vec allocation here I am pretty sure &[Option<&Value<'js>>] should work

fn exec_file<'js>(
ctx: Ctx<'js>,
cmd: String,
args_and_opts: Rest<Value<'js>>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't like this style of arguments, this execFile and spawn don't have variadic arguments.
I know this is the style of @richarddavison :D
At minimum you can write:

fn exec_file<'js>(
    ctx: Ctx<'js>,
    cmd: String,
    args_0: Opt<Value<'js>>,
    args_1: Opt<Value<'js>>,
    args_2: Opt<Value<'js>>,
)

Even better would be to start using proper typing directly in the arguments (this is approximate code):

fn exec_file<'js>(
    ctx: Ctx<'js>,
    cmd: String,
    args_0: Opt<Either<Either<Array<'js>, Object<'js>, Function<'js>>>>, // I wouldnt againt be an Either3 implementation
    args_1: Opt<Either<Object<'js>, Function<'js>>,
    args_2: Opt<Function<'js>>,
)

Opt is different from Option since it doesnt require undefined to be passed to the function

Copy link
Collaborator

Choose a reason for hiding this comment

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

Agree. We can do Either to clean it up. Similar approach can be added in spawn

Comment on lines 299 to 309
if let Some(listener) = listener {
if listener == "data" {
Self::emit_str(
This(this2.clone()),
&ctx3,
"data",
vec![Buffer(buffer.clone()).into_js(&ctx3)?],
false
)?;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a change in behaviour for which I not qualified to comment, @richarddavison

Comment on lines 236 to 237
combined_std_buffer: Option<Arc<Mutex<Vec<u8>>>>,
cb: Option<Function<'js>>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a case where we would want to use a combined_std_buffer independently from cb? All usages in this PR assume that cb and combined_std_buffer are either some or none.
I am not really a want of adding it to the stream, I wonder if we could instead either pipe the stdout/err through an accumulation middleware before forwarding it to the DefaultReadableStream.

let stdout_arc = Arc::new(Mutex::new(stdout_new));
let stderr_arc = Arc::new(Mutex::new(stderr_new));
let combined_stdout_buffer = Some(Arc::clone(&stdout_arc));
let combined_stderr_buffer = Some(Arc::clone(&stderr_arc));
Copy link
Collaborator

Choose a reason for hiding this comment

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

As far as I can see, we don't use None on this value anywhere.

Also depending on the usecase, it is usually fine to use a RefCell since the JS engine is single-threaded. As long as you don't hold the borrow across an await point or a callback to JS (anything that yields control back to the JS engine really).

@Sytten Sytten requested a review from richarddavison April 22, 2025 17:41
@Sytten
Copy link
Collaborator

Sytten commented Apr 22, 2025

For reference, the node implementation uses spawn under the cover: https://github.com/nodejs/node/blob/1720b18260d7f4bbd9fe0d2cbbbb05cc33e7f945/lib/child_process.js#L323 and they do in fact set a listener on 'data' to accumulate their callback value. We should go that route too if possible.

Copy link
Collaborator

@richarddavison richarddavison left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! @Sytten covered most points. Mainly we should build exec and execFile upon spawn

@nithinkjoy-tech
Copy link
Author

Thank you @Sytten and @richarddavison for the review. I will refactor it to build exec and execFile upon spawn and will try to set a listener on 'data' to accumulate their callback value.

@nithinkjoy-tech nithinkjoy-tech marked this pull request as draft April 26, 2025 08:00
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.

child_process: support execFile
3 participants