#child-process #process #kill #terminate #cli-tool #taskkill #tree-kill

kill_tree

🌳 Kill Tree is a library designed to terminate a specified process and all its child processes recursively, operating independently of other commands like kill or taskkill

10 releases

0.2.4 Feb 12, 2024
0.2.3 Feb 12, 2024
0.1.5 Feb 7, 2024

#2 in #terminate

Download history 4/week @ 2024-02-01 72/week @ 2024-02-08 73/week @ 2024-02-15 23/week @ 2024-02-22 10/week @ 2024-02-29 9/week @ 2024-03-07 27/week @ 2024-03-14 1/week @ 2024-03-21 12/week @ 2024-03-28 58/week @ 2024-04-04 89/week @ 2024-04-11 116/week @ 2024-04-18

275 downloads per month
Used in 3 crates (2 directly)

MIT license

87KB
2K SLoC

Kill Tree

logo

🌳 Kill Tree is a library and CLI tool designed to terminate a specified process and all its child processes recursively, operating independently of other commands like kill or taskkill.
It is written in Rust.
If you use this as a Rust library, it supports both synchronous and asynchronous implementation!
Asynchronous implementation is powerd by Tokio.
This project was inspired by node-tree-kill. Thank you. 🤟

I would really APPRECIATE it if you could star(⭐) this repository! Click to jump to the repository.

Build Status Crates.io

Why I Created This

TL;DR


The reason I created this is because I have a case where I need to stop grandchild processes.
This is because the way to kill a grandchild process on the Windows platform is to use the Win32 API to determine the process relationship and either kill them all (Kill Tree's implementation) or call the taskkill program.
Even my program is more than 3-5x faster than taskkill. (see bench)


This is my first Rust crate and CLI tool. it means I started it because it was a suitable project for development with Rust.
No multi-platform library or CLI existed that could terminate processes recursively.

In the past, various types of CLI tools had to be invoked to implement features in the Node.js project.
It was a project that should be able to run shell scripts for general purposely like CI/CD tools.
In the Node.js environment on Windows, I encountered an issue where creating a child process and running an application or batch script via cmd would not terminate the grandchild process.
The reason is that the Windows platform does not have Signal.
I had to call taskkill to cancel or force kill nested shell scripts that user ran on Windows platforms.
When terminating the distributed application, I wanted all child processes to be terminated.
So, I solved it using a library called tree-kill in Npm.js. functionally, I was satisfied.

However, if I need this feature for similar reasons next time, I will need to install Node.js and I fetch tree-kill library and run it.
This creates a deep dependency because it internally invokes CLI tools such as taskkill.
In summary, Node.js runtime (including npm package manager), tree-kill package and taskkill CLI tool are required.
If I run this with an npx or wrapped Node.js script, it takes quite a while.
because it loads and evaluates the script, then calls taskkill from the script to terminate the child process.

And it is often necessary to terminate the process in a terminal environment, with different commands for each platform.
For example, kill -9 777 or taskkill /T /F /PID 777.

So I made it possible to recursively kill the processes directly from the code if it was a Rust project.
I also unified the interface, as the CLI tool to kill processes varies across platforms.

Why Use Rust

Rust is a 'battery-included' system programming language without a garbage collector, featuring a package manager, basic package registry, and various utility features.
It also supports an asynchronous programming model at the language level, eliminating the need for green threads.

Having started my programming journey with C and primarily using C++, I've also explored Go and JavaScript (including TypeScript and Node.js).
Unlike C and C++, which lack a mainstream package manager and registry, and Node.js, which relies on a single-threaded event loop, Rust offers a comprehensive ecosystem.
Go's 'battery-included' approach with goroutines and channels for concurrency was appealing, but Rust's capabilities convinced me otherwise.
(If I didn't know Rust, I might have compromised in Go. 🙂)

This is why I use Rust, and it hasn't been long since I used it, so I think I'll try more.

Why Use Tokio

I like Task based parallelism.

Tokio is an asynchronous runtime framework written in Rust, and it is one of the most widely used.
In the current environment where Standard async runtime is not yet available, I think using Tokio is a pretty reasonable option.
Tokio's robust and many features help me focus on my business logic.

When I first learned programming, I learned Role based parallelism, and I use this.
For example, UI thread (main thread), http request thread, loading thread, etc.
This approach does not guarantee uniform throughput across threads, potentially overburdening certain threads.
However, Task based parallelism is likely to equalize throughput between threads so that computing resources are not wasted.

The Tokio runtime, written in Rust, allows me to work on Zero cost task based parallelism without having to pay attention to detail implementation.

How to Use

Using as CLI Tool

How to Install

🚨 Please, see the SCRIPT contents. always SECURITY! 🚨
If there is a way to safely install the executable file, I would appreciate it if you could tell me!

Copy to clipboard and Paste to shell and 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

Below is an example of sending SIGTERM signals to a process with process ID 777, and to all child processes.

ℹ️ On Windows platforms, all signals including SIGTERM and SIGKILL are ignored.

kill-tree 777

If you want to send another signal, you can enter that signal as the second parameter.
Below is an example of sending a SIGKILL signal to a process with process ID 777 and to all child processes.

kill-tree 777 SIGKILL

Using as Rust Library

Synchronous Method

Add kill_tree to your dependencies.

# Cargo.toml
[dependencies]
kill_tree = "0.2"

Synchronous api is started with kill_tree::blocking.

Kill process and its children recursively with default signal SIGTERM.
Returns a list of process information when a function is called.
Process information is Killed or MaybeAlreadyTerminated.
If process information is Killed type, it has process_id, parent_process_id and name.
Or MaybeAlreadyTerminated type, it has process_id, source.

There are two types because they can be killed during the process of querying and killing processes.
Therefore, consider the operation successful even if the query or kill process appears to fail.
This is because the purpose of this library is to make the process not exist state.

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(())
}

Kill process and its children recursively with signal 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(())
}

If you want to recursively kill all child processes except the current process when the ctrl + c (command + c) event occurs.

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...");
}

Asynchronous Method

Add kill_tree to your dependencies with feature tokio.

# Cargo.toml
[dependencies]
kill_tree = { version = "0.2", features = ["tokio"] }

Synchronous api is started with kill_tree::tokio.

Kill process and its children recursively with default signal 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(())
}

When sending other signals or receiving and processing ctrl + c events, all you have to do is change the above kill_tree::blocking api to kill_tree::tokio and perform await processing, and it will be equivalent.

Support Platform and Architecture

Platform Architecture Support
Windows x86_64
Windows aarch64 Not tested
Linux x86_64
Linux aarch64 Not tested
Macos x86_64
Macos aarch64

This CLI and library depend on an operating system's system library.
Because it's the operating system that owns the processes.
But, don't worry! It's an OS default library, so there's no need to install anything additional!

Platform Dependencies
Windows kernel32.dll
oleaut32.dll
ntdll.dll
advapi32.dll
bcrypt.dll
Linux -
Macos libiconv.dylib
libSystem.dylib

Dependencies

~2–41MB
~620K SLoC