manager.go 3.4 KB

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