Rust:如何生成在 parent 接收到 SIGINT/SIGTERM 后继续存在的 child 进程
Rust: How to spawn child process that continues to live after parent receives SIGINT/SIGTERM
我目前正在编写一个同时启动其他应用程序的应用程序(例如 firefox
)。我希望这些 child 应用程序的寿命比 parent 长(例如,当 parent 退出时,它们应该继续 运行)。只要 parent 退出(main 结束,process:exit()
),这就可以工作(见下面我的代码),但是如果 parent 接收到 SIGINT
(ctrl + c) , SIGTERM
所有 child 进程也立即死亡。我怎样才能避免这种情况? 注意:我的主要进程是long-lived所以下面所有在产生child后立即退出的例子都不适合我的情况,我只是列出它们的完整性以显示我尝试过的内容等。
现在我只关心 Linux 支持,如果没有干净的 cross-plattform 解决方案。
到目前为止,我已经尝试了以下方法,none 的方法令我满意:
use std::{
process::{self, Child, Command, Stdio},
thread,
};
const EXECUTABLE: &str = "/usr/bin/firefox";
fn main() {
// The child continues to live after our process has finished
spawn_and_exit();
// The child continues to live after our process has cleanly finished
//spawn_and_continue()
// The child gets killed as well if our process gets killed
//spawn_and_force_shutdown()
// Child continues to live (if our process shuts down cleanly)
//threaded_clean_spawn()
// Child gets killed as well
//threaded_and_force_shutdown()
// child gets killed as well
//double_threaded_and_force_shutdown()
}
fn wait() {
std::thread::sleep(std::time::Duration::from_millis(250));
}
fn hang() {
println!("You can now kill the process (e.g. Ctrl+C)");
loop { wait(); }
}
/// The child continues to live after our process has finished
fn spawn_and_exit() {
println!("Spawn and exit");
let _child = Command::new(EXECUTABLE)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
// give the process some time to actually start
wait();
wait();
process::exit(0);
}
/// The child continues to live after our process has finished
fn spawn_and_continue() {
println!("Spawn and clean shutdown");
let _child = Command::new(EXECUTABLE)
//.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
// give the process some time to actually start
wait();
}
/// The child gets killed as well if our process gets killed
fn spawn_and_force_shutdown() {
println!("Spawn and force shutdown");
let _child = Command::new(EXECUTABLE)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
wait();
hang();
}
/// Child continues to live (if our process shuts down cleanly)
fn threaded_clean_spawn() {
println!("threaded_clean_spawn");
let _joinhandle = thread::Builder::new().spawn(|| {
spawn_and_continue();
});
wait();
}
/// Child gets killed as well
fn threaded_and_force_shutdown() {
println!("threaded_and_force_shutdown");
let _joinhandle = thread::Builder::new().spawn(|| {
spawn_and_continue();
});
hang();
}
/// child gets killed as well
fn double_threaded_and_force_shutdown() {
println!("double_threaded_and_force_shutdown");
let _joinhandle = thread::Builder::new().spawn(|| {
let joinhandle = thread::Builder::new().spawn(move || {
spawn_and_continue();
}).unwrap();
let _ = joinhandle.join();
println!("inner thing returned");
});
hang();
}
Side-note:最初,我预计 thread::Builder::new().spawn()
会解决我的问题,因为文档 (https://doc.rust-lang.org/std/thread/struct.Builder.html#method.spawn) 指出:
The spawned thread may outlive the caller (unless the caller thread is the main thread; the whole process is terminated when the main thread finishes).
由于括号中的添加,我也尝试了double_threaded_and_force_shutdown
的方法,没有成功。
这基本上是与 How to Spawn Child Processes that Don't Die with Parent? 相同的问题,但针对的是 Rust 而不是 c++。
如果您想“守护进程”,fork crate 可能会有用,这里有一个小例子:
use fork::{daemon, Fork};
use std::process::Command;
fn main() {
if let Ok(Fork::Child) = daemon(false, false) {
Command::new("/usr/bin/firefox")
.output()
.expect("failed to execute process");
}
}
Cargo.toml
的内容:
[dependencies]
fork = "0.1"
这是调用daemon
时的流程:
- parent 分叉 child
- parent 退出
- child 调用
setsid()
开始一个没有控制终端的新会话
- child分叉盛大child
- child 退出
- grandchild 现在是守护进程
您可以查看 lib.rs 代码以获得更好的想法,例如关于如何调用 setsid
:
pub fn setsid() -> Result<libc::pid_t, i32> {
let res = unsafe { libc::setsid() }; // check https://docs.rs/libc
match res {
-1 => Err(-1),
res => Ok(res),
}
}
为了防止父进程被kill时子进程也被终止,你需要double-fork。这是 linux 特有的,与生锈无关。
我正在使用 nix crate 调用 linux API(省略了正确的错误处理):
use std::{
process::{exit, Command},
thread::sleep,
time::Duration,
};
use nix::{
sys::wait::waitpid,
unistd::{fork, ForkResult},
};
fn main() {
match fork().expect("Failed to fork process") {
ForkResult::Parent { child } => {
println!("Try to kill me to check if the target process will be killed");
// Do not forget to wait for the fork in order to prevent it from becoming a zombie!!!
waitpid(Some(child), None).unwrap();
// You have 120 seconds to kill the process :)
sleep(Duration::from_secs(120));
}
ForkResult::Child => {
// replace with your executable
Command::new("/usr/bin/file-roller")
.spawn()
.expect("failed to spawn the target process");
exit(0);
}
}
}
你一定不要忘记在第一个fork上调用waitpid
,当你有它的PID时,否则它会成为一个僵尸进程。摆脱僵尸的唯一等待是调用 waitpid
以便 OS 释放任何相关资源或杀死它们的父级 - 即您的应用程序,因此只需调用 waitpid
和省去麻烦。
我目前正在编写一个同时启动其他应用程序的应用程序(例如 firefox
)。我希望这些 child 应用程序的寿命比 parent 长(例如,当 parent 退出时,它们应该继续 运行)。只要 parent 退出(main 结束,process:exit()
),这就可以工作(见下面我的代码),但是如果 parent 接收到 SIGINT
(ctrl + c) , SIGTERM
所有 child 进程也立即死亡。我怎样才能避免这种情况? 注意:我的主要进程是long-lived所以下面所有在产生child后立即退出的例子都不适合我的情况,我只是列出它们的完整性以显示我尝试过的内容等。
现在我只关心 Linux 支持,如果没有干净的 cross-plattform 解决方案。
到目前为止,我已经尝试了以下方法,none 的方法令我满意:
use std::{
process::{self, Child, Command, Stdio},
thread,
};
const EXECUTABLE: &str = "/usr/bin/firefox";
fn main() {
// The child continues to live after our process has finished
spawn_and_exit();
// The child continues to live after our process has cleanly finished
//spawn_and_continue()
// The child gets killed as well if our process gets killed
//spawn_and_force_shutdown()
// Child continues to live (if our process shuts down cleanly)
//threaded_clean_spawn()
// Child gets killed as well
//threaded_and_force_shutdown()
// child gets killed as well
//double_threaded_and_force_shutdown()
}
fn wait() {
std::thread::sleep(std::time::Duration::from_millis(250));
}
fn hang() {
println!("You can now kill the process (e.g. Ctrl+C)");
loop { wait(); }
}
/// The child continues to live after our process has finished
fn spawn_and_exit() {
println!("Spawn and exit");
let _child = Command::new(EXECUTABLE)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
// give the process some time to actually start
wait();
wait();
process::exit(0);
}
/// The child continues to live after our process has finished
fn spawn_and_continue() {
println!("Spawn and clean shutdown");
let _child = Command::new(EXECUTABLE)
//.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
// give the process some time to actually start
wait();
}
/// The child gets killed as well if our process gets killed
fn spawn_and_force_shutdown() {
println!("Spawn and force shutdown");
let _child = Command::new(EXECUTABLE)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
wait();
hang();
}
/// Child continues to live (if our process shuts down cleanly)
fn threaded_clean_spawn() {
println!("threaded_clean_spawn");
let _joinhandle = thread::Builder::new().spawn(|| {
spawn_and_continue();
});
wait();
}
/// Child gets killed as well
fn threaded_and_force_shutdown() {
println!("threaded_and_force_shutdown");
let _joinhandle = thread::Builder::new().spawn(|| {
spawn_and_continue();
});
hang();
}
/// child gets killed as well
fn double_threaded_and_force_shutdown() {
println!("double_threaded_and_force_shutdown");
let _joinhandle = thread::Builder::new().spawn(|| {
let joinhandle = thread::Builder::new().spawn(move || {
spawn_and_continue();
}).unwrap();
let _ = joinhandle.join();
println!("inner thing returned");
});
hang();
}
Side-note:最初,我预计 thread::Builder::new().spawn()
会解决我的问题,因为文档 (https://doc.rust-lang.org/std/thread/struct.Builder.html#method.spawn) 指出:
The spawned thread may outlive the caller (unless the caller thread is the main thread; the whole process is terminated when the main thread finishes).
由于括号中的添加,我也尝试了double_threaded_and_force_shutdown
的方法,没有成功。
这基本上是与 How to Spawn Child Processes that Don't Die with Parent? 相同的问题,但针对的是 Rust 而不是 c++。
如果您想“守护进程”,fork crate 可能会有用,这里有一个小例子:
use fork::{daemon, Fork};
use std::process::Command;
fn main() {
if let Ok(Fork::Child) = daemon(false, false) {
Command::new("/usr/bin/firefox")
.output()
.expect("failed to execute process");
}
}
Cargo.toml
的内容:
[dependencies]
fork = "0.1"
这是调用daemon
时的流程:
- parent 分叉 child
- parent 退出
- child 调用
setsid()
开始一个没有控制终端的新会话 - child分叉盛大child
- child 退出
- grandchild 现在是守护进程
您可以查看 lib.rs 代码以获得更好的想法,例如关于如何调用 setsid
:
pub fn setsid() -> Result<libc::pid_t, i32> {
let res = unsafe { libc::setsid() }; // check https://docs.rs/libc
match res {
-1 => Err(-1),
res => Ok(res),
}
}
为了防止父进程被kill时子进程也被终止,你需要double-fork。这是 linux 特有的,与生锈无关。
我正在使用 nix crate 调用 linux API(省略了正确的错误处理):
use std::{
process::{exit, Command},
thread::sleep,
time::Duration,
};
use nix::{
sys::wait::waitpid,
unistd::{fork, ForkResult},
};
fn main() {
match fork().expect("Failed to fork process") {
ForkResult::Parent { child } => {
println!("Try to kill me to check if the target process will be killed");
// Do not forget to wait for the fork in order to prevent it from becoming a zombie!!!
waitpid(Some(child), None).unwrap();
// You have 120 seconds to kill the process :)
sleep(Duration::from_secs(120));
}
ForkResult::Child => {
// replace with your executable
Command::new("/usr/bin/file-roller")
.spawn()
.expect("failed to spawn the target process");
exit(0);
}
}
}
你一定不要忘记在第一个fork上调用waitpid
,当你有它的PID时,否则它会成为一个僵尸进程。摆脱僵尸的唯一等待是调用 waitpid
以便 OS 释放任何相关资源或杀死它们的父级 - 即您的应用程序,因此只需调用 waitpid
和省去麻烦。