@@ -69,7 +69,7 @@ type Cmd struct {
69
69
OnStderr StringCallback // one line fetched from stderr
70
70
OnRestart IntReturningCallback // this overwrites RestartDelayMs
71
71
OnExit ParameterlessCallback // when max restart reached, or manually killed
72
- OnProcessCompleted IntCallback // when 1x process done, can be restarting depends on RestartCount and MaxCount
72
+ OnProcessCompleted IntCallback // when 1x process done, return durationMs can be restarting depends on RestartCount and MaxCount
73
73
OnStateChanged CmdStateCallback // triggered when stated changed
74
74
75
75
state CmdState
@@ -131,7 +131,7 @@ type Process struct {
131
131
type Goproc struct {
132
132
cmds []* Cmd
133
133
procs []* Process
134
- lock sync.Mutex
134
+ lock sync.RWMutex
135
135
HasErrFunc func (err error , fmt string , args ... any ) bool
136
136
}
137
137
@@ -158,7 +158,8 @@ func DiscardHasErr(err error, _ string, _ ...any) bool {
158
158
return err != nil
159
159
}
160
160
161
- func New () * Goproc {
161
+ // NewWithCleanup might cause stray goroutine if called too many times
162
+ func NewWithCleanup () * Goproc {
162
163
c := make (chan os.Signal )
163
164
signal .Notify (c , os .Interrupt , syscall .SIGTERM )
164
165
res := & Goproc {
@@ -175,6 +176,15 @@ func New() *Goproc {
175
176
return res
176
177
}
177
178
179
+ // New must call Cleanup before exit or there will be stray
180
+ func New () * Goproc {
181
+ res := & Goproc {
182
+ cmds : []* Cmd {},
183
+ HasErrFunc : L .IsError ,
184
+ }
185
+ return res
186
+ }
187
+
178
188
// AddCommand add a new command to run, not yet started until Start called
179
189
// returns command id
180
190
func (g * Goproc ) AddCommand (cmd * Cmd ) CommandId {
@@ -220,13 +230,13 @@ func (g *Goproc) Signal(cmdId CommandId, signal os.Signal) error {
220
230
// * stop them; signal=os.Kill
221
231
err := proc .exe .Process .Kill ()
222
232
cmd .setState (Killed )
223
- if g .HasErrFunc (err , `error proc .exe.Process.Kill` ) {
233
+ if g .HasErrFunc (err , `error globalRunner .exe.Process.Kill` ) {
224
234
return err
225
235
}
226
236
} else {
227
237
// * relay termination signals;
228
238
err := proc .exe .Process .Signal (signal )
229
- if g .HasErrFunc (err , `error proc .exe.Process.Signal %d` , signal ) {
239
+ if g .HasErrFunc (err , `error globalRunner .exe.Process.Signal %d` , signal ) {
230
240
return err
231
241
}
232
242
}
@@ -236,11 +246,14 @@ func (g *Goproc) Signal(cmdId CommandId, signal os.Signal) error {
236
246
// Start start certain command
237
247
func (g * Goproc ) Start (cmdId CommandId ) error {
238
248
idx := int (cmdId )
249
+ g .lock .RLock ()
239
250
if idx >= len (g .cmds ) || idx < 0 {
251
+ g .lock .RUnlock ()
240
252
return fmt .Errorf (`invalid command index, should be zero to %d` , len (g .cmds )- 1 )
241
253
}
242
254
243
255
cmd := g .cmds [idx ]
256
+ g .lock .RUnlock ()
244
257
cmd .strCache = `` // reset cache
245
258
246
259
prefix := S .IfEmpty (cmd .PrefixLabel , `CMD:` + I .ToStr (idx )) + `: `
@@ -253,7 +266,9 @@ func (g *Goproc) Start(cmdId CommandId) error {
253
266
254
267
for {
255
268
// refill process
269
+ g .lock .RLock ()
256
270
proc := g .procs [idx ]
271
+ g .lock .RUnlock ()
257
272
proc .exe = exec .Command (cmd .Program , cmd .Parameters ... )
258
273
proc .exe .Dir = cmd .WorkDir
259
274
if cmd .InheritEnv {
@@ -264,17 +279,17 @@ func (g *Goproc) Start(cmdId CommandId) error {
264
279
265
280
// get output buffer and start
266
281
stderr , err := proc .exe .StderrPipe ()
267
- if g .HasErrFunc (err , prefix + `error proc .exe.StderrPipe %s` , cmd ) {
282
+ if g .HasErrFunc (err , prefix + `error globalRunner .exe.StderrPipe %s` , cmd ) {
268
283
return err
269
284
}
270
285
stdout , err := proc .exe .StdoutPipe ()
271
- if g .HasErrFunc (err , prefix + `error proc .exe.StdoutPipe %s` , cmd ) {
286
+ if g .HasErrFunc (err , prefix + `error globalRunner .exe.StdoutPipe %s` , cmd ) {
272
287
return err
273
288
}
274
- log .Printf (prefix + `starting: ` + cmd .String ())
289
+ log .Println (prefix + `starting: ` + cmd .String ())
275
290
start := time .Now ()
276
291
err = proc .exe .Start ()
277
- if g .HasErrFunc (err , prefix + `error proc .exe.Start %s` , cmd ) {
292
+ if g .HasErrFunc (err , prefix + `error globalRunner .exe.Start %s` , cmd ) {
278
293
cmd .LastExecutionError = err
279
294
if cmd .OnProcessCompleted != nil {
280
295
durationMs := time .Since (start ).Milliseconds ()
@@ -346,12 +361,12 @@ func (g *Goproc) Start(cmdId CommandId) error {
346
361
347
362
// wait for exit
348
363
err = proc .exe .Wait ()
349
- if g .HasErrFunc (err , prefix + `error proc .exe.Wait %s` , cmd ) {
364
+ if g .HasErrFunc (err , prefix + `error globalRunner .exe.Wait %s` , cmd ) {
350
365
if cmd .state != Killed {
351
366
cmd .setState (Crashed )
352
367
}
353
368
} else {
354
- log .Println ("exited" )
369
+ log .Println (prefix + "exited" )
355
370
if cmd .state != Killed {
356
371
cmd .setState (Exited )
357
372
}
@@ -410,8 +425,8 @@ func (g *Goproc) Start(cmdId CommandId) error {
410
425
411
426
// StartAll start all that not yet started
412
427
func (g * Goproc ) StartAll () {
413
- g .lock .Lock ()
414
- defer g .lock .Unlock ()
428
+ g .lock .RLock ()
429
+ defer g .lock .RUnlock ()
415
430
for idx , cmd := range g .cmds {
416
431
if cmd .state == NotStarted {
417
432
g .Start (CommandId (idx ))
@@ -421,8 +436,8 @@ func (g *Goproc) StartAll() {
421
436
422
437
// StartAllParallel start all that not yet started in parallel
423
438
func (g * Goproc ) StartAllParallel () * sync.WaitGroup {
424
- g .lock .Lock ()
425
- defer g .lock .Unlock ()
439
+ g .lock .RLock ()
440
+ defer g .lock .RUnlock ()
426
441
wg := & sync.WaitGroup {}
427
442
for idx , cmd := range g .cmds {
428
443
if cmd .state == NotStarted {
@@ -463,7 +478,6 @@ func (g *Goproc) CommandString(cmdId CommandId) string {
463
478
464
479
// Run1 execute one command and get stdout stderr output
465
480
func Run1 (cmd * Cmd ) (string , string , error , int ) {
466
- proc := New ()
467
481
onStdout := cmd .OnStdout
468
482
onStderr := cmd .OnStderr
469
483
stdoutBuff := bytes.Buffer {}
@@ -490,14 +504,15 @@ func Run1(cmd *Cmd) (string, string, error, int) {
490
504
}
491
505
return nil
492
506
}
493
- proc .AddCommand (cmd )
494
- proc . StartAll ( )
507
+ cmdId := globalRunner .AddCommand (cmd )
508
+ globalRunner . Start ( cmdId )
495
509
return stdoutBuff .String (), stderrBuff .String (), cmd .LastExecutionError , cmd .LastExitCode
496
510
}
497
511
498
- // Run1 execute one command and get stdout stderr output
512
+ var globalRunner = NewWithCleanup ()
513
+
514
+ // RunLines execute one command and get stdout stderr output
499
515
func RunLines (cmd * Cmd ) ([]string , []string , error , int ) {
500
- proc := New ()
501
516
onStdout := cmd .OnStdout
502
517
onStderr := cmd .OnStderr
503
518
stdoutBuff := []string {}
@@ -522,7 +537,7 @@ func RunLines(cmd *Cmd) ([]string, []string, error, int) {
522
537
}
523
538
return nil
524
539
}
525
- proc .AddCommand (cmd )
526
- proc . StartAll ( )
540
+ cmdId := globalRunner .AddCommand (cmd )
541
+ globalRunner . Start ( cmdId )
527
542
return stdoutBuff , stderrBuff , cmd .LastExecutionError , cmd .LastExitCode
528
543
}
0 commit comments