manager.go 3.5 KB

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