manager.go 3.1 KB

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