Files
wehub-resource-sync 26f897c1ec
release / release-please (push) Failing after 1m49s
docs / build (push) Failing after 6m34s
release / build-and-upload (arm64, linux) (push) Has been cancelled
release / build-and-upload (arm64, windows) (push) Has been cancelled
release / build-darwin (amd64, darwin) (push) Has been cancelled
release / checksums (push) Has been cancelled
release / finalize (push) Has been cancelled
release / build-darwin (arm64, darwin) (push) Has been cancelled
release / build-and-upload (amd64, linux) (push) Has been cancelled
release / build-and-upload (amd64, windows) (push) Has been cancelled
docs / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:34:16 +08:00

151 lines
2.9 KiB
Go

package agent
import (
"errors"
"fmt"
"os"
"os/exec"
"sync"
"time"
"github.com/kunchenguid/no-mistakes/internal/shellenv"
)
type nativeAgentCommand struct {
cmd *exec.Cmd
stdout *nativeAgentPipe
stderr *nativeAgentPipe
waitCh chan error
terminateOnce sync.Once
closePipesOnce sync.Once
pipeMu sync.Mutex
remainingPipes int
pipesDone chan struct{}
}
type nativeAgentPipe struct {
file *os.File
done func()
doneOnce sync.Once
}
func (p *nativeAgentPipe) Read(b []byte) (int, error) {
n, err := p.file.Read(b)
if err != nil {
p.markDone()
}
return n, err
}
func (p *nativeAgentPipe) Close() error {
err := p.file.Close()
p.markDone()
return err
}
func (p *nativeAgentPipe) markDone() {
p.doneOnce.Do(p.done)
}
func startNativeAgentCommand(cmd *exec.Cmd) (*nativeAgentCommand, error) {
stdoutR, stdoutW, err := os.Pipe()
if err != nil {
return nil, fmt.Errorf("stdout pipe: %w", err)
}
stderrR, stderrW, err := os.Pipe()
if err != nil {
_ = stdoutR.Close()
_ = stdoutW.Close()
return nil, fmt.Errorf("stderr pipe: %w", err)
}
cmd.Stdout = stdoutW
cmd.Stderr = stderrW
if err := shellenv.StartShellCommand(cmd); err != nil {
_ = stdoutR.Close()
_ = stdoutW.Close()
_ = stderrR.Close()
_ = stderrW.Close()
return nil, err
}
_ = stdoutW.Close()
_ = stderrW.Close()
started := &nativeAgentCommand{
cmd: cmd,
waitCh: make(chan error, 1),
remainingPipes: 2,
pipesDone: make(chan struct{}),
}
started.stdout = &nativeAgentPipe{file: stdoutR, done: started.markPipeDone}
started.stderr = &nativeAgentPipe{file: stderrR, done: started.markPipeDone}
go func() {
err := cmd.Wait()
started.terminate()
started.waitCh <- started.waitForPipes(err)
}()
return started, nil
}
func (c *nativeAgentCommand) markPipeDone() {
c.pipeMu.Lock()
defer c.pipeMu.Unlock()
c.remainingPipes--
if c.remainingPipes == 0 {
close(c.pipesDone)
}
}
func (c *nativeAgentCommand) pid() int {
if c == nil || c.cmd == nil || c.cmd.Process == nil {
return 0
}
return c.cmd.Process.Pid
}
func (c *nativeAgentCommand) waitForPipes(waitErr error) error {
if c.cmd.WaitDelay <= 0 {
<-c.pipesDone
return waitErr
}
timer := time.NewTimer(c.cmd.WaitDelay)
defer timer.Stop()
select {
case <-c.pipesDone:
return waitErr
case <-timer.C:
c.closePipes()
if waitErr == nil {
return exec.ErrWaitDelay
}
return waitErr
}
}
func (c *nativeAgentCommand) terminate() {
c.terminateOnce.Do(func() {
shellenv.TerminateShellCommandGroup(c.cmd)
})
}
func (c *nativeAgentCommand) waitAfterParseError(parseErr error) error {
c.terminate()
c.closePipes()
waitErr := c.wait()
if errors.Is(waitErr, exec.ErrWaitDelay) {
return waitErr
}
return parseErr
}
func (c *nativeAgentCommand) wait() error {
return <-c.waitCh
}
func (c *nativeAgentCommand) closePipes() {
c.closePipesOnce.Do(func() {
_ = c.stdout.Close()
_ = c.stderr.Close()
})
}