10个版本
0.2.4 | 2024年2月12日 |
---|---|
0.2.3 | 2024年2月12日 |
0.1.5 | 2024年2月7日 |
#123 在 操作系统
2,726 每月下载量
用于 3 个crate(2个直接)
87KB
2K SLoC
Kill Tree
🌳 Kill Tree是一个库和CLI工具,用于递归地终止指定的进程及其所有子进程,独立于其他命令(如kill或taskkill)运行。
它是用Rust编写的。
如果您将其用作Rust库,它支持同步和异步实现!
异步实现由Tokio提供。
本项目受到node-tree-kill的启发。谢谢。🤟
如果您能star(⭐)这个仓库,我将非常感激! 点击跳转到仓库。
为什么创建这个项目
TL;DR
我创建这个项目的原因是因为我有一个需要停止孙子进程的场景。
这是因为Windows平台上终止孙子进程的方式是使用Win32
API来确定进程关系,或者杀死它们(如Kill Tree的实现)或者调用taskkill
程序。
即使我的程序比taskkill
快3-5倍。(见bench)
这是我第一个Rust crate和CLI工具。这意味着我开始它是因为它是一个适合用Rust开发的合适项目。
没有多平台库或CLI可以递归地终止进程。
在过去,各种类型的CLI工具必须被调用来在Node.js项目中实现功能。
这是一个应该能够运行用于通用目的的shell脚本的项目,例如CI/CD工具。
在Windows平台上的Node.js环境中,我遇到了一个问题,即通过cmd创建子进程并运行应用程序或批处理脚本不会终止孙子进程。
原因在于Windows平台没有信号。
我必须调用taskkill来取消或强制杀死用户在Windows平台上运行的嵌套shell脚本。
当终止分布式应用程序时,我希望所有子进程都被终止。
因此,我使用了Npm.js中的一个名为tree-kill的库来解决它。从功能上来说,我对此很满意。
然而,如果下次因为类似的原因需要这个功能,我需要安装Node.js并获取tree-kill库来运行它。
这会创建一个深层依赖,因为它内部调用了CLI工具,如taskkill。
总之,需要Node.js运行时(包括npm包管理器)、tree-kill包和taskkill CLI工具。
如果我用npx或封装的Node.js脚本运行它,需要相当长的时间。
因为它首先加载和评估脚本,然后从脚本中调用taskkill来终止子进程。
通常需要在终端环境中终止进程,每个平台有不同的命令。
例如,kill -9 777
或 taskkill /T /F /PID 777
。
因此,如果这是一个Rust项目,我可以在代码中直接递归地终止进程。
我还统一了接口,因为各个平台上的进程终止CLI工具各不相同。
为什么使用Rust
Rust是一种“电池式”系统编程语言,没有垃圾回收器,具有包管理器、基本包注册中心和各种实用功能。
它还支持语言级别的异步编程模型,消除了对绿色线程的需求。
我从C开始编程之旅,主要使用C++,还探索了Go和JavaScript(包括TypeScript和Node.js)。
与C和C++缺乏主流的包管理器和注册中心,以及Node.js依赖于单线程事件循环不同,Rust提供了一个全面的生态系统。
Go的“电池式”方法,使用goroutines和channels进行并发,很有吸引力,但Rust的功能使我改变了主意。
(如果我不知道Rust,我可能已经在Go上妥协了。😉)
这就是为什么我使用Rust,而且使用它的时间不长,所以我想我会尝试更多。
为什么使用Tokio
我喜欢基于任务的并行处理。
Tokio是一个用Rust编写的异步运行时框架,也是最受欢迎的之一。
在当前环境中,标准异步运行时尚未可用,我认为使用Tokio是一个相当合理的选项。
Tokio的强大功能和众多特性帮助我专注于业务逻辑。
当我第一次学习编程时,我学习了基于角色的并行处理,并且我正在使用它。
例如,UI线程(主线程)、http请求线程、加载线程等。
这种方法不能保证线程之间的均匀吞吐量,可能会过载某些线程。
然而,基于任务的并行处理可能会平衡线程之间的吞吐量,从而不会浪费计算资源。
Rust编写的Tokio运行时允许我进行零成本基于任务的并行处理,而无需关注实现的细节。
如何使用
作为CLI工具使用
安装方法
🚨 请,查看脚本内容。始终注意安全!🚨
如果有一种安全安装可执行文件的方法,如果您能告诉我,我将不胜感激!
复制到剪贴板,粘贴到shell中并按Enter键。
Windows
iwr https://raw.githubusercontent.com/oneofthezombies/kill-tree/main/installs/windows.ps1 -UseBasicParsing | iex
Linux
sudo curl -sSL https://raw.githubusercontent.com/oneofthezombies/kill-tree/main/installs/linux.sh | sudo sh
Macos
sudo curl -sSL https://raw.githubusercontent.com/oneofthezombies/kill-tree/main/installs/macos.sh | sudo sh
以下是向进程ID为 777
的进程及其所有子进程发送 SIGTERM
信号的示例。
ℹ️ 在 Windows 平台上,包括 SIGTERM 和 SIGKILL 在内的所有信号都被忽略。
kill-tree 777
如果您想发送另一个信号,可以将该信号作为第二个参数输入。
以下是向进程ID为 777
的进程及其所有子进程发送 SIGKILL
信号的示例。
kill-tree 777 SIGKILL
作为Rust库使用
同步方法
将 kill_tree
添加到您的依赖项中。
# Cargo.toml
[dependencies]
kill_tree = "0.2"
同步API使用 kill_tree::blocking
启动。
使用默认信号 SIGTERM
递归地杀死进程及其子进程。
当函数被调用时返回进程信息列表。
进程信息是 Killed
或 MaybeAlreadyTerminated
。
如果进程信息是 Killed
类型,它包含 process_id
、parent_process_id
和 name
。
或 MaybeAlreadyTerminated
类型,它包含 process_id
和 source
。
由于它们可以在查询和杀死进程的过程中被杀死,所以有两种类型。
因此,即使查询或杀死进程看起来失败,也要考虑操作成功。
这是因为该库的目的是使进程达到 not exist
状态。
use kill_tree::{blocking::kill_tree, Output, Result};
fn main() -> Result<()> {
let process_id = 777;
let outputs = kill_tree(process_id)?;
for (index, output) in outputs.iter().enumerate() {
match output {
Output::Killed {
process_id,
parent_process_id,
name,
} => {
println!(
"[{index}] Killed process. process id: {process_id}, parent process id: {parent_process_id}, name: {name}"
);
}
Output::MaybeAlreadyTerminated { process_id, source } => {
println!(
"[{index}] Maybe already terminated process. process id: {process_id}, source: {source}"
);
}
}
}
Ok(())
}
使用信号 SIGKILL
递归地杀死进程及其子进程。
use kill_tree::{blocking::kill_tree_with_config, Config, Result};
fn main() -> Result<()> {
let process_id = 777;
let config = Config {
signal: "SIGKILL".to_string(),
..Default::default()
};
let outputs = kill_tree_with_config(process_id, &config)?;
println!("outputs: {outputs:?}"); // The `outputs` value is the same as the example `kill_tree`.
Ok(())
}
如果当 ctrl + c
(command + c
) 事件发生时,您想递归地杀死除当前进程之外的所有子进程。
use kill_tree::{blocking::kill_tree_with_config, Config};
use std::sync::mpsc::channel;
fn cleanup_children() {
let current_process_id = std::process::id();
let config = Config {
include_target: false,
..Default::default()
};
let result = kill_tree_with_config(current_process_id, &config);
println!("kill_tree_with_config: {result:?}");
}
fn main() {
let (tx, rx) = channel();
ctrlc::set_handler(move || {
cleanup_children();
tx.send(()).expect("Could not send signal on channel.");
})
.expect("Error setting handler.");
println!("Current process id: {}", std::process::id());
println!("Waiting for signal...");
rx.recv().expect("Could not receive from channel.");
println!("Got it! Exiting...");
}
异步方法
使用带有功能 tokio
的 kill_tree
添加到您的依赖项中。
# Cargo.toml
[dependencies]
kill_tree = { version = "0.2", features = ["tokio"] }
同步API使用 kill_tree::tokio
启动。
使用默认信号 SIGTERM
递归地杀死进程及其子进程。
use kill_tree::{get_available_max_process_id, tokio::kill_tree, Result};
#[tokio::main]
async fn main() -> Result<()> {
let outputs = kill_tree(get_available_max_process_id()).await?;
println!("outputs: {outputs:?}"); // The `outputs` value is the same as the example `kill_tree`.
Ok(())
}
在发送其他信号或接收和处理 ctrl + c
事件时,您只需将上面的 kill_tree::blocking
API 更改为 kill_tree::tokio
并执行 await
处理,它将等效。
支持平台和架构
平台 | 架构 | 支持 |
---|---|---|
Windows | x86_64 | ✅ |
Windows | aarch64 | 未测试 |
Linux | x86_64 | ✅ |
Linux | aarch64 | 未测试 |
Macos | x86_64 | ✅ |
Macos | aarch64 | ✅ |
此CLI和库依赖于操作系统的系统库。
因为操作系统拥有进程。
但不用担心!这是一个OS默认库,因此无需安装任何附加内容!
平台 | 依赖项 |
---|---|
Windows | kernel32.dll |
oleaut32.dll | |
ntdll.dll | |
advapi32.dll | |
bcrypt.dll | |
Linux | - |
Macos | libiconv.dylib |
libSystem.dylib |
依赖项
~2–39MB
~584K SLoC