feat(process): emit kill action immediately
This commit is contained in:
parent
dfa97f2112
commit
eb6a7819e7
3 changed files with 70 additions and 27 deletions
34
process.go
34
process.go
|
|
@ -29,15 +29,17 @@ type Process struct {
|
|||
ExitCode int
|
||||
Duration time.Duration
|
||||
|
||||
cmd *exec.Cmd
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
output *RingBuffer
|
||||
stdin io.WriteCloser
|
||||
done chan struct{}
|
||||
mu sync.RWMutex
|
||||
gracePeriod time.Duration
|
||||
killGroup bool
|
||||
cmd *exec.Cmd
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
output *RingBuffer
|
||||
stdin io.WriteCloser
|
||||
done chan struct{}
|
||||
mu sync.RWMutex
|
||||
gracePeriod time.Duration
|
||||
killGroup bool
|
||||
killNotified bool
|
||||
killSignal string
|
||||
}
|
||||
|
||||
// Info returns a snapshot of process state.
|
||||
|
|
@ -140,22 +142,28 @@ func (p *Process) Done() <-chan struct{} {
|
|||
//
|
||||
// _ = proc.Kill()
|
||||
func (p *Process) Kill() error {
|
||||
_, err := p.kill()
|
||||
return err
|
||||
}
|
||||
|
||||
// kill terminates the process and reports whether a signal was actually sent.
|
||||
func (p *Process) kill() (bool, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.Status != StatusRunning {
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if p.cmd == nil || p.cmd.Process == nil {
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if p.killGroup {
|
||||
// Kill entire process group (negative PID)
|
||||
return syscall.Kill(-p.cmd.Process.Pid, syscall.SIGKILL)
|
||||
return true, syscall.Kill(-p.cmd.Process.Pid, syscall.SIGKILL)
|
||||
}
|
||||
return p.cmd.Process.Kill()
|
||||
return true, p.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
// Shutdown gracefully stops the process: SIGTERM, then SIGKILL after grace period.
|
||||
|
|
|
|||
50
service.go
50
service.go
|
|
@ -282,21 +282,16 @@ func (s *Service) StartWithOptions(ctx context.Context, opts RunOptions) (*Proce
|
|||
|
||||
close(proc.done)
|
||||
|
||||
if status == StatusKilled {
|
||||
s.emitKilledAction(proc, signalName)
|
||||
}
|
||||
|
||||
exitAction := ActionProcessExited{
|
||||
ID: id,
|
||||
ExitCode: exitCode,
|
||||
Duration: duration,
|
||||
Error: exitErr,
|
||||
}
|
||||
if status == StatusKilled {
|
||||
exitAction.Error = coreerr.E("Service.StartWithOptions", "process was killed", nil)
|
||||
if c := s.coreApp(); c != nil {
|
||||
_ = c.ACTION(ActionProcessKilled{
|
||||
ID: id,
|
||||
Signal: signalName,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if c := s.coreApp(); c != nil {
|
||||
_ = c.ACTION(exitAction)
|
||||
|
|
@ -394,7 +389,14 @@ func (s *Service) Kill(id string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return proc.Kill()
|
||||
sent, err := proc.kill()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sent {
|
||||
s.emitKilledAction(proc, "SIGKILL")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KillPID terminates a process by operating-system PID.
|
||||
|
|
@ -584,6 +586,34 @@ func classifyProcessExit(err error) (Status, int, error, string) {
|
|||
return StatusFailed, 0, err, ""
|
||||
}
|
||||
|
||||
// emitKilledAction broadcasts a kill event once for the given process.
|
||||
func (s *Service) emitKilledAction(proc *Process, signalName string) {
|
||||
if proc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
proc.mu.Lock()
|
||||
if proc.killNotified {
|
||||
proc.mu.Unlock()
|
||||
return
|
||||
}
|
||||
proc.killNotified = true
|
||||
if signalName != "" {
|
||||
proc.killSignal = signalName
|
||||
} else if proc.killSignal == "" {
|
||||
proc.killSignal = "SIGKILL"
|
||||
}
|
||||
signal := proc.killSignal
|
||||
proc.mu.Unlock()
|
||||
|
||||
if c := s.coreApp(); c != nil {
|
||||
_ = c.ACTION(ActionProcessKilled{
|
||||
ID: proc.ID,
|
||||
Signal: signal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// sortProcesses orders processes by start time, then ID for stable output.
|
||||
func sortProcesses(procs []*Process) {
|
||||
sort.Slice(procs, func(i, j int) bool {
|
||||
|
|
|
|||
|
|
@ -294,6 +294,14 @@ func TestService_Actions(t *testing.T) {
|
|||
err = svc.Kill(proc.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
mu.Lock()
|
||||
require.Len(t, killed, 1)
|
||||
assert.Equal(t, proc.ID, killed[0].ID)
|
||||
assert.NotEmpty(t, killed[0].Signal)
|
||||
mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-proc.Done():
|
||||
case <-time.After(2 * time.Second):
|
||||
|
|
@ -304,12 +312,9 @@ func TestService_Actions(t *testing.T) {
|
|||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
assert.Len(t, killed, 1)
|
||||
assert.Equal(t, proc.ID, killed[0].ID)
|
||||
assert.NotEmpty(t, killed[0].Signal)
|
||||
assert.Len(t, exited, 1)
|
||||
assert.Equal(t, proc.ID, exited[0].ID)
|
||||
assert.Error(t, exited[0].Error)
|
||||
assert.NoError(t, exited[0].Error)
|
||||
assert.Equal(t, StatusKilled, proc.Status)
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue