@@ -110,7 +110,8 @@ type program struct {
110110 currentRun int
111111 runResults []result
112112
113- exitCode int
113+ exitCode int
114+ overallState ProcessState
114115
115116 stdoutLock sync.RWMutex
116117 stderrLock sync.RWMutex
@@ -283,6 +284,10 @@ func (p *program) run() error {
283284 p .runResults [currentIndex ].errorBuffer .WriteString ("\n " )
284285 p .runResults [currentIndex ].lastError = err .Error ()
285286 p .resultsLock .Unlock ()
287+
288+ p .stateLock .Lock ()
289+ p .overallState = StateError
290+ p .stateLock .Unlock ()
286291 p .signalDone ()
287292 return err
288293 }
@@ -291,6 +296,10 @@ func (p *program) run() error {
291296 p .runResults [currentIndex ].state = StateRunning
292297 p .resultsLock .Unlock ()
293298
299+ p .stateLock .Lock ()
300+ p .overallState = StateRunning
301+ p .stateLock .Unlock ()
302+
294303 if p .async {
295304 go p .monitorProcess ()
296305 return nil
@@ -301,15 +310,6 @@ func (p *program) run() error {
301310}
302311
303312func (p * program ) monitorProcess () {
304- defer func () {
305- p .cmdLock .Lock ()
306- exitCode := p .cmd .ProcessState .ExitCode ()
307- p .cmdLock .Unlock ()
308- p .stateLock .Lock ()
309- p .exitCode = exitCode
310- p .stateLock .Unlock ()
311- }()
312-
313313 // WaitGroup ensures readOutput goroutines finish reading all data.
314314 // This prevents race conditions where callers might see incomplete output.
315315 var wg sync.WaitGroup
@@ -368,6 +368,20 @@ func (p *program) monitorProcess() {
368368 p .runResults [currentIndex ].state = StateFinished
369369 }
370370 p .resultsLock .Unlock ()
371+
372+ p .cmdLock .Lock ()
373+ exitCode := p .cmd .ProcessState .ExitCode ()
374+ p .cmdLock .Unlock ()
375+
376+ p .stateLock .Lock ()
377+ p .exitCode = exitCode
378+ if err != nil {
379+ p .overallState = StateError
380+ } else {
381+ p .overallState = StateFinished
382+ }
383+ p .stateLock .Unlock ()
384+
371385 p .signalDone ()
372386}
373387
@@ -492,13 +506,9 @@ func (p *program) Error() string {
492506}
493507
494508func (p * program ) State () ProcessState {
495- p .resultsLock .RLock ()
496- defer p .resultsLock .RUnlock ()
497-
498- if len (p .runResults ) == 0 {
499- return StateNotStarted
500- }
501- return p .runResults [len (p .runResults )- 1 ].state
509+ p .stateLock .RLock ()
510+ defer p .stateLock .RUnlock ()
511+ return p .overallState
502512}
503513
504514func (p * program ) Interval () time.Duration {
@@ -568,6 +578,7 @@ func (p *program) forcekill() error {
568578
569579type Statistics struct {
570580 ProgramName string `json:"program_name"`
581+ State string `json:"state"`
571582 TotalRuns int `json:"total_runs"`
572583 Successful int `json:"successful_runs"`
573584 Failed int `json:"failed_runs"`
@@ -622,8 +633,21 @@ func (p *program) Statistics() Statistics {
622633 resultsCopy = slices .Clone (p .runResults )
623634 p .resultsLock .RUnlock ()
624635
636+ var state string
637+ switch p .State () {
638+ case StateFinished :
639+ state = "finished"
640+ case StateError :
641+ state = "error"
642+ case StateRunning :
643+ state = "running"
644+ case StateNotStarted :
645+ state = "not started"
646+ }
647+
625648 stats := Statistics {
626649 ProgramName : p .name ,
650+ State : state ,
627651 TotalRuns : len (resultsCopy ),
628652 LastSuccessfulRun : - 1 ,
629653 Interval : p .interval ,
0 commit comments