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

manager.go 4.1 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package process
  5. import (
  6. "bytes"
  7. "errors"
  8. "fmt"
  9. "os/exec"
  10. "sync"
  11. "time"
  12. "code.gitea.io/gitea/modules/log"
  13. )
  14. // TODO: This packages still uses a singleton for the Manager.
  15. // Once there's a decent web framework and dependencies are passed around like they should,
  16. // then we delete the singleton.
  17. var (
  18. // ErrExecTimeout represent a timeout error
  19. ErrExecTimeout = errors.New("Process execution timeout")
  20. manager *Manager
  21. )
  22. // Process represents a working process inherit from Gogs.
  23. type Process struct {
  24. PID int64 // Process ID, not system one.
  25. Description string
  26. Start time.Time
  27. Cmd *exec.Cmd
  28. }
  29. // Manager knows about all processes and counts PIDs.
  30. type Manager struct {
  31. mutex sync.Mutex
  32. counter int64
  33. Processes map[int64]*Process
  34. }
  35. // GetManager returns a Manager and initializes one as singleton if there's none yet
  36. func GetManager() *Manager {
  37. if manager == nil {
  38. manager = &Manager{
  39. Processes: make(map[int64]*Process),
  40. }
  41. }
  42. return manager
  43. }
  44. // Add a process to the ProcessManager and returns its PID.
  45. func (pm *Manager) Add(description string, cmd *exec.Cmd) int64 {
  46. pm.mutex.Lock()
  47. pid := pm.counter + 1
  48. pm.Processes[pid] = &Process{
  49. PID: pid,
  50. Description: description,
  51. Start: time.Now(),
  52. Cmd: cmd,
  53. }
  54. pm.counter = pid
  55. pm.mutex.Unlock()
  56. return pid
  57. }
  58. // Remove a process from the ProcessManager.
  59. func (pm *Manager) Remove(pid int64) {
  60. pm.mutex.Lock()
  61. delete(pm.Processes, pid)
  62. pm.mutex.Unlock()
  63. }
  64. // Exec a command and use the default timeout.
  65. func (pm *Manager) Exec(desc, cmdName string, args ...string) (string, string, error) {
  66. return pm.ExecDir(-1, "", desc, cmdName, args...)
  67. }
  68. // ExecTimeout a command and use a specific timeout duration.
  69. func (pm *Manager) ExecTimeout(timeout time.Duration, desc, cmdName string, args ...string) (string, string, error) {
  70. return pm.ExecDir(timeout, "", desc, cmdName, args...)
  71. }
  72. // ExecDir a command and use the default timeout.
  73. func (pm *Manager) ExecDir(timeout time.Duration, dir, desc, cmdName string, args ...string) (string, string, error) {
  74. return pm.ExecDirEnv(timeout, dir, desc, nil, cmdName, args...)
  75. }
  76. // ExecDirEnv runs a command in given path and environment variables, and waits for its completion
  77. // up to the given timeout (or DefaultTimeout if -1 is given).
  78. // Returns its complete stdout and stderr
  79. // outputs and an error, if any (including timeout)
  80. func (pm *Manager) ExecDirEnv(timeout time.Duration, dir, desc string, env []string, cmdName string, args ...string) (string, string, error) {
  81. if timeout == -1 {
  82. timeout = 60 * time.Second
  83. }
  84. stdOut := new(bytes.Buffer)
  85. stdErr := new(bytes.Buffer)
  86. cmd := exec.Command(cmdName, args...)
  87. cmd.Dir = dir
  88. cmd.Env = env
  89. cmd.Stdout = stdOut
  90. cmd.Stderr = stdErr
  91. if err := cmd.Start(); err != nil {
  92. return "", "", err
  93. }
  94. pid := pm.Add(desc, cmd)
  95. done := make(chan error)
  96. go func() {
  97. done <- cmd.Wait()
  98. }()
  99. var err error
  100. select {
  101. case <-time.After(timeout):
  102. if errKill := pm.Kill(pid); errKill != nil {
  103. log.Error(4, "exec(%d:%s) failed to kill: %v", pid, desc, errKill)
  104. }
  105. <-done
  106. return "", "", ErrExecTimeout
  107. case err = <-done:
  108. }
  109. pm.Remove(pid)
  110. if err != nil {
  111. out := fmt.Errorf("exec(%d:%s) failed: %v stdout: %v stderr: %v", pid, desc, err, stdOut, stdErr)
  112. return stdOut.String(), stdErr.String(), out
  113. }
  114. return stdOut.String(), stdErr.String(), nil
  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. }