mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-05 14:55:44 +00:00
The intermittent Windows test crashes came from step errors formatting a nil `rc.Run`. `fmt` recovers the nil dereference, but on CPUs with AMX the recovered fault overflows into the heap (https://github.com/golang/go/issues/81238). The errors now name the job, and the Windows test VM hides AMX and runs on UTC, so cached boots no longer jump back 8 hours mid-test. Windows commands now start suspended and resume once they are in their job objects, so no child escapes them. That drops the process scan reading other processes' memory and the last `unsafe` code, which `depguard` now denies. Step processes outlive a crashed runner, and commands are no longer looked up in the runner's working directory, both as with GitHub's runner. Like GitHub's runner, host jobs on Linux and macOS now end the processes carrying the job's `RUNNER_TRACKING_ID`, and image pulls and Dockerfile action builds are retried. Reviewed-on: https://gitea.com/gitea/runner/pulls/1376 Reviewed-by: bircni <[email protected]> Co-authored-by: silverwind <[email protected]>
68 lines
1.5 KiB
Go
68 lines
1.5 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package process
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"sync"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// Group holds tracked step processes in a job-scoped Windows Job Object.
|
|
type Group struct {
|
|
mu sync.Mutex
|
|
job windows.Handle
|
|
}
|
|
|
|
// NewGroup creates the job object. Closing it is what terminates the leftovers,
|
|
// so the caller must Close it when the job ends.
|
|
func NewGroup() (*Group, error) {
|
|
job, err := windows.CreateJobObject(nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Group{job: job}, nil
|
|
}
|
|
|
|
// Assign adds a started process to the job; anything it spawns afterwards joins
|
|
// too. Call before NewKiller so the step's job nests inside this one. Nil-safe.
|
|
func (g *Group) Assign(p *os.Process) error {
|
|
if g == nil || p == nil {
|
|
return nil
|
|
}
|
|
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
if g.job == 0 {
|
|
return nil
|
|
}
|
|
|
|
h, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE, false, uint32(p.Pid))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = windows.CloseHandle(h) }()
|
|
|
|
return windows.AssignProcessToJobObject(g.job, h)
|
|
}
|
|
|
|
// Close terminates every process still assigned to the job and drops its handle, nil-safe and idempotent.
|
|
func (g *Group) Close() error {
|
|
if g == nil {
|
|
return nil
|
|
}
|
|
|
|
g.mu.Lock()
|
|
defer g.mu.Unlock()
|
|
if g.job == 0 {
|
|
return nil
|
|
}
|
|
|
|
h := g.job
|
|
g.job = 0
|
|
return errors.Join(windows.TerminateJobObject(h, 1), windows.CloseHandle(h))
|
|
}
|