Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 4 additions & 15 deletions ext/git/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"sort"
"strconv"
"strings"
"syscall"
"time"

"github.com/bmatcuk/doublestar/v4"
Expand Down Expand Up @@ -806,29 +805,19 @@ func (m *nativeGitClient) runCmdOutput(cmd *exec.Cmd, ropts runOpts) (string, er

// Run git in its own process group so that child processes (e.g. git-remote-https)
// can be cleaned up when the parent is killed on timeout or context cancellation.
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
setSysProcAttr(cmd)

opts := executil.ExecRunOpts{
TimeoutBehavior: executil.TimeoutBehavior{
Signal: syscall.SIGTERM,
ShouldWait: true,
},
TimeoutBehavior: newTimeoutBehavior(),
SkipErrorLogging: ropts.SkipErrorLogging,
CaptureStderr: ropts.CaptureStderr,
}
output, err := executil.RunWithExecRunOpts(cmd, opts)

// After the git command finishes (normally or via timeout/context cancellation),
// kill the entire process group to clean up any orphaned child processes such as
// git-remote-https. Without this, child processes accumulate over time and
// eventually exhaust the container's PID limit, causing "cannot fork()" errors.
//
// The negative int `-cmd.Process.Pid` denotes the process group id, which was set
// to PID in `SysProcAttr{Setpgid: true}` earlier. The `syscall.Kill()` below will
// send SIGKILL to all processes in the group.
if cmd.Process != nil {
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
// git-remote-https.
killProcessGroup(cmd)

return output, err
}
34 changes: 34 additions & 0 deletions ext/git/procattr_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//go:build !windows

package git

import (
"os/exec"
"syscall"

executil "github.com/argoproj/argo-cd/v3/util/exec"
)

// setSysProcAttr configures the command to run in its own process group so that
// child processes (e.g. git-remote-https) can be cleaned up when the parent is
// killed on timeout or context cancellation.
func setSysProcAttr(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}

// killProcessGroup kills the entire process group to clean up any orphaned
// child processes such as git-remote-https. The negative PID denotes the
// process group, which was set via Setpgid above.
func killProcessGroup(cmd *exec.Cmd) {
if cmd.Process != nil {
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
}

// newTimeoutBehavior returns the platform-specific timeout behavior.
func newTimeoutBehavior() executil.TimeoutBehavior {
return executil.TimeoutBehavior{
Signal: syscall.SIGTERM,
ShouldWait: true,
}
}
26 changes: 26 additions & 0 deletions ext/git/procattr_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//go:build windows

package git

import (
"os/exec"
"syscall"

executil "github.com/argoproj/argo-cd/v3/util/exec"
)

// setSysProcAttr is a no-op on Windows because Setpgid is not supported.
func setSysProcAttr(_ *exec.Cmd) {}

// killProcessGroup is a no-op on Windows because syscall.Kill with negative
// PIDs (process groups) is not supported.
func killProcessGroup(_ *exec.Cmd) {}

// newTimeoutBehavior returns the platform-specific timeout behavior.
// On Windows, SIGKILL is the only reliable signal, and we don't wait.
func newTimeoutBehavior() executil.TimeoutBehavior {
return executil.TimeoutBehavior{
Signal: syscall.SIGKILL,
ShouldWait: false,
}
}
Loading