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 4.7KB

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