3

Currently, I am terminating a process using the Golang os.exec.Cmd.Process.Kill() method (on an Ubuntu box).

This seems to terminate the process immediately instead of gracefully. Some of the processes that I am launching also write to files, and it causes the files to become truncated.

I want to terminate the process gracefully with a SIGTERM instead of a SIGKILL using Golang.

Here is a simple example of a process that is started and then terminated using cmd.Process.Kill(), I would like an alternative in Golang to the Kill() method which uses SIGTERM instead of SIGKILL, thanks!

import "os/exec"

cmd := exec.Command("nc", "example.com", "80")
if err := cmd.Start(); err != nil {
   log.Print(err)
}
go func() {
  cmd.Wait()
}()
// Kill the process - this seems to kill the process ungracefully
cmd.Process.Kill()
wasmup
  • 14,541
  • 6
  • 42
  • 58
Evyatar Saias
  • 698
  • 7
  • 18

3 Answers3

7

You can use Signal() API. The supported Syscalls are here.

So basically you might want to use

cmd.Process.Signal(syscall.SIGTERM)

Also please note as per documentation.

The only signal values guaranteed to be present in the os package on all systems are os.Interrupt (send the process an interrupt) and os.Kill (force the process to exit). On Windows, sending os.Interrupt to a process with os.Process.Signal is not implemented; it will return an error instead of sending a signal.

2
cmd.Process.Signal(syscall.SIGTERM)
notorious.no
  • 4,919
  • 3
  • 20
  • 34
1

You may use:

cmd.Process.Signal(os.Interrupt)

Tested example:

package main

import (
    "fmt"
    "log"
    "net"
    "os"
    "os/exec"
    "sync"
    "time"
)

func main() {
    cmd := exec.Command("nc", "-l", "8080")
    cmd.Stderr = os.Stderr
    cmd.Stdout = os.Stdout
    cmd.Stdin = os.Stdin
    err := cmd.Start()
    if err != nil {
        log.Fatal(err)
    }

    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        err := cmd.Wait()
        if err != nil {
            fmt.Println("cmd.Wait:", err)
        }
        fmt.Println("done")
        wg.Done()
    }()

    fmt.Println("TCP Dial")
    fmt.Println("Pid =", cmd.Process.Pid)
    time.Sleep(200 * time.Millisecond)
    // or comment this and use: nc 127.0.0.1 8080
    w1, err := net.DialTimeout("tcp", "127.0.0.1:8080", 1*time.Second)
    if err != nil {
        log.Fatal("tcp DialTimeout:", err)
    }
    defer w1.Close()

    fmt.Fprintln(w1, "Hi")
    time.Sleep(1 * time.Second)
    // cmd.Process.Kill()
    cmd.Process.Signal(os.Interrupt)
    wg.Wait()
}

Output:

TCP Dial
Pid = 21257
Hi
cmd.Wait: signal: interrupt
done
wasmup
  • 14,541
  • 6
  • 42
  • 58
  • 1
    I needed SIGTERM, so syscall.SIGTERM did the trick for my use case, but this is also a fantastic answer, os.Interrupt will also be very useful in some scenarios, thank you for the example. – Evyatar Saias Jul 23 '21 at 17:37