@@ -20,6 +20,8 @@ import (
20
20
// executors, running setup() and teardown(), and actually starting the
21
21
// executors for the different scenarios at the appropriate times.
22
22
type Scheduler struct {
23
+ controller Controller
24
+
23
25
initProgress * pb.ProgressBar
24
26
executorConfigs []lib.ExecutorConfig // sorted by (startTime, ID)
25
27
executors []lib.Executor // sorted by (startTime, ID), excludes executors with no work
@@ -33,7 +35,7 @@ type Scheduler struct {
33
35
// initializing it beyond the bare minimum. Specifically, it creates the needed
34
36
// executor instances and a lot of state placeholders, but it doesn't initialize
35
37
// the executors and it doesn't initialize or run VUs.
36
- func NewScheduler (trs * lib.TestRunState ) (* Scheduler , error ) {
38
+ func NewScheduler (trs * lib.TestRunState , controller Controller ) (* Scheduler , error ) {
37
39
options := trs .Options
38
40
et , err := lib .NewExecutionTuple (options .ExecutionSegment , options .ExecutionSegmentSequence )
39
41
if err != nil {
@@ -81,6 +83,7 @@ func NewScheduler(trs *lib.TestRunState) (*Scheduler, error) {
81
83
maxDuration : maxDuration ,
82
84
maxPossibleVUs : maxPossibleVUs ,
83
85
state : executionState ,
86
+ controller : controller ,
84
87
}, nil
85
88
}
86
89
@@ -380,6 +383,13 @@ func (e *Scheduler) Init(
380
383
) (stopVUEmission func (), initErr error ) {
381
384
logger := e .state .Test .Logger .WithField ("phase" , "execution-scheduler-init" )
382
385
386
+ if err := SignalAndWait (e .controller , "scheduler-init-start" ); err != nil {
387
+ return nil , err
388
+ }
389
+ defer func () {
390
+ initErr = SignalErrorOrWait (e .controller , "scheduler-init-done" , initErr )
391
+ }()
392
+
383
393
execSchedRunCtx , execSchedRunCancel := context .WithCancel (runCtx )
384
394
waitForVUsMetricPush := e .emitVUsAndVUsMax (execSchedRunCtx , samplesOut )
385
395
stopVUEmission = func () {
@@ -405,16 +415,20 @@ func (e *Scheduler) Init(
405
415
// Run the Scheduler, funneling all generated metric samples through the supplied
406
416
// out channel.
407
417
//
408
- //nolint:funlen
418
+ //nolint:funlen, gocognit
409
419
func (e * Scheduler ) Run (globalCtx , runCtx context.Context , samplesOut chan <- metrics.SampleContainer ) (runErr error ) {
410
420
logger := e .state .Test .Logger .WithField ("phase" , "execution-scheduler-run" )
411
421
422
+ if err := SignalAndWait (e .controller , "scheduler-run-start" ); err != nil {
423
+ return err
424
+ }
412
425
defer func () {
413
426
if interruptErr := GetCancelReasonIfTestAborted (runCtx ); interruptErr != nil {
414
427
logger .Debugf ("The test run was interrupted, returning '%s' instead of '%s'" , interruptErr , runErr )
415
428
e .state .SetExecutionStatus (lib .ExecutionStatusInterrupted )
416
429
runErr = interruptErr
417
430
}
431
+ runErr = SignalErrorOrWait (e .controller , "scheduler-run-done" , runErr )
418
432
}()
419
433
420
434
e .initProgress .Modify (pb .WithConstLeft ("Run" ))
@@ -430,6 +444,10 @@ func (e *Scheduler) Run(globalCtx, runCtx context.Context, samplesOut chan<- met
430
444
}
431
445
}
432
446
447
+ if err := SignalAndWait (e .controller , "test-ready-to-run-setup" ); err != nil {
448
+ return err
449
+ }
450
+
433
451
e .initProgress .Modify (pb .WithConstProgress (1 , "Starting test..." ))
434
452
e .state .MarkStarted ()
435
453
defer e .state .MarkEnded ()
@@ -449,11 +467,27 @@ func (e *Scheduler) Run(globalCtx, runCtx context.Context, samplesOut chan<- met
449
467
if ! e .state .Test .Options .NoSetup .Bool {
450
468
e .state .SetExecutionStatus (lib .ExecutionStatusSetup )
451
469
e .initProgress .Modify (pb .WithConstProgress (1 , "setup()" ))
452
- if err := e .state .Test .Runner .Setup (withExecStateCtx , samplesOut ); err != nil {
453
- logger .WithField ("error" , err ).Debug ("setup() aborted by error" )
470
+ actuallyRanSetup := false
471
+ data , err := e .controller .GetOrCreateData ("setup" , func () ([]byte , error ) {
472
+ actuallyRanSetup = true
473
+ if err := e .state .Test .Runner .Setup (withExecStateCtx , samplesOut ); err != nil {
474
+ logger .WithField ("error" , err ).Debug ("setup() aborted by error" )
475
+ return nil , err
476
+ }
477
+ return e .state .Test .Runner .GetSetupData (), nil
478
+ })
479
+ if err != nil {
454
480
return err
455
481
}
482
+ if ! actuallyRanSetup {
483
+ e .state .Test .Runner .SetSetupData (data )
484
+ }
485
+ }
486
+
487
+ if err := SignalAndWait (e .controller , "setup-done" ); err != nil {
488
+ return err
456
489
}
490
+
457
491
e .initProgress .Modify (pb .WithHijack (e .getRunStats ))
458
492
459
493
// Start all executors at their particular startTime in a separate goroutine...
@@ -469,6 +503,8 @@ func (e *Scheduler) Run(globalCtx, runCtx context.Context, samplesOut chan<- met
469
503
// Wait for all executors to finish
470
504
var firstErr error
471
505
for range e .executors {
506
+ // TODO: add logic to abort the test early if there was an error from
507
+ // the controller (e.g. some other instance for this test died)
472
508
err := <- runResults
473
509
if err != nil && firstErr == nil {
474
510
logger .WithError (err ).Debug ("Executor returned with an error, cancelling test run..." )
@@ -477,19 +513,34 @@ func (e *Scheduler) Run(globalCtx, runCtx context.Context, samplesOut chan<- met
477
513
}
478
514
}
479
515
516
+ if err := SignalAndWait (e .controller , "execution-done" ); err != nil {
517
+ return err
518
+ }
519
+
480
520
// Run teardown() after all executors are done, if it's not disabled
481
521
if ! e .state .Test .Options .NoTeardown .Bool {
482
522
e .state .SetExecutionStatus (lib .ExecutionStatusTeardown )
483
523
e .initProgress .Modify (pb .WithConstProgress (1 , "teardown()" ))
484
524
485
525
// We run teardown() with the global context, so it isn't interrupted by
486
526
// thresholds or test.abort() or even Ctrl+C (unless used twice).
487
- if err := e .state .Test .Runner .Teardown (globalCtx , samplesOut ); err != nil {
488
- logger .WithField ("error" , err ).Debug ("teardown() aborted by error" )
527
+ // TODO: add a `sync.Once` equivalent?
528
+ _ , err := e .controller .GetOrCreateData ("teardown" , func () ([]byte , error ) {
529
+ if err := e .state .Test .Runner .Teardown (globalCtx , samplesOut ); err != nil {
530
+ logger .WithField ("error" , err ).Debug ("teardown() aborted by error" )
531
+ return nil , err
532
+ }
533
+ return nil , nil
534
+ })
535
+ if err != nil {
489
536
return err
490
537
}
491
538
}
492
539
540
+ if err := SignalAndWait (e .controller , "teardown-done" ); err != nil {
541
+ return err
542
+ }
543
+
493
544
return firstErr
494
545
}
495
546
0 commit comments