-
Notifications
You must be signed in to change notification settings - Fork 400
Cleanup zombie processes for child process client #156
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
Cleanup zombie processes for child process client #156
Conversation
9262657 to
004acbe
Compare
Using process wrap library, Process Group for Unix and Job Object for Windows
362c45b to
abccf2e
Compare
|
Thank you for PR, I will have a look on this! |
|
Looks good! Is there a way to provide a chained call api to create a And it will be great if you can update the document like |
…ommand Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership of the command instead of mutable reference
…command Updated more examples, comments and readme to take command instead of mutable ref. Also small fix in cargo toml of example to use local path.
abccf2e to
9da3f84
Compare
|
Thanks for the review, I updated I couldn't find a way to provide an inline chained way, due to the fact that most std/tokio::process::Command methods like args(), envs() take and return &mut Command instead of Command, so if you chain it in one line, there's no way to get the Command again. I checked what process-wrap does for this, but they just use command in non-inline way, or create their own structs directly. TokioChildProcess::new({ will workfor inline way, but this is just the same as doing it before, I coulnd't find a clean way to chain it inline |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR updates the TokioChildProcess API to consume a full tokio::process::Command (instead of a mutable reference) and integrates the process_wrap crate to ensure that child processes cleanup their subprocesses (using a JobObject on Windows and a Process Group on Unix). The changes span multiple examples, tests, and the core transport implementation.
- Updated TokioChildProcess::new to take ownership of the command and chain additional configurations.
- Adjusted various examples and tests to use the new command initialization pattern.
- Introduced a ChildWithCleanup wrapper that utilizes process_wrap for proper child process termination.
Reviewed Changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| examples/simple-chat-client/src/config.rs | Updated command creation to comply with the new API |
| examples/simple-chat-client/Cargo.toml | Changed rmcp dependency from git to local path |
| examples/rig-integration/src/config/mcp.rs | Updated command initialization per new API |
| examples/clients/src/std_io.rs | Refactored command creation and updated commented examples |
| examples/clients/src/everything_stdio.rs | Modified command creation for consistency |
| examples/clients/src/collection.rs | Updated command creation; note change in argument value |
| docs/readme/README.zh-cn.md | Refactored example to show new command initialization |
| crates/rmcp/tests/test_with_python.rs | Updated test command creation |
| crates/rmcp/tests/test_with_js.rs | Updated test command creation |
| crates/rmcp/src/transport/child_process.rs | Refactored transport module to wrap child process with cleanup |
| crates/rmcp/src/lib.rs | Updated documentation to reflect the new API |
| crates/rmcp/Cargo.toml | Added process-wrap dependency and updated transport features |
| README.md | Updated quick-start example to use new command pattern |
Comments suppressed due to low confidence (1)
examples/clients/src/collection.rs:22
- [nitpick] Verify that changing the argument from 'mcp-server-git' to 'mcp-client-git' is intentional and consistent with the intended behavior of the example.
cmd.arg("mcp-client-git");
|
|
||
| impl Drop for ChildWithCleanup { | ||
| fn drop(&mut self) { | ||
| let _ = self.inner.start_kill(); |
Copilot
AI
May 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Consider logging or handling errors from 'start_kill()' in the Drop implementation to aid in debugging potential cleanup issues.
|
How about create a command wrapper in rmcp? It could be something like this. #[derive(Debug)]
pub struct Command {
tokio: tokio::process::Command,
}
impl From<tokio::process::Command> for Command {
fn from(tokio: tokio::process::Command) -> Self {
Self { tokio }
}
}
impl Command {
pub fn new<S: AsRef<OsStr>>(program: S) -> Command {
Self::from(tokio::process::Command::new(program))
}
pub fn arg<S: AsRef<OsStr>>(mut self, arg: S) -> Command {
self.tokio.arg(arg);
self
}
pub fn args<I, S>(mut self, args: I) -> Command
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
self.tokio.args(args);
self
}
pub fn env<K, V>(mut self, key: K, val: V) -> Command
where
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.tokio.env(key, val);
self
}
pub fn envs<I, K, V>(mut self, vars: I) -> Command
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
self.tokio.envs(vars);
self
}
pub fn into_child_process(self) -> std::io::Result<TokioChildProcess> {
TokioChildProcess::new(self.tokio)
}
pub fn into_tokio(self) -> tokio::process::Command {
self.tokio
}
pub fn as_tokio_mut(&mut self) -> &mut tokio::process::Command {
&mut self.tokio
}
pub fn as_tokio(&self) -> &tokio::process::Command {
&self.tokio
}
}And we can use it like this let transport = Command::new("node")
.arg("tests/test_with_js/server.js")
.into_child_process()?;
let client = ().serve(transport).await?;Will it be confusing for there are too many |
|
How about add a event listener , ant use wait_pid to recycle the children process ?I think we only need to focus on the possibility of zombie like child processes during the runtime of the main process. |
|
One option for inline could be to modify examples to do like: TokioChildProcess::new(tokio::process::Command::new("node").map(
|cmd| { cmd.arg("tests/test_with_js/client.js"); cmd }
))Another could be for people who use tap crate, using tap_mut, but it's more or less the same, we could provide that trait to command, but it would need to be imported and it's not much less verbose use rmcp::transport::child_process::CommandTapMut;
TokioChildProcess::new(tokio::process::Command::new("node").tap_mut(
|cmd| { cmd.arg("tests/test_with_js/client.js"); }
))Or we could do a wrapper to command like proposed before, but there will be several Command structs then, not sure which is the best approach. |
|
@humbertoyusta The first approach looks great. I am okay with it. |
Added configure command ext to tokio process command so that you can use .configure() to use inline commands for mcp stdio client. Added warning if start kill process fails
|
Actually I still think it's better than a full Command wrapper, but if you think otherwise let me know. |
|
@humbertoyusta I am ok with the ext trait. Looks there's confict and ci didn't pass. So could you merge it with the main branch, for I just made a lot of changes, and fix the formatting and test ci? Thanks for your patience! |
ff874f0 to
d92b46a
Compare
|
It's okey with the ci message, I can rewrite it when merge. |
|
And thanks for your work! |
Ensure child processes kill all subprocesses on drop (for transport-child-process)
Motivation and Context
Starting a tokio child processes, in several mcp servers, like using
uv some-mcp-serverin MacOS, will create subprocesses that will not be cleaned when the first process is killed with the.kill_on_drop(true)Using a JobObject in Windows and a Process Group in Unix, killing the child process will also clean all subprocesses, avoiding resource leaking
How Has This Been Tested?
We (refact.ai) use this library for integrating mcp servers with our client, we recently switched from another library, so it's not in production yet.
Scenarios about starting servers like with
uvwere tested to avoid resource leaking, also fakeish scenarios likesh -c "npx some-mcp-server"were tested, causing subprocess leaking before, not nowBreaking Changes
TokioChildProcess::new(command) takes now tokio::process::Command entirely, instead of &mut tokio::process::Command
Types of changes
Checklist