forked from sakishum/grsync
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
78 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package grsync | ||
|
||
import ( | ||
"errors" | ||
"log" | ||
"os/exec" | ||
"syscall" | ||
"time" | ||
) | ||
|
||
var ErrTimeout = errors.New("command timed out") | ||
|
||
// KillGrace is the amount of time we allow a process to shutdown before | ||
// sending a SIGKILL. | ||
const KillGrace = 5 * time.Second | ||
|
||
// WaitTimeout waits for the given command to finish with a timeout. | ||
// It assumes the command has already been started. | ||
// If the command times out, it attempts to kill the process and returns | ||
// a ErrTimeout error. | ||
func WaitTimeout(c *exec.Cmd, timeout time.Duration) error { | ||
var kill *time.Timer | ||
|
||
term := time.AfterFunc(timeout, func() { | ||
err := c.Process.Signal(syscall.SIGTERM) | ||
if err != nil { | ||
log.Printf("Error terminating process: %s", err) | ||
return | ||
} | ||
|
||
kill = time.AfterFunc(KillGrace, func() { | ||
err := c.Process.Kill() | ||
if err != nil { | ||
log.Printf("Error killing process: %s", err) | ||
return | ||
} | ||
}) | ||
}) | ||
|
||
err := c.Wait() | ||
|
||
// Shutdown all timers (the kill timer and the term timer) before checking cmd err, | ||
// otherwise there is no chance to turn off these timers that have not expired. | ||
if kill != nil { | ||
kill.Stop() | ||
} | ||
termSent := !term.Stop() | ||
// For a timer created with AfterFunc(d, f), if t.Stop returns false, then | ||
// the timer has already expired and the function f has been started in its own goroutine. | ||
// So if termSent is true, it means the cmd does not finished before the term timer expired. | ||
|
||
// Now, we can check cmd err. | ||
// If the process exited without error treat it as success. | ||
// This allows a process to do a clean shutdown on signal. | ||
if err == nil { | ||
return nil | ||
} | ||
|
||
// If SIGTERM was sent then treat any process error as a timeout. | ||
if termSent { | ||
return ErrTimeout | ||
} | ||
|
||
// Otherwise there was an cmd error unrelated to termination. | ||
return err | ||
} |