Skip to content

Feat(process): Implement Command::spawn_with #7249

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

Merged
merged 1 commit into from
Apr 16, 2025

Conversation

PaulDance
Copy link
Contributor

@PaulDance PaulDance commented Apr 9, 2025

Motivation

#7238: under Windows, the spawn sometimes has to be configured in a specific way in order to achieve a specific behavior or to stay compatible with some other API. In our case, it is about spawning child processes as PPL when the parent (Rust) process currently runs as PPL.

In the standard library, it is currently supported through the nightly-only windows_process_extensions_raw_attribute feature. It was previously implemented by configuring the Command object using its raw_attribute extension method and then the regular spawn added the attributes on the Windows process when appropriate. This meant that simply creating and configuring a std::process::Command object was sufficient to spawn Tokio processes since there is impl From<std::process:Command> for tokio::process::Command.

However, the unstable API has recently been changed and the attribute setting must now be explicitly performed at spawn time through a separate spawning method: Command::spawn_with_attributes. This means that the previous solution does not work anymore.

Solution

Since nightly APIs cannot currently be consumed from Tokio in any way, including when put behind some sort of cfg gate such as tokio_unstable, this proposes to add a broader API that enables customizing what is used in order to actually perform the synchronous process spawn while letting the rest stay as is.

This makes for a relatively small change and straightforward API addition. In particular, it allows keeping the nightly-only code outside of Tokio and more generally any kind of desired spawn customization, although not strictly necessary for the initial use case.

However, it could be considered too vague for such a niche use case while it could be replaced with a more specific spawn_with_attributes when the upstream feature is stabilized, even though it could take a long time, but this is the best comprise found for now. This is why the linked issue is not marked here as to be closed: it circumvents the problem at hand, but does not more directly solve it.

Testing

No tests covering the new API have been added. Indeed, the standard library's side has landed a bit too recently (rust-lang/rust#123604, 1.85) it seems compared to the nightly distribution seen in CI so the Windows-specific part could be tested, while there does not seem to be nightly-only tests yet either. Also, considering the current spawn API has been internally rewritten in terms similar to spawn_with's, the current tests should already cover this adequately.

@Darksonn Darksonn added A-tokio Area: The main tokio crate M-process Module: tokio/process labels Apr 9, 2025
@Darksonn
Copy link
Contributor

Darksonn commented Apr 9, 2025

cc @ipetkov

@PaulDance PaulDance force-pushed the command-spawn-with branch from 68dc83d to b04752a Compare April 9, 2025 19:58
@PaulDance PaulDance changed the title Feat: Implement Command::spawn_with Feat: Implement Command::spawn_with Apr 9, 2025
@PaulDance PaulDance force-pushed the command-spawn-with branch from b04752a to 4f793fe Compare April 9, 2025 20:17
@Darksonn Darksonn requested a review from ipetkov April 9, 2025 20:18
@PaulDance PaulDance marked this pull request as ready for review April 9, 2025 20:20
@PaulDance PaulDance force-pushed the command-spawn-with branch 3 times, most recently from 29f509c to 1f28c27 Compare April 10, 2025 15:25
Copy link
Member

@ipetkov ipetkov left a comment

Choose a reason for hiding this comment

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

Overall the changes seem sensible! Just have a few minor comments around cutting down unnecessary monomorphization

@PaulDance PaulDance force-pushed the command-spawn-with branch from 1f28c27 to 897ffbf Compare April 11, 2025 18:02
@PaulDance
Copy link
Contributor Author

Something like this?

@PaulDance PaulDance force-pushed the command-spawn-with branch 2 times, most recently from 92f4e92 to c937c35 Compare April 11, 2025 18:15
@PaulDance PaulDance requested review from ipetkov and Darksonn April 11, 2025 18:34
@PaulDance PaulDance force-pushed the command-spawn-with branch from c937c35 to 0801c11 Compare April 11, 2025 20:50
@PaulDance PaulDance requested a review from Darksonn April 11, 2025 20:57
@PaulDance PaulDance force-pushed the command-spawn-with branch 5 times, most recently from f0346e3 to 2701079 Compare April 16, 2025 11:33
@PaulDance PaulDance changed the title Feat: Implement Command::spawn_with Feat(process): Implement Command::spawn_with Apr 16, 2025
@Darksonn
Copy link
Contributor

That's weird. The FreeBSD 64-bit run failed with this:

failures:

---- runtime::tests::queue::stress1 stdout ----

thread 'runtime::tests::queue::stress1' panicked at tokio/src/runtime/tests/queue.rs:214:9:
assertion `left == right` failed
  left: 499997
 right: 500000
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    runtime::tests::queue::stress1

test result: FAILED. 131 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.27s

This is not a known spurious failure. I'm filing a bug ...

@PaulDance
Copy link
Contributor Author

PaulDance commented Apr 16, 2025

Yeah, I had the same reaction 😅

I triggered a new pipeline since I could not find a way to restart the Cirrus job through GH, so we will see.

It ended up succeeding, so it does seem like a spurious failure indeed.

Copy link
Contributor

@Darksonn Darksonn left a comment

Choose a reason for hiding this comment

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

LGTM. Just one nit.

@PaulDance PaulDance force-pushed the command-spawn-with branch from 2701079 to c2b8925 Compare April 16, 2025 11:47
@PaulDance PaulDance requested a review from Darksonn April 16, 2025 11:48
@Darksonn Darksonn enabled auto-merge (squash) April 16, 2025 11:51
@Darksonn Darksonn disabled auto-merge April 16, 2025 11:51
@Darksonn
Copy link
Contributor

I noticed that github will set the resulting commit to be authored by [email protected], which does not match the email in the SoB tag you have included. Is this something you wish to update before I merge the PR?

@PaulDance
Copy link
Contributor Author

Ah yes, I didn't know I had this inconsistency. I now disabled this proxying, so it should be better?

@Darksonn
Copy link
Contributor

Now it wants to use your hotmail rather than the harfanglab.fr address you put in the SoB.

@PaulDance
Copy link
Contributor Author

Eh, whatever, fine by me 🤷

@Darksonn Darksonn merged commit 7a6c424 into tokio-rs:master Apr 16, 2025
82 checks passed
@Darksonn
Copy link
Contributor

Thanks for the PR!

@PaulDance
Copy link
Contributor Author

Many thanks!

@PaulDance PaulDance deleted the command-spawn-with branch April 16, 2025 12:09
kodiakhq bot pushed a commit to pdylanross/fatigue that referenced this pull request May 6, 2025
Bumps tokio from 1.44.2 to 1.45.0.

Release notes
Sourced from tokio's releases.

Tokio v1.45.0
Added

metrics: stabilize worker_total_busy_duration, worker_park_count, and worker_unpark_count (#6899, #7276)
process: add Command::spawn_with (#7249)

Changed

io: do not require Unpin for some trait impls (#7204)
rt: mark runtime::Handle as unwind safe (#7230)
time: revert internal sharding implementation (#7226)

Unstable

rt: remove alt multi-threaded runtime (#7275)

#6899: tokio-rs/tokio#6899
#7276: tokio-rs/tokio#7276
#7249: tokio-rs/tokio#7249
#7204: tokio-rs/tokio#7204
#7230: tokio-rs/tokio#7230
#7226: tokio-rs/tokio#7226
#7275: tokio-rs/tokio#7275



Commits

00754c8 chore: prepare Tokio v1.45.0 (#7308)
1ae9434 time: revert "use sharding for timer implementation" related changes (#7226)
8895bba ci: Test AArch64 Windows (#7288)
48ca254 time: update sleep documentation to reflect maximum allowed duration (#7302)
a0af02a compat: add more documentation to tokio_util::compat (#7279)
0ce3a11 metrics: stabilize worker_park_count and worker_unpark_count (#7276)
1ea9ce1 ci: fix cfg!(miri) declarations in tests (#7286)
4d4d126 chore: prepare tokio-util v0.7.15 (#7283)
5490267 fs: update the mockall dev dependency to 0.13.0 (#7234)
1434b32 examples: improve echo example consistency (#7256)
Additional commits viewable in compare view




Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


Dependabot commands and options

You can trigger Dependabot actions by commenting on this PR:

@dependabot rebase will rebase this PR
@dependabot recreate will recreate this PR, overwriting any edits that have been made to it
@dependabot merge will merge this PR after your CI passes on it
@dependabot squash and merge will squash and merge this PR after your CI passes on it
@dependabot cancel merge will cancel a previously requested merge and block automerging
@dependabot reopen will reopen this PR if it is closed
@dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
@dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
@dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
@dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
@dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
@piscisaureus
Copy link

This is very useful, thank you!
🙏 for this to become stable.

@Darksonn
Copy link
Contributor

This was added as unstable as a temporary measure until stdlib stabilizes spawn_with_attributes. Did you have a different use-case in mind?

@Pistonight
Copy link
Contributor

@Darksonn I had a different use case for this: I wanted to take the stdout/stderr of a child and pipe it to another command like the example here: https://doc.rust-lang.org/std/process/struct.Stdio.html#impl-From%3CChildStdout%3E-for-Stdio

(Maybe there's another way to do it other than my approach below, please let me know)

I could take it from tokio::process::ChildStdout, but that involves converting it from sync to async and back to sync, which seems unnecessary:

#[tokio::main]
async fn main() {
    let hello = Command::new("echo")
        .arg("Hello, world!")
        .stdout(Stdio::piped())
        .spawn() // converted to nonblocking io here
        .expect("failed echo command");
    
    let std_io: Result<Stdio, _> = hello.stdout.unwrap().try_into(); // extra try_into() and unwrap() compared to the std example
    
    let reverse = Command::new("rev")
        .stdin(std_io.unwrap())
        .output().await.unwrap();
    // works, but not ideal
    assert_eq!(reverse.stdout, b"!dlrow ,olleH\n");
}

What I really want to do:

    let mut std_io: Option<Stdio> = None;
    let hello = Command::new("echo")
        .arg("Hello, world!")
        .stdout(Stdio::piped())
        .spawn_with(|cmd| {
            let mut child = cmd.spawn()?;
            std_io = child.stdout.take().map(Stdio::from); // take control of the stdout handle
          // ^ does not compile due to being inside Fn closure
            Ok(child)
        })
        .expect("failed echo command");
    
    let reverse = Command::new("rev")
        .stdin(std_io.take().unwrap()) // put it in directly without converting it to non-blocking
        .output().await.unwrap();
    
    assert_eq!(reverse.stdout, b"!dlrow ,olleH\n");

Note that this doesn't compile and currently has to be worked around with RefCell unless the closure is upgraded to FnMut or FnOnce, but it works once I do that

@Darksonn
Copy link
Contributor

Darksonn commented Aug 3, 2025

If you have change requests, then please file a new issue.

As for upgrading the closure to FnOnce, I would be happy to see a PR for that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-tokio Area: The main tokio crate M-process Module: tokio/process
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants