Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang - kill process by name

What would be an effective way to kill a process with Go code if you only know the process name? I see some functions provided by the os package like:

func FindProcess(pid int) (*Process, error)
func (p *Process) Kill() error
func (p *Process) Signal(sig Signal) error

Is there a good/common practice to get the pid without having to execute commands and then parse the output?

I have found a way to get back the pid using a command like the following:

  • echo $(ps cax | grep myapp | grep -o '^[ ]*[0-9]*')

and I have used it with exec.Command() but I would like to avoid it if there is a better approach.

like image 834
tgogos Avatar asked Dec 09 '16 12:12

tgogos


People also ask

How do you kill a go process?

The SIGINT signal is sent to a process by its controlling terminal when a user wishes to interrupt the process. This is typically initiated by pressing Ctrl+C, but on some systems, the "delete" character or "break" key can be used.

How do you stop a process in Golang?

The provided context is used to kill the process (by calling os. Process. Kill) if the context becomes done before the command completes on its own. After the Run() completes, you can inspect ctx.

How kill all services in Linux?

The easiest way is to use the Magic SysRq key : Alt + SysRq + i . This will kill all processes except for init . Alt + SysRq + o will shut down the system (killing init also). Also note that on some modern keyboards, you have to use PrtSc rather than SysRq .


3 Answers

Running external commands is probably the best way to do this. However, the following code runs on Ubuntu at least as long as you are the owner of the process to kill.

// killprocess project main.go
package main

import (
    "bytes"
    "fmt"
    "io"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "strconv"
    "strings"
)

// args holds the commandline args
var args []string

// findAndKillProcess walks iterative through the /process directory tree
// looking up the process name found in each /proc/<pid>/status file. If
// the name matches the name in the argument the process with the corresponding
// <pid> will be killed.
func findAndKillProcess(path string, info os.FileInfo, err error) error {
    // We just return in case of errors, as they are likely due to insufficient
    // privileges. We shouldn't get any errors for accessing the information we
    // are interested in. Run as root (sudo) and log the error, in case you want
    // this information.
    if err != nil {
        // log.Println(err)
        return nil
    }

    // We are only interested in files with a path looking like /proc/<pid>/status.
    if strings.Count(path, "/") == 3 {
        if strings.Contains(path, "/status") {

            // Let's extract the middle part of the path with the <pid> and
            // convert the <pid> into an integer. Log an error if it fails.
            pid, err := strconv.Atoi(path[6:strings.LastIndex(path, "/")])
            if err != nil {
                log.Println(err)
                return nil
            }

            // The status file contains the name of the process in its first line.
            // The line looks like "Name: theProcess".
            // Log an error in case we cant read the file.
            f, err := ioutil.ReadFile(path)
            if err != nil {
                log.Println(err)
                return nil
            }

            // Extract the process name from within the first line in the buffer
            name := string(f[6:bytes.IndexByte(f, '\n')])

            if name == args[1] {
                fmt.Printf("PID: %d, Name: %s will be killed.\n", pid, name)
                proc, err := os.FindProcess(pid)
                if err != nil {
                    log.Println(err)
                }
                // Kill the process
                proc.Kill()

                // Let's return a fake error to abort the walk through the
                // rest of the /proc directory tree
                return io.EOF
            }

        }
    }

    return nil
}

// main is the entry point of any go application
func main() {
    args = os.Args
    if len(args) != 2 {
        log.Fatalln("Usage: killprocess <processname>")
    }
    fmt.Printf("trying to kill process \"%s\"\n", args[1])

    err := filepath.Walk("/proc", findAndKillProcess)
    if err != nil {
        if err == io.EOF {
            // Not an error, just a signal when we are done
            err = nil
        } else {
            log.Fatal(err)
        }
    }
}

It's just an example that certainly can be improved. I wrote this for Linux and tested the code on Ubuntu 15.10. It will not run on Windows.

like image 80
Peter Gloor Avatar answered Nov 08 '22 17:11

Peter Gloor


I finally used something like the following:

// `echo "sudo_password" | sudo -S [command]`
// is used in order to run the command with `sudo`

_, err := exec.Command("sh", "-c", "echo '"+ sudopassword +"' | sudo -S pkill -SIGINT my_app_name").Output()

if err != nil {
    // ...
} else {
    // ...
}

I used the SIGINT signal to gracefully stop the app.

From wikipedia:

  • SIGINT

    The SIGINT signal is sent to a process by its controlling terminal when a user wishes to interrupt the process. This is typically initiated by pressing Ctrl+C, but on some systems, the "delete" character or "break" key can be used.

  • SIGKILL

    The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon receiving this signal. The following exceptions apply:

like image 30
tgogos Avatar answered Nov 08 '22 15:11

tgogos


Cross-Platform (3rd party) Solution

I've implemented various solutions to do this for months now, and for some reason it took me that long to find gopsutil. It is a 3rd party library and that may or may not be a deal breaker for you, but it has worked flawlessly for our cross-platform projects. The following example will kill the first process with the matching name, but it can easily be adapted to kill all processes with the name.

import "github.com/shirou/gopsutil/v3/process"

func KillProcess(name string) error {
    processes, err := process.Processes()
    if err != nil {
        return err
    }
    for _, p := range processes {
        n, err := p.Name()
        if err != nil {
            return err
        }
        if n == name {
            return p.Kill()
        }
    }
    return fmt.Errorf("process not found")
}

With Context Support

As an added bonus, the library also supports context cancellation on all process related operations including process queries, and killing the process.

func KillAllProcessesCtx(ctx context.Context, name string) error {
    processes, err := process.ProcessesWithContext(ctx)
    if err != nil {
        return err
    }
    for _, p := range processes {
        n, err := p.NameWithContext(ctx)
        if err != nil {
            return err
        }
        if n == name {
            err = p.KillWithContext(ctx)
            if err != nil {
                return err
            }
        }
    }
    return nil
}

Graceful Termination

The library also supports graceful termination by sending your own signal to the process.

// Do this
err = p.SendSignal(syscall.SIGINT)
        
// Instead of this
err = p.Kill()
like image 33
Clark McCauley Avatar answered Nov 08 '22 16:11

Clark McCauley