Skip to content

Commit

Permalink
Implement timeouts for all exec command runners
Browse files Browse the repository at this point in the history
First is to write an internal CombinedOutput and Run function with a
timeout.

Second, the following instances of command runners need to have timeouts:

    plugins/inputs/ping/ping.go
    125:	out, err := c.CombinedOutput()

    plugins/inputs/exec/exec.go
    91:	if err := cmd.Run(); err != nil {

    plugins/inputs/ipmi_sensor/command.go
    31:	err := cmd.Run()

    plugins/inputs/sysstat/sysstat.go
    194:	out, err := cmd.CombinedOutput()

    plugins/inputs/leofs/leofs.go
    185:	defer cmd.Wait()

    plugins/inputs/sysstat/sysstat.go
    282:	if err := cmd.Wait(); err != nil {

closes #1067
  • Loading branch information
sparrc committed Apr 29, 2016
1 parent 5d3c582 commit 2ad29b5
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 1 deletion.
51 changes: 51 additions & 0 deletions internal/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ package internal

import (
"bufio"
"bytes"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
"time"
"unicode"
)

const alphanum string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

var timeoutErr = errors.New("Command timed out.")

// Duration just wraps time.Duration
type Duration struct {
Duration time.Duration
Expand Down Expand Up @@ -139,3 +144,49 @@ func SnakeCase(in string) string {

return string(out)
}

// CombinedOutputTimeout runs the given command with the given timeout and
// returns the combined output of stdout and stderr.
// If the command times out, it attempts to kill the process.
func CombinedOutputTimeout(c *exec.Cmd, timeout time.Duration) ([]byte, error) {
var b bytes.Buffer
c.Stdout = &b
c.Stderr = &b
if err := c.Start(); err != nil {
return nil, err
}
timer := time.NewTimer(timeout)
done := make(chan error)
go func() { done <- c.Wait() }()
select {
case err := <-done:
timer.Stop()
return b.Bytes(), err
case <-timer.C:
if err := c.Process.Kill(); err != nil {
log.Printf("FATAL error killing process: %s", err)
}
return b.Bytes(), timeoutErr
}
}

// RunTimeout runs the given command with the given timeout.
// If the command times out, it attempts to kill the process.
func RunTimeout(c *exec.Cmd, timeout time.Duration) error {
if err := c.Start(); err != nil {
return nil, err
}
timer := time.NewTimer(timeout)
done := make(chan error)
go func() { done <- c.Wait() }()
select {
case err := <-done:
timer.Stop()
return err
case <-timer.C:
if err := c.Process.Kill(); err != nil {
log.Printf("FATAL error killing process: %s", err)
}
return timeoutErr
}
}
6 changes: 5 additions & 1 deletion plugins/inputs/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ func AddNagiosState(exitCode error, acc telegraf.Accumulator) error {
return nil
}

func (c CommandRunner) Run(e *Exec, command string, acc telegraf.Accumulator) ([]byte, error) {
func (c CommandRunner) Run(
e *Exec,
command string,
acc telegraf.Accumulator,
) ([]byte, error) {
split_cmd, err := shellquote.Split(command)
if err != nil || len(split_cmd) == 0 {
return nil, fmt.Errorf("exec: unable to parse command, %s", err)
Expand Down

0 comments on commit 2ad29b5

Please sign in to comment.