运行 命令时如何避免僵尸进程?
How to avoid zombie processes when running a Command?
一个小型 Iron 项目在某些路由中调用 Command
,return 调用 Response
。下面是路由处理函数的相关代码:
fn convert(req: &mut Request) -> IronResult<Response> {
// ...
// init some bindings like destination_html and destination_pdf
// ...
convert_to_pdf(destination_html, destination_pdf);
Ok( Response::with((status::Ok, "Done")) )
}
以及被调用函数的代码:
fn convert_to_pdf(destination_html: &str, destination_pdf: &str) {
Command::new("xvfb-run")
.arg("-a")
.arg("wkhtmltopdf")
.arg(destination_html)
.arg(destination_pdf)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("failed to execute process");
}
该过程有效(文件从 HTML 转换为 PDF)并且响应被 return 发送到浏览器。一切都很好,但僵尸进程仍然存在,作为我的应用程序的 child:
我不知道为什么,也不知道如何避免。 我能做什么?
wkhtmltopdf
命令是一个漫长的过程,我不想同步调用它并等待它的return。而且我不想每天两次重启我的 Rust 程序(僵尸 parent child)来杀死僵尸。
您的问题是您没有等待进程终止,因此操作系统没有释放任何资源(参见 man pages for proper explanation)。你的僵尸正在占用内存,这将导致资源耗尽。杀死父进程不会做任何事情,你需要手动杀死每个僵尸(如果你在一个线程中 运行 wkhtmltopdf,它会起作用)。
除此之外...
您正在尝试生成命令并回答您的客户...甚至没有检查 wkhtmltopdf 的状态代码。此外,你是 运行 作为 root,这是一个错误的做法(无论你是否以 root 身份进行开发)。并且您的应用程序容易受到 DDoS 攻击(如果您有很多生成 PDF 的客户端,您的服务器将面临资源耗尽)。
(恕我直言)你应该把你的项目分成两部分:
- 没有渲染进程的服务器
- PDF 渲染引擎
第一个会向第二个发送消息"please generate a PDF with the following parameters(..)"。第二个将查看消息队列,获取第一个,生成 PDF 并等待 completion/errors。您甚至可以在消息中添加一个唯一的#ID,并在渲染引擎上创建一个端点来实际查询作业#ID 的状态。
你想做的是像Celery这样的作业队列,但它是用Python编写的,并且使用的是第三方软件(Redis)。
它很丑,我讨厌它,它有效!
use std::{thread, time};
let _ = thread::spawn(|| {
// Cull zombies every minute in the background
loop {
let minute = time::Duration::from_secs(60);
thread::sleep(minute);
println!("Culling Zombies");
// 99999 FreeBSD
// cat /proc/sys/kernel/pid_max Linux
for pid in 1..99999 {
let _ = nix::sys::wait::waitpid(nix::unistd::Pid::from_raw(pid as i32), Some(nix::sys::wait::WaitPidFlag::WNOHANG));
}
}
});
而不是使用 std::process::Command
,您可以使用带有选项 kill_on_drop(true)
的 tokio::process::Command
。
Controls whether a kill operation should be invoked on a spawned child process when its corresponding Child handle is dropped.
By default, this value is assumed to be false, meaning the next spawned process will not be killed on drop, similar to the behavior of the standard library.
Caveats
On Unix platforms processes must be “reaped” by their parent process after they have exited in order to release all OS resources. A child process which has exited, but has not yet been reaped by its parent is considered a “zombie” process. Such processes continue to count against limits imposed by the system, and having too many zombie processes present can prevent additional processes from being spawned.
东京文档:https://docs.rs/tokio/latest/tokio/process/struct.Command.html#method.kill_on_drop
一个小型 Iron 项目在某些路由中调用 Command
,return 调用 Response
。下面是路由处理函数的相关代码:
fn convert(req: &mut Request) -> IronResult<Response> {
// ...
// init some bindings like destination_html and destination_pdf
// ...
convert_to_pdf(destination_html, destination_pdf);
Ok( Response::with((status::Ok, "Done")) )
}
以及被调用函数的代码:
fn convert_to_pdf(destination_html: &str, destination_pdf: &str) {
Command::new("xvfb-run")
.arg("-a")
.arg("wkhtmltopdf")
.arg(destination_html)
.arg(destination_pdf)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("failed to execute process");
}
该过程有效(文件从 HTML 转换为 PDF)并且响应被 return 发送到浏览器。一切都很好,但僵尸进程仍然存在,作为我的应用程序的 child:
我不知道为什么,也不知道如何避免。 我能做什么?
wkhtmltopdf
命令是一个漫长的过程,我不想同步调用它并等待它的return。而且我不想每天两次重启我的 Rust 程序(僵尸 parent child)来杀死僵尸。
您的问题是您没有等待进程终止,因此操作系统没有释放任何资源(参见 man pages for proper explanation)。你的僵尸正在占用内存,这将导致资源耗尽。杀死父进程不会做任何事情,你需要手动杀死每个僵尸(如果你在一个线程中 运行 wkhtmltopdf,它会起作用)。
除此之外...
您正在尝试生成命令并回答您的客户...甚至没有检查 wkhtmltopdf 的状态代码。此外,你是 运行 作为 root,这是一个错误的做法(无论你是否以 root 身份进行开发)。并且您的应用程序容易受到 DDoS 攻击(如果您有很多生成 PDF 的客户端,您的服务器将面临资源耗尽)。
(恕我直言)你应该把你的项目分成两部分:
- 没有渲染进程的服务器
- PDF 渲染引擎
第一个会向第二个发送消息"please generate a PDF with the following parameters(..)"。第二个将查看消息队列,获取第一个,生成 PDF 并等待 completion/errors。您甚至可以在消息中添加一个唯一的#ID,并在渲染引擎上创建一个端点来实际查询作业#ID 的状态。
你想做的是像Celery这样的作业队列,但它是用Python编写的,并且使用的是第三方软件(Redis)。
它很丑,我讨厌它,它有效!
use std::{thread, time};
let _ = thread::spawn(|| {
// Cull zombies every minute in the background
loop {
let minute = time::Duration::from_secs(60);
thread::sleep(minute);
println!("Culling Zombies");
// 99999 FreeBSD
// cat /proc/sys/kernel/pid_max Linux
for pid in 1..99999 {
let _ = nix::sys::wait::waitpid(nix::unistd::Pid::from_raw(pid as i32), Some(nix::sys::wait::WaitPidFlag::WNOHANG));
}
}
});
而不是使用 std::process::Command
,您可以使用带有选项 kill_on_drop(true)
的 tokio::process::Command
。
Controls whether a kill operation should be invoked on a spawned child process when its corresponding Child handle is dropped.
By default, this value is assumed to be false, meaning the next spawned process will not be killed on drop, similar to the behavior of the standard library.
Caveats
On Unix platforms processes must be “reaped” by their parent process after they have exited in order to release all OS resources. A child process which has exited, but has not yet been reaped by its parent is considered a “zombie” process. Such processes continue to count against limits imposed by the system, and having too many zombie processes present can prevent additional processes from being spawned.
东京文档:https://docs.rs/tokio/latest/tokio/process/struct.Command.html#method.kill_on_drop