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
151 lines
2.9 KiB
Go
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()
|
|
})
|
|
}
|