You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

manager.go 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package process
  6. import (
  7. "bytes"
  8. "context"
  9. "fmt"
  10. "io"
  11. "os/exec"
  12. "sort"
  13. "sync"
  14. "time"
  15. )
  16. // TODO: This packages still uses a singleton for the Manager.
  17. // Once there's a decent web framework and dependencies are passed around like they should,
  18. // then we delete the singleton.
  19. var (
  20. manager *Manager
  21. managerInit sync.Once
  22. // DefaultContext is the default context to run processing commands in
  23. DefaultContext = context.Background()
  24. )
  25. // Process represents a working process inheriting from Gitea.
  26. type Process struct {
  27. PID int64 // Process ID, not system one.
  28. Description string
  29. Start time.Time
  30. Cancel context.CancelFunc
  31. }
  32. // Manager knows about all processes and counts PIDs.
  33. type Manager struct {
  34. mutex sync.Mutex
  35. counter int64
  36. processes map[int64]*Process
  37. }
  38. // GetManager returns a Manager and initializes one as singleton if there's none yet
  39. func GetManager() *Manager {
  40. managerInit.Do(func() {
  41. manager = &Manager{
  42. processes: make(map[int64]*Process),
  43. }
  44. })
  45. return manager
  46. }
  47. // Add a process to the ProcessManager and returns its PID.
  48. func (pm *Manager) Add(description string, cancel context.CancelFunc) int64 {
  49. pm.mutex.Lock()
  50. pid := pm.counter + 1
  51. pm.processes[pid] = &Process{
  52. PID: pid,
  53. Description: description,
  54. Start: time.Now(),
  55. Cancel: cancel,
  56. }
  57. pm.counter = pid
  58. pm.mutex.Unlock()
  59. return pid
  60. }
  61. // Remove a process from the ProcessManager.
  62. func (pm *Manager) Remove(pid int64) {
  63. pm.mutex.Lock()
  64. delete(pm.processes, pid)
  65. pm.mutex.Unlock()
  66. }
  67. // Cancel a process in the ProcessManager.
  68. func (pm *Manager) Cancel(pid int64) {
  69. pm.mutex.Lock()
  70. process, ok := pm.processes[pid]
  71. pm.mutex.Unlock()
  72. if ok {
  73. process.Cancel()
  74. }
  75. }
  76. // Processes gets the processes in a thread safe manner
  77. func (pm *Manager) Processes() []*Process {
  78. pm.mutex.Lock()
  79. processes := make([]*Process, 0, len(pm.processes))
  80. for _, process := range pm.processes {
  81. processes = append(processes, process)
  82. }
  83. pm.mutex.Unlock()
  84. sort.Sort(processList(processes))
  85. return processes
  86. }
  87. // Exec a command and use the default timeout.
  88. func (pm *Manager) Exec(desc, cmdName string, args ...string) (string, string, error) {
  89. return pm.ExecDir(-1, "", desc, cmdName, args...)
  90. }
  91. // ExecTimeout a command and use a specific timeout duration.
  92. func (pm *Manager) ExecTimeout(timeout time.Duration, desc, cmdName string, args ...string) (string, string, error) {
  93. return pm.ExecDir(timeout, "", desc, cmdName, args...)
  94. }
  95. // ExecDir a command and use the default timeout.
  96. func (pm *Manager) ExecDir(timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) {
  97. return pm.ExecDirEnv(timeout, dir, desc, nil, cmdName, args...)
  98. }
  99. // ExecDirEnv runs a command in given path and environment variables, and waits for its completion
  100. // up to the given timeout (or DefaultTimeout if -1 is given).
  101. // Returns its complete stdout and stderr
  102. // outputs and an error, if any (including timeout)
  103. func (pm *Manager) ExecDirEnv(timeout time.Duration, dir, desc string, env []string, cmdName string, args ...string) (string, string, error) {
  104. return pm.ExecDirEnvStdIn(timeout, dir, desc, env, nil, cmdName, args...)
  105. }
  106. // ExecDirEnvStdIn runs a command in given path and environment variables with provided stdIN, and waits for its completion
  107. // up to the given timeout (or DefaultTimeout if -1 is given).
  108. // Returns its complete stdout and stderr
  109. // outputs and an error, if any (including timeout)
  110. func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env []string, stdIn io.Reader, cmdName string, args ...string) (string, string, error) {
  111. if timeout == -1 {
  112. timeout = 60 * time.Second
  113. }
  114. stdOut := new(bytes.Buffer)
  115. stdErr := new(bytes.Buffer)
  116. ctx, cancel := context.WithTimeout(DefaultContext, timeout)
  117. defer cancel()
  118. cmd := exec.CommandContext(ctx, cmdName, args...)
  119. cmd.Dir = dir
  120. cmd.Env = env
  121. cmd.Stdout = stdOut
  122. cmd.Stderr = stdErr
  123. if stdIn != nil {
  124. cmd.Stdin = stdIn
  125. }
  126. if err := cmd.Start(); err != nil {
  127. return "", "", err
  128. }
  129. pid := pm.Add(desc, cancel)
  130. err := cmd.Wait()
  131. pm.Remove(pid)
  132. if err != nil {
  133. err = &Error{
  134. PID: pid,
  135. Description: desc,
  136. Err: err,
  137. CtxErr: ctx.Err(),
  138. Stdout: stdOut.String(),
  139. Stderr: stdErr.String(),
  140. }
  141. }
  142. return stdOut.String(), stdErr.String(), err
  143. }
  144. type processList []*Process
  145. func (l processList) Len() int {
  146. return len(l)
  147. }
  148. func (l processList) Less(i, j int) bool {
  149. return l[i].PID < l[j].PID
  150. }
  151. func (l processList) Swap(i, j int) {
  152. l[i], l[j] = l[j], l[i]
  153. }
  154. // Error is a wrapped error describing the error results of Process Execution
  155. type Error struct {
  156. PID int64
  157. Description string
  158. Err error
  159. CtxErr error
  160. Stdout string
  161. Stderr string
  162. }
  163. func (err *Error) Error() string {
  164. return fmt.Sprintf("exec(%d:%s) failed: %v(%v) stdout: %s stderr: %s", err.PID, err.Description, err.Err, err.CtxErr, err.Stdout, err.Stderr)
  165. }
  166. // Unwrap implements the unwrappable implicit interface for go1.13 Unwrap()
  167. func (err *Error) Unwrap() error {
  168. return err.Err
  169. }