manager.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package process
  2. import (
  3. "bytes"
  4. "errors"
  5. "fmt"
  6. "os/exec"
  7. "sync"
  8. "time"
  9. raven "github.com/getsentry/raven-go"
  10. log "gopkg.in/clog.v1"
  11. )
  12. var (
  13. ErrExecTimeout = errors.New("Process execution timeout")
  14. )
  15. const DEFAULT_TIMEOUT = 60 * time.Second
  16. // Process represents a running process calls shell command.
  17. type Process struct {
  18. PID int64
  19. Description string
  20. Start time.Time
  21. Cmd *exec.Cmd
  22. }
  23. type pidCounter struct {
  24. sync.Mutex
  25. // The current number of pid, initial is 0, and increase 1 every time it's been used.
  26. pid int64
  27. }
  28. func (c *pidCounter) PID() int64 {
  29. c.pid++
  30. return c.pid
  31. }
  32. var counter = new(pidCounter)
  33. var Processes []*Process
  34. // Add adds a process to global list and returns its PID.
  35. func Add(desc string, cmd *exec.Cmd) int64 {
  36. counter.Lock()
  37. defer counter.Unlock()
  38. pid := counter.PID()
  39. Processes = append(Processes, &Process{
  40. PID: pid,
  41. Description: desc,
  42. Start: time.Now(),
  43. Cmd: cmd,
  44. })
  45. return pid
  46. }
  47. // Remove removes a process from global list.
  48. // It returns true if the process is found and removed by given pid.
  49. func Remove(pid int64) bool {
  50. counter.Lock()
  51. defer counter.Unlock()
  52. for i := range Processes {
  53. if Processes[i].PID == pid {
  54. Processes = append(Processes[:i], Processes[i+1:]...)
  55. return true
  56. }
  57. }
  58. return false
  59. }
  60. // Exec starts executing a shell command in given path, it tracks corresponding process and timeout.
  61. func ExecDir(timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) {
  62. if timeout == -1 {
  63. timeout = DEFAULT_TIMEOUT
  64. }
  65. bufOut := new(bytes.Buffer)
  66. bufErr := new(bytes.Buffer)
  67. cmd := exec.Command(cmdName, args...)
  68. cmd.Dir = dir
  69. cmd.Stdout = bufOut
  70. cmd.Stderr = bufErr
  71. if err := cmd.Start(); err != nil {
  72. return "", err.Error(), err
  73. }
  74. pid := Add(desc, cmd)
  75. done := make(chan error)
  76. go func() {
  77. done <- cmd.Wait()
  78. }()
  79. var err error
  80. select {
  81. case <-time.After(timeout):
  82. if errKill := Kill(pid); errKill != nil {
  83. raven.CaptureErrorAndWait(err, nil)
  84. log.Error(2, "Fail to kill timeout process [pid: %d, desc: %s]: %v", pid, desc, errKill)
  85. }
  86. <-done
  87. return "", ErrExecTimeout.Error(), ErrExecTimeout
  88. case err = <-done:
  89. }
  90. Remove(pid)
  91. return bufOut.String(), bufErr.String(), err
  92. }
  93. // Exec starts executing a shell command, it tracks corresponding process and timeout.
  94. func ExecTimeout(timeout time.Duration, desc, cmdName string, args ...string) (string, string, error) {
  95. return ExecDir(timeout, "", desc, cmdName, args...)
  96. }
  97. // Exec starts executing a shell command, it tracks corresponding its process and use default timeout.
  98. func Exec(desc, cmdName string, args ...string) (string, string, error) {
  99. return ExecDir(-1, "", desc, cmdName, args...)
  100. }
  101. // Kill kills and removes a process from global list.
  102. func Kill(pid int64) error {
  103. for _, proc := range Processes {
  104. if proc.PID == pid {
  105. if proc.Cmd != nil && proc.Cmd.Process != nil &&
  106. proc.Cmd.ProcessState != nil && !proc.Cmd.ProcessState.Exited() {
  107. if err := proc.Cmd.Process.Kill(); err != nil {
  108. return fmt.Errorf("fail to kill process [pid: %d, desc: %s]: %v", proc.PID, proc.Description, err)
  109. }
  110. }
  111. Remove(pid)
  112. return nil
  113. }
  114. }
  115. return nil
  116. }