summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/web.go1
-rw-r--r--models/pull.go31
-rw-r--r--models/repo.go92
-rw-r--r--models/repo_generate.go14
-rw-r--r--modules/git/blame.go14
-rw-r--r--modules/git/command.go37
-rw-r--r--modules/git/git.go4
-rw-r--r--modules/graceful/context.go90
-rw-r--r--modules/graceful/manager.go108
-rw-r--r--modules/graceful/manager_unix.go68
-rw-r--r--modules/graceful/manager_windows.go35
-rw-r--r--modules/process/manager.go73
-rw-r--r--modules/process/manager_test.go31
-rw-r--r--options/locale/locale_en-US.ini3
-rw-r--r--routers/admin/admin.go11
-rw-r--r--routers/repo/http.go10
-rw-r--r--routers/routes/routes.go1
-rw-r--r--services/gitdiff/gitdiff.go29
-rw-r--r--services/mirror/mirror.go56
-rw-r--r--services/release/release.go11
-rw-r--r--templates/admin/monitor.tmpl13
21 files changed, 535 insertions, 197 deletions
diff --git a/cmd/web.go b/cmd/web.go
index 3c346ef87a..e0e47a181f 100644
--- a/cmd/web.go
+++ b/cmd/web.go
@@ -197,6 +197,7 @@ func runWeb(ctx *cli.Context) error {
log.Info("HTTP Listener: %s Closed", listenAddr)
graceful.Manager.WaitForServers()
graceful.Manager.WaitForTerminate()
+ log.Info("PID: %d Gitea Web Finished", os.Getpid())
log.Close()
return nil
}
diff --git a/models/pull.go b/models/pull.go
index 174faee97a..5b18c9ed10 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/sync"
@@ -536,16 +535,13 @@ func (pr *PullRequest) getMergeCommit() (*git.Commit, error) {
headFile := pr.GetGitRefName()
// Check if a pull request is merged into BaseBranch
- _, stderr, err := process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("isMerged (git merge-base --is-ancestor): %d", pr.BaseRepo.ID),
- []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
- git.GitExecutable, "merge-base", "--is-ancestor", headFile, pr.BaseBranch)
-
+ _, err := git.NewCommand("merge-base", "--is-ancestor", headFile, pr.BaseBranch).RunInDirWithEnv(pr.BaseRepo.RepoPath(), []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()})
if err != nil {
// Errors are signaled by a non-zero status that is not 1
if strings.Contains(err.Error(), "exit status 1") {
return nil, nil
}
- return nil, fmt.Errorf("git merge-base --is-ancestor: %v %v", stderr, err)
+ return nil, fmt.Errorf("git merge-base --is-ancestor: %v", err)
}
commitIDBytes, err := ioutil.ReadFile(pr.BaseRepo.RepoPath() + "/" + headFile)
@@ -559,11 +555,9 @@ func (pr *PullRequest) getMergeCommit() (*git.Commit, error) {
cmd := commitID[:40] + ".." + pr.BaseBranch
// Get the commit from BaseBranch where the pull request got merged
- mergeCommit, stderr, err := process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("isMerged (git rev-list --ancestry-path --merges --reverse): %d", pr.BaseRepo.ID),
- []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
- git.GitExecutable, "rev-list", "--ancestry-path", "--merges", "--reverse", cmd)
+ mergeCommit, err := git.NewCommand("rev-list", "--ancestry-path", "--merges", "--reverse", cmd).RunInDirWithEnv("", []string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()})
if err != nil {
- return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %v %v", stderr, err)
+ return nil, fmt.Errorf("git rev-list --ancestry-path --merges --reverse: %v", err)
} else if len(mergeCommit) < 40 {
// PR was fast-forwarded, so just use last commit of PR
mergeCommit = commitID[:40]
@@ -621,12 +615,9 @@ func (pr *PullRequest) testPatch(e Engine) (err error) {
indexTmpPath := filepath.Join(os.TempDir(), "gitea-"+pr.BaseRepo.Name+"-"+strconv.Itoa(time.Now().Nanosecond()))
defer os.Remove(indexTmpPath)
- var stderr string
- _, stderr, err = process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("testPatch (git read-tree): %d", pr.BaseRepo.ID),
- []string{"GIT_DIR=" + pr.BaseRepo.RepoPath(), "GIT_INDEX_FILE=" + indexTmpPath},
- git.GitExecutable, "read-tree", pr.BaseBranch)
+ _, err = git.NewCommand("read-tree", pr.BaseBranch).RunInDirWithEnv("", []string{"GIT_DIR=" + pr.BaseRepo.RepoPath(), "GIT_INDEX_FILE=" + indexTmpPath})
if err != nil {
- return fmt.Errorf("git read-tree --index-output=%s %s: %v - %s", indexTmpPath, pr.BaseBranch, err, stderr)
+ return fmt.Errorf("git read-tree --index-output=%s %s: %v", indexTmpPath, pr.BaseBranch, err)
}
prUnit, err := pr.BaseRepo.getUnit(e, UnitTypePullRequests)
@@ -642,9 +633,15 @@ func (pr *PullRequest) testPatch(e Engine) (err error) {
args = append(args, patchPath)
pr.ConflictedFiles = []string{}
- _, stderr, err = process.GetManager().ExecDirEnv(-1, "", fmt.Sprintf("testPatch (git apply --check): %d", pr.BaseRepo.ID),
+ stderrBuilder := new(strings.Builder)
+ err = git.NewCommand(args...).RunInDirTimeoutEnvPipeline(
[]string{"GIT_INDEX_FILE=" + indexTmpPath, "GIT_DIR=" + pr.BaseRepo.RepoPath()},
- git.GitExecutable, args...)
+ -1,
+ "",
+ nil,
+ stderrBuilder)
+ stderr := stderrBuilder.String()
+
if err != nil {
for i := range patchConflicts {
if strings.Contains(stderr, patchConflicts[i]) {
diff --git a/models/repo.go b/models/repo.go
index 0ccf786db3..eec9065359 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -30,7 +30,6 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/options"
- "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
api "code.gitea.io/gitea/modules/structs"
@@ -1202,11 +1201,11 @@ func initRepoCommit(tmpPath string, u *User) (err error) {
"GIT_COMMITTER_DATE="+commitTimeStr,
)
- var stderr string
- if _, stderr, err = process.GetManager().ExecDir(-1,
- tmpPath, fmt.Sprintf("initRepoCommit (git add): %s", tmpPath),
- git.GitExecutable, "add", "--all"); err != nil {
- return fmt.Errorf("git add: %s", stderr)
+ if stdout, err := git.NewCommand("add", "--all").
+ SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)).
+ RunInDir(tmpPath); err != nil {
+ log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err)
+ return fmt.Errorf("git add --all: %v", err)
}
binVersion, err := git.BinVersion()
@@ -1228,18 +1227,20 @@ func initRepoCommit(tmpPath string, u *User) (err error) {
}
}
- if _, stderr, err = process.GetManager().ExecDirEnv(-1,
- tmpPath, fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath),
- env,
- git.GitExecutable, args...); err != nil {
- return fmt.Errorf("git commit: %s", stderr)
+ if stdout, err := git.NewCommand(args...).
+ SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
+ RunInDirWithEnv(tmpPath, env); err != nil {
+ log.Error("Failed to commit: %v: Stdout: %s\nError: %v", args, stdout, err)
+ return fmt.Errorf("git commit: %v", err)
}
- if _, stderr, err = process.GetManager().ExecDir(-1,
- tmpPath, fmt.Sprintf("initRepoCommit (git push): %s", tmpPath),
- git.GitExecutable, "push", "origin", "master"); err != nil {
- return fmt.Errorf("git push: %s", stderr)
+ if stdout, err := git.NewCommand("push", "origin", "master").
+ SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)).
+ RunInDir(tmpPath); err != nil {
+ log.Error("Failed to push back to master: Stdout: %s\nError: %v", stdout, err)
+ return fmt.Errorf("git push: %v", err)
}
+
return nil
}
@@ -1297,14 +1298,11 @@ func prepareRepoCommit(e Engine, repo *Repository, tmpDir, repoPath string, opts
)
// Clone to temporary path and do the init commit.
- _, stderr, err := process.GetManager().ExecDirEnv(
- -1, "",
- fmt.Sprintf("initRepository(git clone): %s", repoPath),
- env,
- git.GitExecutable, "clone", repoPath, tmpDir,
- )
- if err != nil {
- return fmt.Errorf("git clone: %v - %s", err, stderr)
+ if stdout, err := git.NewCommand("clone", repoPath, tmpDir).
+ SetDescription(fmt.Sprintf("initRepository (git clone): %s to %s", repoPath, tmpDir)).
+ RunInDirWithEnv("", env); err != nil {
+ log.Error("Failed to clone from %v into %s: stdout: %s\nError: %v", repo, tmpDir, stdout, err)
+ return fmt.Errorf("git clone: %v", err)
}
// README
@@ -1584,11 +1582,11 @@ func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err
}
}
- _, stderr, err := process.GetManager().ExecDir(-1,
- repoPath, fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath),
- git.GitExecutable, "update-server-info")
- if err != nil {
- return nil, errors.New("CreateRepository(git update-server-info): " + stderr)
+ if stdout, err := git.NewCommand("update-server-info").
+ SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
+ RunInDir(repoPath); err != nil {
+ log.Error("CreateRepitory(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
+ return nil, fmt.Errorf("CreateRepository(git update-server-info): %v", err)
}
}
@@ -2422,12 +2420,13 @@ func GitGcRepos() error {
if err := repo.GetOwner(); err != nil {
return err
}
- _, stderr, err := process.GetManager().ExecDir(
- time.Duration(setting.Git.Timeout.GC)*time.Second,
- RepoPath(repo.Owner.Name, repo.Name), "Repository garbage collection",
- git.GitExecutable, args...)
- if err != nil {
- return fmt.Errorf("%v: %v", err, stderr)
+ if stdout, err := git.NewCommand(args...).
+ SetDescription(fmt.Sprintf("Repository Garbage Collection: %s", repo.FullName())).
+ RunInDirTimeout(
+ time.Duration(setting.Git.Timeout.GC)*time.Second,
+ RepoPath(repo.Owner.Name, repo.Name)); err != nil {
+ log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
+ return fmt.Errorf("Repository garbage collection failed: Error: %v", err)
}
return nil
})
@@ -2647,18 +2646,19 @@ func ForkRepository(doer, owner *User, oldRepo *Repository, name, desc string) (
}
repoPath := RepoPath(owner.Name, repo.Name)
- _, stderr, err := process.GetManager().ExecTimeout(10*time.Minute,
- fmt.Sprintf("ForkRepository(git clone): %s/%s", owner.Name, repo.Name),
- git.GitExecutable, "clone", "--bare", oldRepo.repoPath(sess), repoPath)
- if err != nil {
- return nil, fmt.Errorf("git clone: %v", stderr)
- }
-
- _, stderr, err = process.GetManager().ExecDir(-1,
- repoPath, fmt.Sprintf("ForkRepository(git update-server-info): %s", repoPath),
- git.GitExecutable, "update-server-info")
- if err != nil {
- return nil, fmt.Errorf("git update-server-info: %v", stderr)
+ if stdout, err := git.NewCommand(
+ "clone", "--bare", oldRepo.repoPath(sess), repoPath).
+ SetDescription(fmt.Sprintf("ForkRepository(git clone): %s to %s", oldRepo.FullName(), repo.FullName())).
+ RunInDirTimeout(10*time.Minute, ""); err != nil {
+ log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, oldRepo, stdout, err)
+ return nil, fmt.Errorf("git clone: %v", err)
+ }
+
+ if stdout, err := git.NewCommand("update-server-info").
+ SetDescription(fmt.Sprintf("ForkRepository(git update-server-info): %s", repo.FullName())).
+ RunInDir(repoPath); err != nil {
+ log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err)
+ return nil, fmt.Errorf("git update-server-info: %v", err)
}
if err = createDelegateHooks(repoPath); err != nil {
diff --git a/models/repo_generate.go b/models/repo_generate.go
index 56a3940ac1..6dd8540d9e 100644
--- a/models/repo_generate.go
+++ b/models/repo_generate.go
@@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
- "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/util"
"github.com/gobwas/glob"
@@ -168,14 +167,11 @@ func generateRepoCommit(e Engine, repo, templateRepo, generateRepo *Repository,
}
repoPath := repo.repoPath(e)
- _, stderr, err := process.GetManager().ExecDirEnv(
- -1, tmpDir,
- fmt.Sprintf("generateRepoCommit(git remote add): %s", repoPath),
- env,
- git.GitExecutable, "remote", "add", "origin", repoPath,
- )
- if err != nil {
- return fmt.Errorf("git remote add: %v - %s", err, stderr)
+ if stdout, err := git.NewCommand("remote", "add", "origin", repoPath).
+ SetDescription(fmt.Sprintf("generateRepoCommit (git remote add): %s to %s", templateRepoPath, tmpDir)).
+ RunInDirWithEnv(tmpDir, env); err != nil {
+ log.Error("Unable to add %v as remote origin to temporary repo to %s: stdout %s\nError: %v", repo, tmpDir, stdout, err)
+ return fmt.Errorf("git remote add: %v", err)
}
return initRepoCommit(tmpDir, repo.Owner)
diff --git a/modules/git/blame.go b/modules/git/blame.go
index 4f4343fe96..5a9ae9a74f 100644
--- a/modules/git/blame.go
+++ b/modules/git/blame.go
@@ -6,6 +6,7 @@ package git
import (
"bufio"
+ "context"
"fmt"
"io"
"os"
@@ -28,6 +29,7 @@ type BlameReader struct {
output io.ReadCloser
scanner *bufio.Scanner
lastSha *string
+ cancel context.CancelFunc
}
var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})")
@@ -76,7 +78,8 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
// Close BlameReader - don't run NextPart after invoking that
func (r *BlameReader) Close() error {
- process.GetManager().Remove(r.pid)
+ defer process.GetManager().Remove(r.pid)
+ defer r.cancel()
if err := r.cmd.Wait(); err != nil {
return fmt.Errorf("Wait: %v", err)
@@ -97,20 +100,24 @@ func CreateBlameReader(repoPath, commitID, file string) (*BlameReader, error) {
}
func createBlameReader(dir string, command ...string) (*BlameReader, error) {
- cmd := exec.Command(command[0], command[1:]...)
+ // FIXME: graceful: This should have a timeout
+ ctx, cancel := context.WithCancel(DefaultContext)
+ cmd := exec.CommandContext(ctx, command[0], command[1:]...)
cmd.Dir = dir
cmd.Stderr = os.Stderr
stdout, err := cmd.StdoutPipe()
if err != nil {
+ defer cancel()
return nil, fmt.Errorf("StdoutPipe: %v", err)
}
if err = cmd.Start(); err != nil {
+ defer cancel()
return nil, fmt.Errorf("Start: %v", err)
}
- pid := process.GetManager().Add(fmt.Sprintf("GetBlame [repo_path: %s]", dir), cmd)
+ pid := process.GetManager().Add(fmt.Sprintf("GetBlame [repo_path: %s]", dir), cancel)
scanner := bufio.NewScanner(stdout)
@@ -120,5 +127,6 @@ func createBlameReader(dir string, command ...string) (*BlameReader, error) {
stdout,
scanner,
nil,
+ cancel,
}, nil
}
diff --git a/modules/git/command.go b/modules/git/command.go
index 65878edb7d..f01db2e1d8 100644
--- a/modules/git/command.go
+++ b/modules/git/command.go
@@ -30,8 +30,10 @@ const DefaultLocale = "C"
// Command represents a command with its subcommands or arguments.
type Command struct {
- name string
- args []string
+ name string
+ args []string
+ parentContext context.Context
+ desc string
}
func (c *Command) String() string {
@@ -47,19 +49,34 @@ func NewCommand(args ...string) *Command {
cargs := make([]string, len(GlobalCommandArgs))
copy(cargs, GlobalCommandArgs)
return &Command{
- name: GitExecutable,
- args: append(cargs, args...),
+ name: GitExecutable,
+ args: append(cargs, args...),
+ parentContext: DefaultContext,
}
}
// NewCommandNoGlobals creates and returns a new Git Command based on given command and arguments only with the specify args and don't care global command args
func NewCommandNoGlobals(args ...string) *Command {
return &Command{
- name: GitExecutable,
- args: args,
+ name: GitExecutable,
+ args: args,
+ parentContext: DefaultContext,
}
}
+// SetParentContext sets the parent context for this command
+func (c *Command) SetParentContext(ctx context.Context) *Command {
+ c.parentContext = ctx
+ return c
+}
+
+// SetDescription sets the description for this command which be returned on
+// c.String()
+func (c *Command) SetDescription(desc string) *Command {
+ c.desc = desc
+ return c
+}
+
// AddArguments adds new argument(s) to the command.
func (c *Command) AddArguments(args ...string) *Command {
c.args = append(c.args, args...)
@@ -92,7 +109,7 @@ func (c *Command) RunInDirTimeoutEnvFullPipelineFunc(env []string, timeout time.
log("%s: %v", dir, c)
}
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ ctx, cancel := context.WithTimeout(c.parentContext, timeout)
defer cancel()
cmd := exec.CommandContext(ctx, c.name, c.args...)
@@ -110,7 +127,11 @@ func (c *Command) RunInDirTimeoutEnvFullPipelineFunc(env []string, timeout time.
return err
}
- pid := process.GetManager().Add(fmt.Sprintf("%s %s %s [repo_path: %s]", GitExecutable, c.name, strings.Join(c.args, " "), dir), cmd)
+ desc := c.desc
+ if desc == "" {
+ desc = fmt.Sprintf("%s %s %s [repo_path: %s]", GitExecutable, c.name, strings.Join(c.args, " "), dir)
+ }
+ pid := process.GetManager().Add(desc, cancel)
defer process.GetManager().Remove(pid)
if fn != nil {
diff --git a/modules/git/git.go b/modules/git/git.go
index df50eac72a..286e1ad8b4 100644
--- a/modules/git/git.go
+++ b/modules/git/git.go
@@ -6,6 +6,7 @@
package git
import (
+ "context"
"fmt"
"os/exec"
"runtime"
@@ -35,6 +36,9 @@ var (
// Could be updated to an absolute path while initialization
GitExecutable = "git"
+ // DefaultContext is the default context to run git commands in
+ DefaultContext = context.Background()
+
gitVersion string
)
diff --git a/modules/graceful/context.go b/modules/graceful/context.go
new file mode 100644
index 0000000000..a4a4df7dea
--- /dev/null
+++ b/modules/graceful/context.go
@@ -0,0 +1,90 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package graceful
+
+import (
+ "context"
+ "fmt"
+ "time"
+)
+
+// Errors for context.Err()
+var (
+ ErrShutdown = fmt.Errorf("Graceful Manager called Shutdown")
+ ErrHammer = fmt.Errorf("Graceful Manager called Hammer")
+ ErrTerminate = fmt.Errorf("Graceful Manager called Terminate")
+)
+
+// ChannelContext is a context that wraps a channel and error as a context
+type ChannelContext struct {
+ done <-chan struct{}
+ err error
+}
+
+// NewChannelContext creates a ChannelContext from a channel and error
+func NewChannelContext(done <-chan struct{}, err error) *ChannelContext {
+ return &ChannelContext{
+ done: done,
+ err: err,
+ }
+}
+
+// Deadline returns the time when work done on behalf of this context
+// should be canceled. There is no Deadline for a ChannelContext
+func (ctx *ChannelContext) Deadline() (deadline time.Time, ok bool) {
+ return
+}
+
+// Done returns the channel provided at the creation of this context.
+// When closed, work done on behalf of this context should be canceled.
+func (ctx *ChannelContext) Done() <-chan struct{} {
+ return ctx.done
+}
+
+// Err returns nil, if Done is not closed. If Done is closed,
+// Err returns the error provided at the creation of this context
+func (ctx *ChannelContext) Err() error {
+ select {
+ case <-ctx.done:
+ return ctx.err
+ default:
+ return nil
+ }
+}
+
+// Value returns nil for all calls as no values are or can be associated with this context
+func (ctx *ChannelContext) Value(key interface{}) interface{} {
+ return nil
+}
+
+// ShutdownContext returns a context.Context that is Done at shutdown
+// Callers using this context should ensure that they are registered as a running server
+// in order that they are waited for.
+func (g *gracefulManager) ShutdownContext() context.Context {
+ return &ChannelContext{
+ done: g.IsShutdown(),
+ err: ErrShutdown,
+ }
+}
+
+// HammerContext returns a context.Context that is Done at hammer
+// Callers using this context should ensure that they are registered as a running server
+// in order that they are waited for.
+func (g *gracefulManager) HammerContext() context.Context {
+ return &ChannelContext{
+ done: g.IsHammer(),
+ err: ErrHammer,
+ }
+}
+
+// TerminateContext returns a context.Context that is Done at terminate
+// Callers using this context should ensure that they are registered as a terminating server
+// in order that they are waited for.
+func (g *gracefulManager) TerminateContext() context.Context {
+ return &ChannelContext{
+ done: g.IsTerminate(),
+ err: ErrTerminate,
+ }
+}
diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go
index 48f76635ff..b9a56ca9c6 100644
--- a/modules/graceful/manager.go
+++ b/modules/graceful/manager.go
@@ -5,9 +5,12 @@
package graceful
import (
+ "context"
"time"
+ "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
)
@@ -34,9 +37,110 @@ const numberOfServersToCreate = 3
var Manager *gracefulManager
func init() {
- Manager = newGracefulManager()
+ Manager = newGracefulManager(context.Background())
+ // Set the git default context to the HammerContext
+ git.DefaultContext = Manager.HammerContext()
+ // Set the process default context to the HammerContext
+ process.DefaultContext = Manager.HammerContext()
}
+// CallbackWithContext is combined runnable and context to watch to see if the caller has finished
+type CallbackWithContext func(ctx context.Context, callback func())
+
+// RunnableWithShutdownFns is a runnable with functions to run at shutdown and terminate
+// After the callback to atShutdown is called and is complete, the main function must return.
+// Similarly the callback function provided to atTerminate must return once termination is complete.
+// Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals
+// - users must therefore be careful to only call these as necessary.
+// If run is not expected to run indefinitely RunWithShutdownChan is likely to be more appropriate.
+type RunnableWithShutdownFns func(atShutdown, atTerminate func(context.Context, func()))
+
+// RunWithShutdownFns takes a function that has both atShutdown and atTerminate callbacks
+// After the callback to atShutdown is called and is complete, the main function must return.
+// Similarly the callback function provided to atTerminate must return once termination is complete.
+// Please note that use of the atShutdown and atTerminate callbacks will create go-routines that will wait till their respective signals
+// - users must therefore be careful to only call these as necessary.
+// If run is not expected to run indefinitely RunWithShutdownChan is likely to be more appropriate.
+func (g *gracefulManager) RunWithShutdownFns(run RunnableWithShutdownFns) {
+ g.runningServerWaitGroup.Add(1)
+ defer g.runningServerWaitGroup.Done()
+ run(func(ctx context.Context, atShutdown func()) {
+ go func() {
+ select {
+ case <-g.IsShutdown():
+ atShutdown()
+ case <-ctx.Done():
+ return
+ }
+ }()
+ }, func(ctx context.Context, atTerminate func()) {
+ g.RunAtTerminate(ctx, atTerminate)
+ })
+}
+
+// RunnableWithShutdownChan is a runnable with functions to run at shutdown and terminate.
+// After the atShutdown channel is closed, the main function must return once shutdown is complete.
+// (Optionally IsHammer may be waited for instead however, this should be avoided if possible.)
+// The callback function provided to atTerminate must return once termination is complete.
+// Please note that use of the atTerminate function will create a go-routine that will wait till terminate - users must therefore be careful to only call this as necessary.
+type RunnableWithShutdownChan func(atShutdown <-chan struct{}, atTerminate CallbackWithContext)
+
+// RunWithShutdownChan takes a function that has channel to watch for shutdown and atTerminate callbacks
+// After the atShutdown channel is closed, the main function must return once shutdown is complete.
+// (Optionally IsHammer may be waited for instead however, this should be avoided if possible.)
+// The callback function provided to atTerminate must return once termination is complete.
+// Please note that use of the atTerminate function will create a go-routine that will wait till terminate - users must therefore be careful to only call this as necessary.
+func (g *gracefulManager) RunWithShutdownChan(run RunnableWithShutdownChan) {
+ g.runningServerWaitGroup.Add(1)
+ defer g.runningServerWaitGroup.Done()
+ run(g.IsShutdown(), func(ctx context.Context, atTerminate func()) {
+ g.RunAtTerminate(ctx, atTerminate)
+ })
+}
+
+// RunWithShutdownContext takes a function that has a context to watch for shutdown.
+// After the provided context is Done(), the main function must return once shutdown is complete.
+// (Optionally the HammerContext may be obtained and waited for however, this should be avoided if possible.)
+func (g *gracefulManager) RunWithShutdownContext(run func(context.Context)) {
+ g.runningServerWaitGroup.Add(1)
+ defer g.runningServerWaitGroup.Done()
+ run(g.ShutdownContext())
+}
+
+// RunAtTerminate adds to the terminate wait group and creates a go-routine to run the provided function at termination
+func (g *gracefulManager) RunAtTerminate(ctx context.Context, terminate func()) {
+ g.terminateWaitGroup.Add(1)
+ go func() {
+ select {
+ case <-g.IsTerminate():
+ terminate()
+ case <-ctx.Done():
+ }
+ g.terminateWaitGroup.Done()
+ }()
+}
+
+// RunAtShutdown creates a go-routine to run the provided function at shutdown
+func (g *gracefulManager) RunAtShutdown(ctx context.Context, shutdown func()) {
+ go func() {
+ select {
+ case <-g.IsShutdown():
+ shutdown()
+ case <-ctx.Done():
+ }
+ }()
+}
+
+// RunAtHammer creates a go-routine to run the provided function at shutdown
+func (g *gracefulManager) RunAtHammer(ctx context.Context, hammer func()) {
+ go func() {
+ select {
+ case <-g.IsHammer():
+ hammer()
+ case <-ctx.Done():
+ }
+ }()
+}
func (g *gracefulManager) doShutdown() {
if !g.setStateTransition(stateRunning, stateShuttingDown) {
return
@@ -50,6 +154,8 @@ func (g *gracefulManager) doShutdown() {
}
go func() {
g.WaitForServers()
+ // Mop up any remaining unclosed events.
+ g.doHammerTime(0)
<-time.After(1 * time.Second)
g.doTerminate()
}()
diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go
index 15b0ff4448..1ffc59f0df 100644
--- a/modules/graceful/manager_unix.go
+++ b/modules/graceful/manager_unix.go
@@ -7,6 +7,7 @@
package graceful
import (
+ "context"
"errors"
"os"
"os/signal"
@@ -31,19 +32,19 @@ type gracefulManager struct {
terminateWaitGroup sync.WaitGroup
}
-func newGracefulManager() *gracefulManager {
+func newGracefulManager(ctx context.Context) *gracefulManager {
manager := &gracefulManager{
isChild: len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1,
lock: &sync.RWMutex{},
}
manager.createServerWaitGroup.Add(numberOfServersToCreate)
- manager.Run()
+ manager.Run(ctx)
return manager
}
-func (g *gracefulManager) Run() {
+func (g *gracefulManager) Run(ctx context.Context) {
g.setState(stateRunning)
- go g.handleSignals()
+ go g.handleSignals(ctx)
c := make(chan struct{})
go func() {
defer close(c)
@@ -69,9 +70,7 @@ func (g *gracefulManager) Run() {
}
}
-func (g *gracefulManager) handleSignals() {
- var sig os.Signal
-
+func (g *gracefulManager) handleSignals(ctx context.Context) {
signalChannel := make(chan os.Signal, 1)
signal.Notify(
@@ -86,35 +85,40 @@ func (g *gracefulManager) handleSignals() {
pid := syscall.Getpid()
for {
- sig = <-signalChannel
- switch sig {
- case syscall.SIGHUP:
- if setting.GracefulRestartable {
- log.Info("PID: %d. Received SIGHUP. Forking...", pid)
- err := g.doFork()
- if err != nil && err.Error() != "another process already forked. Ignoring this one" {
- log.Error("Error whilst forking from PID: %d : %v", pid, err)
+ select {
+ case sig := <-signalChannel:
+ switch sig {
+ case syscall.SIGHUP:
+ if setting.GracefulRestartable {
+ log.Info("PID: %d. Received SIGHUP. Forking...", pid)
+ err := g.doFork()
+ if err != nil && err.Error() != "another process already forked. Ignoring this one" {
+ log.Error("Error whilst forking from PID: %d : %v", pid, err)
+ }
+ } else {
+ log.Info("PID: %d. Received SIGHUP. Not set restartable. Shutting down...", pid)
+
+ g.doShutdown()
}
- } else {
- log.Info("PID: %d. Received SIGHUP. Not set restartable. Shutting down...", pid)
-
+ case syscall.SIGUSR1:
+ log.Info("PID %d. Received SIGUSR1.", pid)
+ case syscall.SIGUSR2:
+ log.Warn("PID %d. Received SIGUSR2. Hammering...", pid)
+ g.doHammerTime(0 * time.Second)
+ case syscall.SIGINT:
+ log.Warn("PID %d. Received SIGINT. Shutting down...", pid)
g.doShutdown()
+ case syscall.SIGTERM:
+ log.Warn("PID %d. Received SIGTERM. Shutting down...", pid)
+ g.doShutdown()
+ case syscall.SIGTSTP:
+ log.Info("PID %d. Received SIGTSTP.", pid)
+ default:
+ log.Info("PID %d. Received %v.", pid, sig)
}
- case syscall.SIGUSR1:
- log.Info("PID %d. Received SIGUSR1.", pid)
- case syscall.SIGUSR2:
- log.Warn("PID %d. Received SIGUSR2. Hammering...", pid)
- g.doHammerTime(0 * time.Second)
- case syscall.SIGINT:
- log.Warn("PID %d. Received SIGINT. Shutting down...", pid)
- g.doShutdown()
- case syscall.SIGTERM:
- log.Warn("PID %d. Received SIGTERM. Shutting down...", pid)
+ case <-ctx.Done():
+ log.Warn("PID: %d. Background context for manager closed - %v - Shutting down...", pid, ctx.Err())
g.doShutdown()
- case syscall.SIGTSTP:
- log.Info("PID %d. Received SIGTSTP.", pid)
- default:
- log.Info("PID %d. Received %v.", pid, sig)
}
}
}
diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go
index 925b1fc560..dd48a8d74c 100644
--- a/modules/graceful/manager_windows.go
+++ b/modules/graceful/manager_windows.go
@@ -8,6 +8,7 @@
package graceful
import (
+ "context"
"os"
"strconv"
"sync"
@@ -29,6 +30,7 @@ const (
)
type gracefulManager struct {
+ ctx context.Context
isChild bool
lock *sync.RWMutex
state state
@@ -40,10 +42,11 @@ type gracefulManager struct {
terminateWaitGroup sync.WaitGroup
}
-func newGracefulManager() *gracefulManager {
+func newGracefulManager(ctx context.Context) *gracefulManager {
manager := &gracefulManager{
isChild: false,
lock: &sync.RWMutex{},
+ ctx: ctx,
}
manager.createServerWaitGroup.Add(numberOfServersToCreate)
manager.Run()
@@ -89,23 +92,29 @@ func (g *gracefulManager) Execute(args []string, changes <-chan svc.ChangeReques
waitTime := 30 * time.Second
loop:
- for change := range changes {
- switch change.Cmd {
- case svc.Interrogate:
- status <- change.CurrentStatus
- case svc.Stop, svc.Shutdown:
+ for {
+ select {
+ case <-g.ctx.Done():
g.doShutdown()
waitTime += setting.GracefulHammerTime
break loop
- case hammerCode:
- g.doShutdown()
- g.doHammerTime(0 * time.Second)
- break loop
- default:
- log.Debug("Unexpected control request: %v", change.Cmd)
+ case change := <-changes:
+ switch change.Cmd {
+ case svc.Interrogate:
+ status <- change.CurrentStatus
+ case svc.Stop, svc.Shutdown:
+ g.doShutdown()
+ waitTime += setting.GracefulHammerTime
+ break loop
+ case hammerCode:
+ g.doShutdown()
+ g.doHammerTime(0 * time.Second)
+ break loop
+ default:
+ log.Debug("Unexpected control request: %v", change.Cmd)
+ }
}
}
-
status <- svc.Status{
State: svc.StopPending,
WaitHint: uint32(waitTime / time.Millisecond),
diff --git a/modules/process/manager.go b/modules/process/manager.go
index 3e77c0a6a9..af6ee9b81d 100644
--- a/modules/process/manager.go
+++ b/modules/process/manager.go
@@ -12,6 +12,7 @@ import (
"fmt"
"io"
"os/exec"
+ "sort"
"sync"
"time"
)
@@ -24,14 +25,17 @@ var (
// ErrExecTimeout represent a timeout error
ErrExecTimeout = errors.New("Process execution timeout")
manager *Manager
+
+ // DefaultContext is the default context to run processing commands in
+ DefaultContext = context.Background()
)
-// Process represents a working process inherit from Gogs.
+// Process represents a working process inheriting from Gitea.
type Process struct {
PID int64 // Process ID, not system one.
Description string
Start time.Time
- Cmd *exec.Cmd
+ Cancel context.CancelFunc
}
// Manager knows about all processes and counts PIDs.
@@ -39,28 +43,28 @@ type Manager struct {
mutex sync.Mutex
counter int64
- Processes map[int64]*Process
+ processes map[int64]*Process
}
// GetManager returns a Manager and initializes one as singleton if there's none yet
func GetManager() *Manager {
if manager == nil {
manager = &Manager{
- Processes: make(map[int64]*Process),
+ processes: make(map[int64]*Process),
}
}
return manager
}
// Add a process to the ProcessManager and returns its PID.
-func (pm *Manager) Add(description string, cmd *exec.Cmd) int64 {
+func (pm *Manager) Add(description string, cancel context.CancelFunc) int64 {
pm.mutex.Lock()
pid := pm.counter + 1
- pm.Processes[pid] = &Process{
+ pm.processes[pid] = &Process{
PID: pid,
Description: description,
Start: time.Now(),
- Cmd: cmd,
+ Cancel: cancel,
}
pm.counter = pid
pm.mutex.Unlock()
@@ -71,10 +75,32 @@ func (pm *Manager) Add(description string, cmd *exec.Cmd) int64 {
// Remove a process from the ProcessManager.
func (pm *Manager) Remove(pid int64) {
pm.mutex.Lock()
- delete(pm.Processes, pid)
+ delete(pm.processes, pid)
pm.mutex.Unlock()
}
+// Cancel a process in the ProcessManager.
+func (pm *Manager) Cancel(pid int64) {
+ pm.mutex.Lock()
+ process, ok := pm.processes[pid]
+ pm.mutex.Unlock()
+ if ok {
+ process.Cancel()
+ }
+}
+
+// Processes gets the processes in a thread safe manner
+func (pm *Manager) Processes() []*Process {
+ pm.mutex.Lock()
+ processes := make([]*Process, 0, len(pm.processes))
+ for _, process := range pm.processes {
+ processes = append(processes, process)
+ }
+ pm.mutex.Unlock()
+ sort.Sort(processList(processes))
+ return processes
+}
+
// Exec a command and use the default timeout.
func (pm *Manager) Exec(desc, cmdName string, args ...string) (string, string, error) {
return pm.ExecDir(-1, "", desc, cmdName, args...)
@@ -110,7 +136,7 @@ func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env
stdOut := new(bytes.Buffer)
stdErr := new(bytes.Buffer)
- ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ ctx, cancel := context.WithTimeout(DefaultContext, timeout)
defer cancel()
cmd := exec.CommandContext(ctx, cmdName, args...)
@@ -126,7 +152,7 @@ func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env
return "", "", err
}
- pid := pm.Add(desc, cmd)
+ pid := pm.Add(desc, cancel)
err := cmd.Wait()
pm.Remove(pid)
@@ -137,21 +163,16 @@ func (pm *Manager) ExecDirEnvStdIn(timeout time.Duration, dir, desc string, env
return stdOut.String(), stdErr.String(), err
}
-// Kill and remove a process from list.
-func (pm *Manager) Kill(pid int64) error {
- if proc, exists := pm.Processes[pid]; exists {
- pm.mutex.Lock()
- if proc.Cmd != nil &&
- proc.Cmd.Process != nil &&
- proc.Cmd.ProcessState != nil &&
- !proc.Cmd.ProcessState.Exited() {
- if err := proc.Cmd.Process.Kill(); err != nil {
- return fmt.Errorf("failed to kill process(%d/%s): %v", pid, proc.Description, err)
- }
- }
- delete(pm.Processes, pid)
- pm.mutex.Unlock()
- }
+type processList []*Process
+
+func (l processList) Len() int {
+ return len(l)
+}
+
+func (l processList) Less(i, j int) bool {
+ return l[i].PID < l[j].PID
+}
- return nil
+func (l processList) Swap(i, j int) {
+ l[i], l[j] = l[j], l[i]
}
diff --git a/modules/process/manager_test.go b/modules/process/manager_test.go
index 9980aba921..b18f76f944 100644
--- a/modules/process/manager_test.go
+++ b/modules/process/manager_test.go
@@ -1,7 +1,7 @@
package process
import (
- "os/exec"
+ "context"
"testing"
"time"
@@ -9,27 +9,42 @@ import (
)
func TestManager_Add(t *testing.T) {
- pm := Manager{Processes: make(map[int64]*Process)}
+ pm := Manager{processes: make(map[int64]*Process)}
- pid := pm.Add("foo", exec.Command("foo"))
+ pid := pm.Add("foo", nil)
assert.Equal(t, int64(1), pid, "expected to get pid 1 got %d", pid)
- pid = pm.Add("bar", exec.Command("bar"))
+ pid = pm.Add("bar", nil)
assert.Equal(t, int64(2), pid, "expected to get pid 2 got %d", pid)
}
+func TestManager_Cancel(t *testing.T) {
+ pm := Manager{processes: make(map[int64]*Process)}
+
+ ctx, cancel := context.WithCancel(context.Background())
+ pid := pm.Add("foo", cancel)
+
+ pm.Cancel(pid)
+
+ select {
+ case <-ctx.Done():
+ default:
+ assert.Fail(t, "Cancel should cancel the provided context")
+ }
+}
+
func TestManager_Remove(t *testing.T) {
- pm := Manager{Processes: make(map[int64]*Process)}
+ pm := Manager{processes: make(map[int64]*Process)}
- pid1 := pm.Add("foo", exec.Command("foo"))
+ pid1 := pm.Add("foo", nil)
assert.Equal(t, int64(1), pid1, "expected to get pid 1 got %d", pid1)
- pid2 := pm.Add("bar", exec.Command("bar"))
+ pid2 := pm.Add("bar", nil)
assert.Equal(t, int64(2), pid2, "expected to get pid 2 got %d", pid2)
pm.Remove(pid2)
- _, exists := pm.Processes[pid2]
+ _, exists := pm.processes[pid2]
assert.False(t, exists, "PID %d is in the list but shouldn't", pid2)
}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index be4522014f..ae93026bdd 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1999,6 +1999,9 @@ monitor.process = Running Processes
monitor.desc = Description
monitor.start = Start Time
monitor.execute_time = Execution Time
+monitor.process.cancel = Cancel process
+monitor.process.cancel_desc = Cancelling a process may cause data loss
+monitor.process.cancel_notices = Cancel: <strong>%s</strong>?
notices.system_notice_list = System Notices
notices.view_detail_header = View Notice Details
diff --git a/routers/admin/admin.go b/routers/admin/admin.go
index 45bdbfe7f2..9f155ff008 100644
--- a/routers/admin/admin.go
+++ b/routers/admin/admin.go
@@ -352,7 +352,16 @@ func Monitor(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.monitor")
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminMonitor"] = true
- ctx.Data["Processes"] = process.GetManager().Processes
+ ctx.Data["Processes"] = process.GetManager().Processes()
ctx.Data["Entries"] = cron.ListTasks()
ctx.HTML(200, tplMonitor)
}
+
+// MonitorCancel cancels a process
+func MonitorCancel(ctx *context.Context) {
+ pid := ctx.ParamsInt64("pid")
+ process.GetManager().Cancel(pid)
+ ctx.JSON(200, map[string]interface{}{
+ "redirect": ctx.Repo.RepoLink + "/admin/monitor",
+ })
+}
diff --git a/routers/repo/http.go b/routers/repo/http.go
index 0025ba2af4..c66d7aae65 100644
--- a/routers/repo/http.go
+++ b/routers/repo/http.go
@@ -8,6 +8,7 @@ package repo
import (
"bytes"
"compress/gzip"
+ gocontext "context"
"fmt"
"net/http"
"os"
@@ -24,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
)
@@ -463,8 +465,10 @@ func serviceRPC(h serviceHandler, service string) {
// set this for allow pre-receive and post-receive execute
h.environ = append(h.environ, "SSH_ORIGINAL_COMMAND="+service)
+ ctx, cancel := gocontext.WithCancel(git.DefaultContext)
+ defer cancel()
var stderr bytes.Buffer
- cmd := exec.Command(git.GitExecutable, service, "--stateless-rpc", h.dir)
+ cmd := exec.CommandContext(ctx, git.GitExecutable, service, "--stateless-rpc", h.dir)
cmd.Dir = h.dir
if service == "receive-pack" {
cmd.Env = append(os.Environ(), h.environ...)
@@ -472,6 +476,10 @@ func serviceRPC(h serviceHandler, service string) {
cmd.Stdout = h.w
cmd.Stdin = reqBody
cmd.Stderr = &stderr
+
+ pid := process.GetManager().Add(fmt.Sprintf("%s %s %s [repo_path: %s]", git.GitExecutable, service, "--stateless-rpc", h.dir), cancel)
+ defer process.GetManager().Remove(pid)
+
if err := cmd.Run(); err != nil {
log.Error("Fail to serve RPC(%s): %v - %s", service, err, stderr.String())
return
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index 6de293c507..bac7d37916 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -422,6 +422,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/config", admin.Config)
m.Post("/config/test_mail", admin.SendTestMail)
m.Get("/monitor", admin.Monitor)
+ m.Post("/monitor/cancel/:pid", admin.MonitorCancel)
m.Group("/users", func() {
m.Get("", admin.Users)
diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go
index 351bfef156..b8efa8a43f 100644
--- a/services/gitdiff/gitdiff.go
+++ b/services/gitdiff/gitdiff.go
@@ -8,6 +8,7 @@ package gitdiff
import (
"bufio"
"bytes"
+ "context"
"fmt"
"html"
"html/template"
@@ -825,9 +826,12 @@ func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID
return nil, err
}
+ // FIXME: graceful: These commands should likely have a timeout
+ ctx, cancel := context.WithCancel(git.DefaultContext)
+ defer cancel()
var cmd *exec.Cmd
if len(beforeCommitID) == 0 && commit.ParentCount() == 0 {
- cmd = exec.Command(git.GitExecutable, "show", afterCommitID)
+ cmd = exec.CommandContext(ctx, git.GitExecutable, "show", afterCommitID)
} else {
actualBeforeCommitID := beforeCommitID
if len(actualBeforeCommitID) == 0 {
@@ -840,7 +844,7 @@ func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID
}
diffArgs = append(diffArgs, actualBeforeCommitID)
diffArgs = append(diffArgs, afterCommitID)
- cmd = exec.Command(git.GitExecutable, diffArgs...)
+ cmd = exec.CommandContext(ctx, git.GitExecutable, diffArgs...)
beforeCommitID = actualBeforeCommitID
}
cmd.Dir = repoPath
@@ -855,7 +859,7 @@ func GetDiffRangeWithWhitespaceBehavior(repoPath, beforeCommitID, afterCommitID
return nil, fmt.Errorf("Start: %v", err)
}
- pid := process.GetManager().Add(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath), cmd)
+ pid := process.GetManager().Add(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath), cancel)
defer process.GetManager().Remove(pid)
diff, err := ParsePatch(maxLines, maxLineCharacters, maxFiles, stdout)
@@ -908,27 +912,31 @@ func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiff
if len(file) > 0 {
fileArgs = append(fileArgs, "--", file)
}
+ // FIXME: graceful: These commands should have a timeout
+ ctx, cancel := context.WithCancel(git.DefaultContext)
+ defer cancel()
+
var cmd *exec.Cmd
switch diffType {
case RawDiffNormal:
if len(startCommit) != 0 {
- cmd = exec.Command(git.GitExecutable, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
+ cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"diff", "-M", startCommit, endCommit}, fileArgs...)...)
} else if commit.ParentCount() == 0 {
- cmd = exec.Command(git.GitExecutable, append([]string{"show", endCommit}, fileArgs...)...)
+ cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"show", endCommit}, fileArgs...)...)
} else {
c, _ := commit.Parent(0)
- cmd = exec.Command(git.GitExecutable, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
+ cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"diff", "-M", c.ID.String(), endCommit}, fileArgs...)...)
}
case RawDiffPatch:
if len(startCommit) != 0 {
query := fmt.Sprintf("%s...%s", endCommit, startCommit)
- cmd = exec.Command(git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
+ cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", query}, fileArgs...)...)
} else if commit.ParentCount() == 0 {
- cmd = exec.Command(git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
+ cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", "--root", endCommit}, fileArgs...)...)
} else {
c, _ := commit.Parent(0)
query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
- cmd = exec.Command(git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
+ cmd = exec.CommandContext(ctx, git.GitExecutable, append([]string{"format-patch", "--no-signature", "--stdout", query}, fileArgs...)...)
}
default:
return fmt.Errorf("invalid diffType: %s", diffType)
@@ -939,6 +947,9 @@ func GetRawDiffForFile(repoPath, startCommit, endCommit string, diffType RawDiff
cmd.Dir = repoPath
cmd.Stdout = writer
cmd.Stderr = stderr
+ pid := process.GetManager().Add(fmt.Sprintf("GetRawDiffForFile: [repo_path: %s]", repoPath), cancel)
+ defer process.GetManager().Remove(pid)
+
if err = cmd.Run(); err != nil {
return fmt.Errorf("Run: %v - %s", err, stderr)
}
diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go
index d35a205d00..9c52f1723b 100644
--- a/services/mirror/mirror.go
+++ b/services/mirror/mirror.go
@@ -15,7 +15,6 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
- "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/timeutil"
@@ -172,25 +171,36 @@ func runSync(m *models.Mirror) ([]*mirrorSyncResult, bool) {
gitArgs = append(gitArgs, "--prune")
}
- _, stderr, err := process.GetManager().ExecDir(
- timeout, repoPath, fmt.Sprintf("Mirror.runSync: %s", repoPath),
- git.GitExecutable, gitArgs...)
- if err != nil {
+ stdoutBuilder := strings.Builder{}
+ stderrBuilder := strings.Builder{}
+ if err := git.NewCommand(gitArgs...).
+ SetDescription(fmt.Sprintf("Mirror.runSync: %s", m.Repo.FullName())).
+ RunInDirTimeoutPipeline(timeout, repoPath, &stdoutBuilder, &stderrBuilder); err != nil {
+ stdout := stdoutBuilder.String()
+ stderr := stderrBuilder.String()
// sanitize the output, since it may contain the remote address, which may
// contain a password
- message, err := sanitizeOutput(stderr, repoPath)
+ stderrMessage, sanitizeErr := sanitizeOutput(stderr, repoPath)
+ if sanitizeErr != nil {
+ log.Error("sanitizeOutput failed on stderr: %v", sanitizeErr)
+ log.Error("Failed to update mirror repository %v:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdout, stderr, err)
+ return nil, false
+ }
+ stdoutMessage, err := sanitizeOutput(stdout, repoPath)
if err != nil {
- log.Error("sanitizeOutput: %v", err)
+ log.Error("sanitizeOutput failed: %v", sanitizeErr)
+ log.Error("Failed to update mirror repository %v:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdout, stderrMessage, err)
return nil, false
}
- desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, message)
- log.Error(desc)
+
+ log.Error("Failed to update mirror repository %v:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
+ desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, stderrMessage)
if err = models.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
return nil, false
}
- output := stderr
+ output := stderrBuilder.String()
gitRepo, err := git.OpenRepository(repoPath)
if err != nil {
@@ -208,18 +218,30 @@ func runSync(m *models.Mirror) ([]*mirrorSyncResult, bool) {
}
if m.Repo.HasWiki() {
- if _, stderr, err := process.GetManager().ExecDir(
- timeout, wikiPath, fmt.Sprintf("Mirror.runSync: %s", wikiPath),
- git.GitExecutable, "remote", "update", "--prune"); err != nil {
+ stderrBuilder.Reset()
+ stdoutBuilder.Reset()
+ if err := git.NewCommand("remote", "update", "--prune").
+ SetDescription(fmt.Sprintf("Mirror.runSync Wiki: %s ", m.Repo.FullName())).
+ RunInDirTimeoutPipeline(timeout, wikiPath, &stdoutBuilder, &stderrBuilder); err != nil {
+ stdout := stdoutBuilder.String()
+ stderr := stderrBuilder.String()
// sanitize the output, since it may contain the remote address, which may
// contain a password
- message, err := sanitizeOutput(stderr, wikiPath)
+ stderrMessage, sanitizeErr := sanitizeOutput(stderr, repoPath)
+ if sanitizeErr != nil {
+ log.Error("sanitizeOutput failed on stderr: %v", sanitizeErr)
+ log.Error("Failed to update mirror repository wiki %v:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdout, stderr, err)
+ return nil, false
+ }
+ stdoutMessage, err := sanitizeOutput(stdout, repoPath)
if err != nil {
- log.Error("sanitizeOutput: %v", err)
+ log.Error("sanitizeOutput failed: %v", sanitizeErr)
+ log.Error("Failed to update mirror repository wiki %v:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdout, stderrMessage, err)
return nil, false
}
- desc := fmt.Sprintf("Failed to update mirror wiki repository '%s': %s", wikiPath, message)
- log.Error(desc)
+
+ log.Error("Failed to update mirror repository wiki %v:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
+ desc := fmt.Sprintf("Failed to update mirror repository wiki '%s': %s", wikiPath, stderrMessage)
if err = models.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
diff --git a/services/release/release.go b/services/release/release.go
index 2f70bbb665..fd0c410e7c 100644
--- a/services/release/release.go
+++ b/services/release/release.go
@@ -13,7 +13,6 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification"
- "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/timeutil"
)
@@ -128,11 +127,11 @@ func DeleteReleaseByID(id int64, doer *models.User, delTag bool) error {
}
if delTag {
- _, stderr, err := process.GetManager().ExecDir(-1, repo.RepoPath(),
- fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID),
- git.GitExecutable, "tag", "-d", rel.TagName)
- if err != nil && !strings.Contains(stderr, "not found") {
- return fmt.Errorf("git tag -d: %v - %s", err, stderr)
+ if stdout, err := git.NewCommand("tag", "-d", rel.TagName).
+ SetDescription(fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID)).
+ RunInDir(repo.RepoPath()); err != nil && !strings.Contains(err.Error(), "not found") {
+ log.Error("DeleteReleaseByID (git tag -d): %d in %v Failed:\nStdout: %s\nError: %v", rel.ID, repo, stdout, err)
+ return fmt.Errorf("git tag -d: %v", err)
}
if err := models.DeleteReleaseByID(id); err != nil {
diff --git a/templates/admin/monitor.tmpl b/templates/admin/monitor.tmpl
index 6cc927d68f..38402fece2 100644
--- a/templates/admin/monitor.tmpl
+++ b/templates/admin/monitor.tmpl
@@ -42,6 +42,7 @@
<th>{{.i18n.Tr "admin.monitor.desc"}}</th>
<th>{{.i18n.Tr "admin.monitor.start"}}</th>
<th>{{.i18n.Tr "admin.monitor.execute_time"}}</th>
+ <th></th>
</tr>
</thead>
<tbody>
@@ -51,6 +52,7 @@
<td>{{.Description}}</td>
<td>{{DateFmtLong .Start}}</td>
<td>{{TimeSince .Start $.Lang}}</td>
+ <td><a class="delete-button" href="" data-url="{{$.Link}}/cancel/{{.PID}}" data-id="{{.PID}}" data-name="{{.Description}}"><i class="close icon text red"></i></a></td>
</tr>
{{end}}
</tbody>
@@ -58,4 +60,15 @@
</div>
</div>
</div>
+<div class="ui small basic delete modal">
+ <div class="ui icon header">
+ <i class="close icon"></i>
+ {{.i18n.Tr "admin.monitor.process.cancel"}}
+ </div>
+ <div class="content">
+ <p>{{$.i18n.Tr "admin.monitor.process.cancel_notices" `<span class="name"></span>` | Safe}}</p>
+ <p>{{$.i18n.Tr "admin.monitor.process.cancel_desc"}}</p>
+ </div>
+ {{template "base/delete_modal_actions" .}}
+</div>
{{template "base/footer" .}}