Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
cc73e2c
Wait for iteration callbacks before completing run
stephanos Sep 8, 2026
1759295
Document iteration completion ordering
stephanos Sep 8, 2026
ae80743
Clarify iteration completion comments
stephanos Sep 8, 2026
d70ec3e
Update generic_executor.go
stephanos Sep 8, 2026
4e344af
Update generic_executor.go
stephanos Sep 8, 2026
8e19c1a
Preserve failure reporting when runs are canceled
stephanos Sep 8, 2026
3f0b051
Reuse executor setup in cancellation tests
stephanos Sep 8, 2026
66323cb
Update generic_executor_test.go
stephanos Sep 8, 2026
64302ef
Clarify cancellation test name
stephanos Sep 8, 2026
3aefefa
Update generic_executor.go
stephanos Sep 8, 2026
165a7b3
Exercise Nexus workflow messaging in throughput stress
stephanos Sep 5, 2026
69b6fe8
Address Nexus throughput review feedback
stephanos Sep 7, 2026
45ae3a7
Simplify Nexus workflow action configuration test
stephanos Sep 8, 2026
cf1700c
Address remaining Nexus throughput review feedback
stephanos Sep 8, 2026
f857579
Inline Nexus throughput action setup
stephanos Sep 8, 2026
0087dca
Update throughput_stress.go
stephanos Sep 8, 2026
c8dcd9e
Make Nexus throughput signals explicit
stephanos Sep 8, 2026
730d7f7
Simplify Nexus throughput action tests
stephanos Sep 8, 2026
fe65d63
Complete Nexus throughput workflow targets
stephanos Sep 8, 2026
dff3e8c
Document Nexus throughput action ordering
stephanos Sep 8, 2026
a53002e
Exercise Nexus throughput workflow actions
stephanos Sep 8, 2026
1e764e4
fix
stephanos Sep 9, 2026
3acf594
Inline Nexus action test setup
stephanos Sep 9, 2026
1395c07
simpler
stephanos Sep 9, 2026
b27e90f
Restore Nexus action ordering comment
stephanos Sep 9, 2026
388f659
Update throughput_stress_test.go
stephanos Sep 9, 2026
143ea02
Exercise Nexus workflow action callbacks
stephanos Sep 9, 2026
b4e0f1b
Simplify Nexus workflow action sequencing
stephanos Sep 9, 2026
581ed81
Simplify Nexus workflow action test
stephanos Sep 9, 2026
54b57dc
Update throughput_stress_test.go
stephanos Sep 9, 2026
b48e064
Simplify Nexus target action assertions
stephanos Sep 9, 2026
3232d06
Combine Nexus workflow action options
stephanos Sep 9, 2026
ca4a8e1
Address Nexus throughput review feedback
stephanos Sep 9, 2026
4d5cb43
Merge remote-tracking branch 'origin/main' into fix-generic-executor-…
stephanos Sep 9, 2026
5194747
Merge branch 'fix-generic-executor-completion-order' into nexus-sdk-e…
stephanos Sep 9, 2026
fd97400
tweak
stephanos Sep 9, 2026
4c8e104
Update throughput_stress_test.go
stephanos Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions docs/throughput-stress.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ is any.

Asking for `include-standalone-nexus=true` while Nexus is off is a contradiction, and fails the run.

## Nexus operation with a standalone activity
## Nexus operation actions

`include-nexus-standalone-activity` adds a standalone activity backed Nexus operation.
It is driven two ways each iteration: As an in-workflow Nexus operation, and — when standalone Nexus is part of the run —
as a standalone Nexus operation.
### `include-nexus-workflow-actions`

This is **opt-in and off by default**; pass `--option include-nexus-standalone-activity=true`.
It requires `nexus-enabled` and also needs server support for standalone activities and activity
completion callbacks (dynamic config `activity.enableStandalone` and `activity.enableCallbacks`) and a
Nexus callback URL; if those are off the operation fails clearly rather than being skipped.
The workflow actions start their target workflow with signal-with-start, update it, then send an
ordinary signal that completes it. This requires Nexus update callback support.

Currently only supported and run by Go workers.
### `include-nexus-standalone-activity`

The standalone activity action is driven two ways each iteration: as an in-workflow Nexus operation
and, when standalone Nexus is part of the run, as a standalone Nexus operation. This requires
standalone activity and callback support.
32 changes: 17 additions & 15 deletions loadgen/generic_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,24 +154,26 @@ func (g *genericRun) Run(ctx context.Context) error {
// cancellation while the run is healthy.
stopping := iterErr != nil && ctx.Err() != nil && errors.Is(iterErr, context.Canceled)

switch {
case stopping:
g.logger.Debugf("Iteration %v abandoned: run is stopping", run.Iteration)
case iterErr == nil:
run.Duration = elapsed
g.completed.Add(1)
if g.config.OnCompletion != nil {
g.config.OnCompletion(ctx, run)
}
default:
g.failed.Add(1)
if g.config.OnIterationFailure != nil {
g.config.OnIterationFailure(ctx, run, iterErr)
}
}

// Notify the waiter after callbacks finish so Run cannot return before they do.
select {
case <-ctx.Done():
case doneCh <- err:
switch {
case stopping:
g.logger.Debugf("Iteration %v abandoned: run is stopping", run.Iteration)
case iterErr == nil:
run.Duration = elapsed
g.completed.Add(1)
if g.config.OnCompletion != nil {
g.config.OnCompletion(ctx, run)
}
default:
g.failed.Add(1)
if g.config.OnIterationFailure != nil {
g.config.OnIterationFailure(ctx, run, iterErr)
}
}
}
}()

Expand Down
112 changes: 89 additions & 23 deletions loadgen/generic_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,18 @@ func (i *iterationTracker) assertSeen(t *testing.T, iterations int) {
}

func execute(executor *GenericExecutor, runConfig RunConfiguration) error {
return executeContext(context.Background(), executor, runConfig)
}

func executeContext(ctx context.Context, executor *GenericExecutor, runConfig RunConfiguration) error {
logger := zap.Must(zap.NewDevelopment())
defer logger.Sync()
info := ScenarioInfo{
MetricsHandler: client.MetricsNopHandler,
Logger: logger.Sugar(),
Configuration: runConfig,
}
return executor.Run(context.Background(), info)
return executor.Run(ctx, info)
}

func TestRunHappyPathIterations(t *testing.T) {
Expand All @@ -62,6 +66,44 @@ func TestRunHappyPathIterations(t *testing.T) {
})
}

func TestRunWaitsForOnCompletion(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
callbackStarted := make(chan struct{})
releaseCallback := make(chan struct{})
runDone := make(chan error, 1)

go func() {
runDone <- execute(&GenericExecutor{
Execute: func(ctx context.Context, run *Run) error {
return nil
}},
RunConfiguration{
Iterations: 1,
OnCompletion: func(ctx context.Context, run *Run) {
close(callbackStarted)
<-releaseCallback
},
},
)
}()

<-callbackStarted

synctest.Wait()

returned := false
select {
case <-runDone:
returned = true
default:
}
close(releaseCallback)

require.False(t, returned, "executor returned before OnCompletion finished")
require.NoError(t, <-runDone)
})
}

func TestRunFailIterations(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
tracker := newIterationTracker()
Expand Down Expand Up @@ -278,6 +320,38 @@ func TestRunContinueOnIterationFailure(t *testing.T) {
})
}

func TestRunReportsNonCancellationFailureAfterCancellation(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

failureReported := make(chan struct{}, 1)
err := executeContext(ctx, &GenericExecutor{
Execute: func(ctx context.Context, run *Run) error {
cancel()
return errors.New("deliberate fail from test")
},
}, RunConfiguration{
Iterations: 1,
ContinueOnIterationFailure: true,
OnIterationFailure: func(ctx context.Context, run *Run, err error) {
failureReported <- struct{}{}
},
})
require.Error(t, err)

synctest.Wait()

reported := false
select {
case <-failureReported:
reported = true
default:
}
require.True(t, reported, "non-cancellation error should be reported as a failure")
})
}

// TestRunStoppedIterationsAreNotCountedAsFailures pins that iterations abandoned
// by a caller stopping the run are left out of the tallies, so a clean stop is
// not reported as a burst of failures.
Expand All @@ -291,7 +365,7 @@ func TestRunStoppedIterationsAreNotCountedAsFailures(t *testing.T) {
defer cancel()

var inFlight int
executor := &GenericExecutor{
err := executeContext(ctx, &GenericExecutor{
Execute: func(ctx context.Context, run *Run) error {
mu.Lock()
inFlight++
Expand All @@ -306,27 +380,19 @@ func TestRunStoppedIterationsAreNotCountedAsFailures(t *testing.T) {
<-ctx.Done()
return ctx.Err()
},
}

logger := zap.Must(zap.NewDevelopment())
defer logger.Sync()
err := executor.Run(ctx, ScenarioInfo{
MetricsHandler: client.MetricsNopHandler,
Logger: logger.Sugar(),
Configuration: RunConfiguration{
Iterations: 100,
MaxConcurrent: concurrent,
ContinueOnIterationFailure: true,
OnCompletion: func(ctx context.Context, run *Run) {
mu.Lock()
defer mu.Unlock()
completed = append(completed, run.Iteration)
},
OnIterationFailure: func(ctx context.Context, run *Run, err error) {
mu.Lock()
defer mu.Unlock()
failed = append(failed, run.Iteration)
},
}, RunConfiguration{
Iterations: 100,
MaxConcurrent: concurrent,
ContinueOnIterationFailure: true,
OnCompletion: func(ctx context.Context, run *Run) {
mu.Lock()
defer mu.Unlock()
completed = append(completed, run.Iteration)
},
OnIterationFailure: func(ctx context.Context, run *Run, err error) {
mu.Lock()
defer mu.Unlock()
failed = append(failed, run.Iteration)
},
})

Expand Down
Loading
Loading