termtap/internal/app/process.go
Hayden Hargreaves 002773e77f test: AI generated all of these tests
Just for the MVP of course. Need to validate the idea.
2026-04-23 19:47:04 -07:00

106 lines
2.4 KiB
Go

package app
import (
"errors"
"fmt"
"os/exec"
"sync"
"syscall"
"time"
"termtap.dev/internal/model"
"termtap.dev/internal/process"
)
var killEscalationDelay = 1500 * time.Millisecond
var scheduleKillEscalation = time.AfterFunc
var killEscalationMu sync.RWMutex
func StartProcess(cmd model.Command, addr string, ch chan<- model.Event) (*model.Process, error) {
ch <- model.Event{
Time: time.Now().Local(),
Type: model.EventTypeProcessStarting,
Body: fmt.Sprintf("starting process '%s'", process.CommandString(cmd)),
}
proc := process.NewProcess(cmd, addr, ch)
if err := proc.Exec.Start(); err != nil {
return nil, fmt.Errorf("start process: %w", err)
}
process.UpdateStatus(proc, true, ch)
go waitForProcessExit(proc, ch)
return proc, nil
}
func StopProcess(proc *model.Process, ch chan<- model.Event, sig syscall.Signal) {
if proc == nil || proc.Exec == nil || proc.Exec.Process == nil {
return
}
ch <- model.Event{
Time: time.Now().Local(),
Type: model.EventTypeProcessSignaled,
Body: fmt.Sprintf("process received signal '%s'", sig.String()),
PID: proc.Exec.Process.Pid,
}
_ = process.SignalProcess(proc.Exec, sig)
killEscalationMu.RLock()
delay := killEscalationDelay
scheduler := scheduleKillEscalation
killEscalationMu.RUnlock()
scheduler(delay, func() {
if process.ProcessAlive(proc.Exec) {
_ = process.SignalProcess(proc.Exec, syscall.SIGKILL)
}
})
}
func waitForProcessExit(proc *model.Process, ch chan<- model.Event) {
if proc == nil || proc.Exec == nil {
return
}
defer func() {
if proc.Done != nil {
close(proc.Done)
}
}()
if err := proc.Exec.Wait(); err != nil {
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
ch <- model.Event{
Time: time.Now().Local(),
Type: model.EventTypeProcessExited,
Body: "process exited",
PID: proc.Exec.Process.Pid,
ExitCode: exitErr.ExitCode(),
}
process.UpdateStatus(proc, false, ch)
return
}
ch <- model.Event{
Time: time.Now().Local(),
Type: model.EventTypeFatal,
Body: fmt.Sprintf("%q", err),
}
process.UpdateStatus(proc, false, ch)
return
}
code := proc.Exec.ProcessState.ExitCode()
ch <- model.Event{
Time: time.Now().Local(),
Type: model.EventTypeProcessExited,
Body: "process exited",
PID: proc.Exec.Process.Pid,
ExitCode: code,
}
process.UpdateStatus(proc, false, ch)
}