mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-30 16:47:17 +00:00
eea03676d3
Signed-off-by: Royce Remer <royceremer@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
68 lines
1.9 KiB
Go
68 lines
1.9 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//go:build unix
|
|
|
|
package process
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCommandContextCancelKillProcessGroup(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
defer cancel()
|
|
|
|
// When executing external commands, there can be multiple subprocesses involved.
|
|
// e.g.: Gitea -> "git upload-pack" -> "git pack-objects".
|
|
// If the context is canceled (HTTP client disconnects), all subprocesses must terminate.
|
|
|
|
// Spawn a shell that itself spawns a long-lived background process and
|
|
// prints its PID — mimicking git upload-pack spawning git pack-objects.
|
|
|
|
r, w, err := os.Pipe()
|
|
require.NoError(t, err)
|
|
|
|
cmd := CommandContext(ctx, "sh", "-c", "sleep 600 & echo $!; wait")
|
|
cmd.Stdout = w
|
|
require.NoError(t, cmd.Start())
|
|
_ = w.Close() // parent keeps only the read end
|
|
|
|
t.Cleanup(func() {
|
|
// make sure our test doesn't leave a zombie process even if test fails
|
|
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
|
_ = cmd.Wait()
|
|
})
|
|
|
|
// Block until the shell prints the grandchild PID.
|
|
scanner := bufio.NewScanner(r)
|
|
require.True(t, scanner.Scan(), "expected grandchild PID on stdout")
|
|
grandchildPID, err := strconv.Atoi(strings.TrimSpace(scanner.Text()))
|
|
require.NoError(t, err)
|
|
_ = r.Close()
|
|
|
|
// Sanity: grandchild must be alive before we cancel.
|
|
grandchild, err := os.FindProcess(grandchildPID)
|
|
require.NoError(t, err)
|
|
require.NoError(t, grandchild.Signal(syscall.Signal(0)), "grandchild should be alive before cancel")
|
|
|
|
// Cancel the context
|
|
cancel()
|
|
_ = cmd.Wait()
|
|
|
|
// Subprocess should not exist after context cancel (killed by process group)
|
|
assert.Eventually(t, func() bool {
|
|
return grandchild.Signal(syscall.Signal(0)) != nil
|
|
}, 5*time.Second, 10*time.Millisecond)
|
|
}
|