manager.go 3.1 KB

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