本站源代码
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.

158 lines
4.4KB

  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. "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. // ErrExecTimeout represent a timeout error
  21. ErrExecTimeout = errors.New("Process execution timeout")
  22. manager *Manager
  23. )
  24. // Process represents a working process inherit from Gogs.
  25. type Process struct {
  26. PID int64 // Process ID, not system one.
  27. Description string
  28. Start time.Time
  29. Cmd *exec.Cmd
  30. }
  31. // Manager knows about all processes and counts PIDs.
  32. type Manager struct {
  33. mutex sync.Mutex
  34. counter int64
  35. Processes map[int64]*Process
  36. }
  37. // GetManager returns a Manager and initializes one as singleton if there's none yet
  38. func GetManager() *Manager {
  39. if manager == nil {
  40. manager = &Manager{
  41. Processes: make(map[int64]*Process),
  42. }
  43. }
  44. return manager
  45. }
  46. // Add a process to the ProcessManager and returns its PID.
  47. func (pm *Manager) Add(description string, cmd *exec.Cmd) int64 {
  48. pm.mutex.Lock()
  49. pid := pm.counter + 1
  50. pm.Processes[pid] = &Process{
  51. PID: pid,
  52. Description: description,
  53. Start: time.Now(),
  54. Cmd: cmd,
  55. }
  56. pm.counter = pid
  57. pm.mutex.Unlock()
  58. return pid
  59. }
  60. // Remove a process from the ProcessManager.
  61. func (pm *Manager) Remove(pid int64) {
  62. pm.mutex.Lock()
  63. delete(pm.Processes, pid)
  64. pm.mutex.Unlock()
  65. }
  66. // Exec a command and use the default timeout.
  67. func (pm *Manager) Exec(desc, cmdName string, args ...string) (string, string, error) {
  68. return pm.ExecDir(-1, "", desc, cmdName, args...)
  69. }
  70. // ExecTimeout a command and use a specific timeout duration.
  71. func (pm *Manager) ExecTimeout(timeout time.Duration, desc, cmdName string, args ...string) (string, string, error) {
  72. return pm.ExecDir(timeout, "", desc, cmdName, args...)
  73. }
  74. // ExecDir a command and use the default timeout.
  75. func (pm *Manager) ExecDir(timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) {
  76. return pm.ExecDirEnv(timeout, dir, desc, nil, cmdName, args...)
  77. }
  78. // ExecDirEnv runs a command in given path and environment variables, and waits for its completion
  79. // up to the given timeout (or DefaultTimeout if -1 is given).
  80. // Returns its complete stdout and stderr
  81. // outputs and an error, if any (including timeout)
  82. func (pm *Manager) ExecDirEnv(timeout time.Duration, dir, desc string, env []string, cmdName string, args ...string) (string, string, error) {
  83. return pm.ExecDirEnvStdIn(timeout, dir, desc, env, nil, cmdName, args...)
  84. }
  85. // ExecDirEnvStdIn runs a command in given path and environment variables with provided stdIN, and waits for its completion
  86. // up to the given timeout (or DefaultTimeout if -1 is given).
  87. // Returns its complete stdout and stderr
  88. // outputs and an error, if any (including timeout)
  89. func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env []string, stdIn io.Reader, cmdName string, args ...string) (string, string, error) {
  90. if timeout == -1 {
  91. timeout = 60 * time.Second
  92. }
  93. stdOut := new(bytes.Buffer)
  94. stdErr := new(bytes.Buffer)
  95. ctx, cancel := context.WithTimeout(context.Background(), timeout)
  96. defer cancel()
  97. cmd := exec.CommandContext(ctx, cmdName, args...)
  98. cmd.Dir = dir
  99. cmd.Env = env
  100. cmd.Stdout = stdOut
  101. cmd.Stderr = stdErr
  102. if stdIn != nil {
  103. cmd.Stdin = stdIn
  104. }
  105. if err := cmd.Start(); err != nil {
  106. return "", "", err
  107. }
  108. pid := pm.Add(desc, cmd)
  109. err := cmd.Wait()
  110. pm.Remove(pid)
  111. if err != nil {
  112. err = fmt.Errorf("exec(%d:%s) failed: %v(%v) stdout: %v stderr: %v", pid, desc, err, ctx.Err(), stdOut, stdErr)
  113. }
  114. return stdOut.String(), stdErr.String(), err
  115. }
  116. // Kill and remove a process from list.
  117. func (pm *Manager) Kill(pid int64) error {
  118. if proc, exists := pm.Processes[pid]; exists {
  119. pm.mutex.Lock()
  120. if proc.Cmd != nil &&
  121. proc.Cmd.Process != nil &&
  122. proc.Cmd.ProcessState != nil &&
  123. !proc.Cmd.ProcessState.Exited() {
  124. if err := proc.Cmd.Process.Kill(); err != nil {
  125. return fmt.Errorf("failed to kill process(%d/%s): %v", pid, proc.Description, err)
  126. }
  127. }
  128. delete(pm.Processes, pid)
  129. pm.mutex.Unlock()
  130. }
  131. return nil
  132. }
上海开阖软件有限公司 沪ICP备12045867号-1