Terminating a Process Started with os/exec in Golang

Go

Go Problem Overview


Is there a way to terminate a process started with os.exec in Golang? For example (from http://golang.org/pkg/os/exec/#example_Cmd_Start),

cmd := exec.Command("sleep", "5")
err := cmd.Start()
if err != nil {
    log.Fatal(err)
}
log.Printf("Waiting for command to finish...")
err = cmd.Wait()
log.Printf("Command finished with error: %v", err)

Is there a way to terminate that process ahead of time, perhaps after 3 seconds?

Thanks in advance

Go Solutions


Solution 1 - Go

Run and terminate an exec.Process:

// Start a process:
cmd := exec.Command("sleep", "5")
if err := cmd.Start(); err != nil {
	log.Fatal(err)
}

// Kill it:
if err := cmd.Process.Kill(); err != nil {
    log.Fatal("failed to kill process: ", err)
}

Run and terminate an exec.Process after a timeout:

ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()

if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
    // This will fail after 3 seconds. The 5 second sleep
    // will be interrupted.
}

See this example in the Go docs


Legacy

Before Go 1.7, we didn't have the context package and this answer was different.

Run and terminate an exec.Process after a timeout using channels and a goroutine:

// Start a process:
cmd := exec.Command("sleep", "5")
if err := cmd.Start(); err != nil {
	log.Fatal(err)
}

// Wait for the process to finish or kill it after a timeout (whichever happens first):
done := make(chan error, 1)
go func() {
	done <- cmd.Wait()
}()
select {
case <-time.After(3 * time.Second):
	if err := cmd.Process.Kill(); err != nil {
		log.Fatal("failed to kill process: ", err)
	}
	log.Println("process killed as timeout reached")
case err := <-done:
	if err != nil {
		log.Fatalf("process finished with error = %v", err)
	}
	log.Print("process finished successfully")
}

Either the process ends and its error (if any) is received through done or 3 seconds have passed and the program is killed before it's finished.

Solution 2 - Go

The other answers are right about calling Kill(), but the parts regarding killing the process after a timeout are little outdated now.

This can be done now with the context package and exec.CommandContext (example adapted from the example in the docs):

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
	defer cancel()

	if err := exec.CommandContext(ctx, "sleep", "5").Run(); err != nil {
		// This will fail after 100 milliseconds. The 5 second sleep
		// will be interrupted.
	}
}

From the docs: > 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.Err(). If the timeout was reached, the type of the error returned will be DeadLineExceeded. If it's nil, check the err returned by Run() to see if the command completed without errors.

Solution 3 - Go

A simpler version without select and channels.

func main() {
	cmd := exec.Command("cat", "/dev/urandom")
    cmd.Start()
	timer := time.AfterFunc(1*time.Second, func() {
		err := cmd.Process.Kill()
		if err != nil {
			panic(err) // panic as can't kill a process.
		}
	})
	err := cmd.Wait()
    timer.Stop()

    // read error from here, you will notice the kill from the 
    fmt.Println(err)
}

> Well, after consulting some experienced go programmer, this is apparently not a GOly enough way to solve the problem. So please refer to the accepted answer.


Here is an even shorter version, and very straight forward. BUT, possibly having tons of hanging goroutines if timeout is long.

func main() {
    cmd := exec.Command("cat", "/dev/urandom")
	cmd.Start()
	go func(){
		time.Sleep(timeout)
		cmd.Process.Kill()
	}()
	return cmd.Wait()
}

Solution 4 - Go

While exec.CommandContext is very convenient and works fine in most cases, I had some issues with the process' children staying alive - which resulted in cmd.Wait() hanging.

If someone encounters a similar situation, here's how I solved the issue.

  1. Request process group to be created before starting the command using Setpgid
  2. Start a go routine that will kill the process group upon timeout

Naive example (for readability):

cmd := exec.Command("sleep", "5")

// Request the OS to assign process group to the new process, to which all its children will belong
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

go func() {
    time.Sleep(time.Second)
    // Send kill signal to the process group instead of single process (it gets the same value as the PID, only negative)
    syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) 
}

err := cmd.Run()
if err != nil {
    log.Fatal(err)
}
log.Printf("Command finished successfully")

A bit nicer example (can be less intuitive for new Gophers):

	// Create a context with timeout, which will close ctx.Done() channel upon timeout
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	defer cancel() // Make sure the context is canceled, which will close ctx.Done() channel on function exit
	cmd := exec.Command("sleep", "5")

	// Request the OS to assign process group to the new process, to which all its children will belong
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

	go func() {
		// Wait until timeout or deferred cancellation
		<- ctx.Done()

		// Send kill signal to the process group instead of single process (it gets the same value as the PID, only negative)
		_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
	}()

	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Command finished successfully")

P.S. For brevity, I replaced cmd.Start + cmd.Wait with cmd.Run

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTech163View Question on Stackoverflow
Solution 1 - GoZippoView Answer on Stackoverflow
Solution 2 - GoDavid CastilloView Answer on Stackoverflow
Solution 3 - GoxiaoyiView Answer on Stackoverflow
Solution 4 - GoJean SpectorView Answer on Stackoverflow