mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-06-21 17:24:23 +02:00
- Adds `runner.post_task_script` and `runner.post_task_script_timeout` (default `5m`) to run a host executable after each task’s built-in cleanup (post-steps, container teardown, bind-workdir removal). - Stops task heartbeats via `Reporter.StopHeartbeats()` while the script runs so Gitea won’t assign overlapping work; the final task acknowledgement still happens in `reporter.Close()`. - Script output goes to the runner process log; non-zero exits are warned only and do not change the job result. - Documents lifecycle, offline behavior, timeouts, and Windows limits (`.ps1` not supported yet) in `docs/post-task-script.md`. Reviewed-on: https://gitea.com/gitea/runner/pulls/1026 Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
79 lines
2.4 KiB
Go
79 lines
2.4 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package process
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// processAlive reports whether pid refers to a still-running process.
|
|
func processAlive(pid int) bool {
|
|
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(pid))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer windows.CloseHandle(h)
|
|
var code uint32
|
|
if err := windows.GetExitCodeProcess(h, &code); err != nil {
|
|
return false
|
|
}
|
|
const stillActive = 259 // STILL_ACTIVE
|
|
return code == stillActive
|
|
}
|
|
|
|
// TestKillerKillsTree verifies that a process assigned to the Job Object is
|
|
// terminated together with a child it spawns afterwards. This mirrors a step or
|
|
// post-task script that launches a child which spawns further processes, where
|
|
// cancelling must take down the whole tree, not just the direct child.
|
|
func TestKillerKillsTree(t *testing.T) {
|
|
dir := t.TempDir()
|
|
pidFile := filepath.Join(dir, "child.pid")
|
|
|
|
// Parent powershell spawns a detached, long-lived child powershell (writing
|
|
// its PID to a file) and then sleeps. The child is launched AFTER the parent
|
|
// has been assigned to the job, so it must be captured by the job too.
|
|
script := fmt.Sprintf(
|
|
`$c = Start-Process powershell -PassThru -ArgumentList '-NoProfile','-Command','Start-Sleep -Seconds 600'; `+
|
|
`Set-Content -LiteralPath %q -Value $c.Id; Start-Sleep -Seconds 600`, pidFile)
|
|
cmd := exec.Command("powershell.exe", "-NoProfile", "-Command", script)
|
|
require.NoError(t, cmd.Start())
|
|
t.Cleanup(func() { _ = cmd.Process.Kill() })
|
|
|
|
killer, err := NewKiller(cmd.Process)
|
|
require.NoError(t, err)
|
|
defer killer.Close()
|
|
|
|
// Wait for the child PID to be reported.
|
|
var childPID int
|
|
require.Eventually(t, func() bool {
|
|
b, e := os.ReadFile(pidFile)
|
|
if e != nil {
|
|
return false
|
|
}
|
|
s := strings.TrimSpace(string(b))
|
|
if s == "" {
|
|
return false
|
|
}
|
|
childPID, _ = strconv.Atoi(s)
|
|
return childPID > 0 && processAlive(childPID)
|
|
}, 20*time.Second, 200*time.Millisecond, "child process should start")
|
|
|
|
// Killing the job must terminate both the parent and the detached child.
|
|
require.NoError(t, killer.Kill())
|
|
|
|
require.Eventually(t, func() bool {
|
|
return !processAlive(cmd.Process.Pid) && !processAlive(childPID)
|
|
}, 20*time.Second, 200*time.Millisecond, "parent and child should both be terminated")
|
|
}
|