Skip to content

Commit eeffb42

Browse files
committed
benchmarking(boomer): let each user class own its slice of the runtime config
dynconfig had become one struct of every knob every user class ever needed, with a payload mirror, a merge, a validator, and a log line that all had to grow by hand for each new field, and every class saw and was checked against every other class's knobs. The payload is now an arbitrary JSON object, and a worker runs exactly one user class, known at launch. So each class declares the keys it reads as a struct with json tags, wraps it in a dynconfig.Typed codec with its defaults and its validation func, and registers the codec with its userclass.Entry. The worker builds the holder from that codec; the holder stores the typed value, decodes each payload over the class's defaults, refuses a payload the class cannot run on in favor of the last good config, and hands the class its knobs back through dynconfig.Get. Keys the class does not name are ignored. The wait window and the resume and lifecycle modes are shared pieces a class embeds; durations travel as seconds, the unit the locust flags use, and print as durations in the applied-config log line. Each payload stands on its own. The master serves every flag it knows, null for the ones the operator left blank, so a field cleared in the web form takes its knob back to the class default. The old merge over the running value kept the last set value instead, with no way back to the default short of a restart. The startup --config-json still names only the keys the run sets, and the rest start at the defaults as before. The GluttonUser defaults the worker used to seed from main.go (a 500ms max wait, one ping per wake) live in that class's codec now, and the durdir read modes move into the glutton package with the class that reads them.
1 parent ff8420e commit eeffb42

21 files changed

Lines changed: 829 additions & 753 deletions

File tree

‎benchmarking/locust/common/boomer_config.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@
3535
* serve_config_headless(): the same /boomer-config payload from a plain
3636
HTTP server, for a headless run whose values change while it runs.
3737
38-
Keep _FLAGS aligned with internal/benchmarking/boomer/dynconfig.payload.
38+
_FLAGS is the set of keys the payload carries. The Go side takes the payload
39+
as an arbitrary JSON object, and the user class a worker runs reads and
40+
validates the keys it documents (the dynconfig.Typed codec it registers with
41+
its userclass.Entry), so a flag added here needs a matching field in the
42+
class that consumes it.
3943
"""
4044

4145
import argparse

‎cmd/benchmarking/boomer-worker/main.go‎

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func main() {
4747
routerURL = flag.String("router-url", "http://atenet-router.ate-system.svc.cluster.local", "atenet HTTP router base URL (no trailing slash).")
4848
atespace = flag.String("atespace", "benchmark", "Atespace every actor this worker creates lives in. Ensured (CreateAtespace, AlreadyExists is ok) at startup.")
4949
promAddr = flag.String("prometheus-addr", ":8001", "Address for the Prometheus /metrics endpoint.")
50-
configJSON = flag.String("config-json", "", "Initial dynconfig as a JSON object (keys: trace_probability, min_wait_time, max_wait_time, min_live_time, max_live_time in seconds, durdir_file_size_bytes, resume_mode, lifecycle_mode, durdir_read_mode, durdir_template, mem_target, mem_churn, mem_read, cpu_cores, cpu_duty_cycle, max_pings_per_wake). Unset fields keep their built-in defaults.")
50+
configJSON = flag.String("config-json", "", "Initial dynconfig as a JSON object keyed by locust flag name in snake_case (trace_probability, min_wait_time, ...); the selected user class reads the keys it documents and ignores the rest. Unset keys keep the class's built-in defaults. Each payload the master serves later stands on its own in the same way, so a key it leaves null is back at the default.")
5151
masterWebPort = flag.Int("master-web-port", 0, "If non-zero, fetch dynconfig from http://{master-host}:{master-web-port}/boomer-config on each spawn message. Exits if the first fetch fails; later failures keep the last fetched values. {master-host} comes from boomer's existing --master-host flag.")
5252
configPollInterval = flag.Duration("config-poll-interval", 10*time.Second, "With --master-web-port, also fetch dynconfig on this interval. A spawn message comes only when the number of users or the spawn rate changes, thus a load shape that changes the sample rate alone needs this. Zero stops the polling.")
5353
userClass = flag.String("user-class", "glutton", fmt.Sprintf("Locust user class to run, lowercase; one of %s.", strings.Join(userclass.Names(), "|")))
@@ -102,17 +102,25 @@ func main() {
102102
return
103103
}
104104

105-
initialCfg, err := dynconfig.Parse([]byte(*configJSON), dynconfig.Config{
106-
MaxWait: 500 * time.Millisecond,
107-
MaxPingsPerWake: 1,
108-
})
109-
if err != nil {
105+
entry, ok := userclass.Lookup(class)
106+
if !ok {
107+
slog.Error("fatal: unknown --user-class value",
108+
slog.String("user_class", *userClass),
109+
slog.String("known", strings.Join(userclass.Names(), ",")))
110+
os.Exit(1)
111+
}
112+
113+
// The holder is built from the class's own codec, so a --config-json
114+
// the class cannot run on fails here, and a bad value from the master
115+
// later is refused in favor of the last good config.
116+
dyn := dynconfig.NewHolder(entry.Config)
117+
if _, err := dyn.Apply([]byte(*configJSON)); err != nil {
110118
slog.Error("failed to parse --config-json", slog.String("err", err.Error()))
111119
os.Exit(1)
112120
}
113121

114122
ctx := context.Background()
115-
sampler := btrace.NewUpdatableSampler(initialCfg.TraceProbability)
123+
sampler := btrace.NewUpdatableSampler(dyn.Common().TraceProbability)
116124
tp, err := btrace.Init(ctx, "substrate-boomer", sampler)
117125
if err != nil {
118126
slog.Error("failed to initialize tracing", slog.String("err", err.Error()))
@@ -144,8 +152,6 @@ func main() {
144152
transport.IdleConnTimeout = 5 * time.Minute
145153
httpClient := &http.Client{Timeout: 30 * time.Second, Transport: transport}
146154

147-
dyn := dynconfig.NewHolder(initialCfg)
148-
149155
if *masterWebPort > 0 {
150156
masterHost := flag.Lookup("master-host").Value.String()
151157
configURL := fmt.Sprintf("http://%s:%d/boomer-config", masterHost, *masterWebPort)
@@ -195,13 +201,6 @@ func main() {
195201
ActorDeadline: time.Duration(*actorDeadline * float64(time.Second)),
196202
}
197203

198-
entry, ok := userclass.Lookup(class)
199-
if !ok {
200-
slog.Error("fatal: unknown --user-class value",
201-
slog.String("user_class", *userClass),
202-
slog.String("known", strings.Join(userclass.Names(), ",")))
203-
os.Exit(1)
204-
}
205204
taskFn, shutdownFn := entry.Init(cfg)
206205

207206
slog.Info("registered boomer task",

‎internal/benchmarking/boomer/agentsession/agentsession.go‎

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,36 @@ func init() {
9696
Name: "agentsession",
9797
LocustFile: "agentsession.py",
9898
UserClass: agentSessionUserClass,
99+
Config: knobsCodec,
99100
Init: initAgentSession,
100101
})
101102
}
102103

104+
// knobs is the AgentSessionUser slice of the runtime config. The master
105+
// populates the keys from the --agentsession-* locust flags
106+
// (common/agentsession_config.py).
107+
type knobs struct {
108+
dynconfig.Lifecycle
109+
// Script is the built-in script variant; "" falls back to the default.
110+
Script string `json:"agentsession_script"`
111+
// ScriptFile is a script YAML on the worker; wins over Script when set.
112+
ScriptFile string `json:"agentsession_script_file"`
113+
// ThinkScale multiplies the script's per-step think times; 0 reads as 1.
114+
ThinkScale float64 `json:"agentsession_think_scale"`
115+
}
116+
117+
var knobsCodec = dynconfig.Typed[knobs]{
118+
Validate: func(k knobs) error {
119+
if err := k.Lifecycle.Validate(); err != nil {
120+
return err
121+
}
122+
if k.ThinkScale < 0 {
123+
return fmt.Errorf("agentsession_think_scale cannot be negative: %f", k.ThinkScale)
124+
}
125+
return nil
126+
},
127+
}
128+
103129
func initAgentSession(cfg *userclass.Config) (taskFn func(), shutdown func(context.Context)) {
104130
if cfg.Tracer == nil {
105131
cfg.Tracer = otel.Tracer("substrate-boomer/agentsession")
@@ -177,12 +203,12 @@ type loadedScript struct {
177203
// scriptSource is what the knobs select: a file named by
178204
// --agentsession-script-file wins, else the built-in variant named by
179205
// --agentsession-script, else the default.
180-
func scriptSource(dyn dynconfig.Config) (source string, fromFile bool) {
181-
if dyn.AgentSessionScriptFile != "" {
182-
return dyn.AgentSessionScriptFile, true
206+
func scriptSource(dyn knobs) (source string, fromFile bool) {
207+
if dyn.ScriptFile != "" {
208+
return dyn.ScriptFile, true
183209
}
184-
if dyn.AgentSessionScript != "" {
185-
return dyn.AgentSessionScript, false
210+
if dyn.Script != "" {
211+
return dyn.Script, false
186212
}
187213
return DefaultScript, false
188214
}
@@ -198,7 +224,7 @@ func scriptSource(dyn dynconfig.Config) (source string, fromFile bool) {
198224
func (r *runtime) loadScript() (*loadedScript, error) {
199225
r.scriptMu.Lock()
200226
defer r.scriptMu.Unlock()
201-
source, fromFile := scriptSource(r.cfg.Dyn.Load())
227+
source, fromFile := scriptSource(dynconfig.Get[knobs](r.cfg.Dyn))
202228
data, err := readScript(source, fromFile)
203229
if err != nil {
204230
return nil, err
@@ -292,7 +318,7 @@ func memoryLimit(tmpl *ateapipb.ActorTemplate) (limit int64, found bool, err err
292318
// script's think time scaled by --agentsession-think-scale (0 reads as 1.0),
293319
// with ±20% jitter so a fleet of sessions doesn't move in lockstep.
294320
func (r *runtime) think(s Step) time.Duration {
295-
scale := r.cfg.Dyn.Load().AgentSessionThinkScale
321+
scale := dynconfig.Get[knobs](r.cfg.Dyn).ThinkScale
296322
if scale <= 0 {
297323
scale = 1.0
298324
}
@@ -517,7 +543,7 @@ func (u *sessionUser) runStep(ctx context.Context, step Step) bool {
517543
return false
518544
}
519545

520-
if u.cfg.Dyn.Load().ResumeMode == dynconfig.ResumeModeExplicit {
546+
if dynconfig.Get[knobs](u.cfg.Dyn).ResumeMode == dynconfig.ResumeModeExplicit {
521547
if err := u.resume(ctx); err != nil {
522548
u.noteFailure(err)
523549
return false
@@ -752,7 +778,7 @@ func (u *sessionUser) resume(ctx context.Context) error {
752778
// it instead of waking a stranded actor.
753779
func (u *sessionUser) hibernate(ctx context.Context) {
754780
var err error
755-
if u.cfg.Dyn.Load().LifecycleMode == dynconfig.LifecycleModePause {
781+
if dynconfig.Get[knobs](u.cfg.Dyn).LifecycleMode == dynconfig.LifecycleModePause {
756782
err = u.tracedCall(ctx, "PauseActor", func(callCtx context.Context, tr *metadata.MD) error {
757783
_, err := u.cfg.APIStub.PauseActor(callCtx, &ateapipb.PauseActorRequest{Actor: u.ref()}, grpc.Trailer(tr))
758784
return err

‎internal/benchmarking/boomer/agentsession/agentsession_test.go‎

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ func TestExecOpAgainstFake(t *testing.T) {
221221
HTTPClient: http.DefaultClient,
222222
RouterURL: ts.URL,
223223
Atespace: "benchmark",
224-
Dyn: dynconfig.NewHolder(dynconfig.Config{}),
224+
Dyn: dynconfig.Static(knobs{}),
225225
},
226226
actorName: "agent-test",
227227
}
@@ -270,15 +270,15 @@ func TestExecOpAgainstFake(t *testing.T) {
270270
// error that leaves the previous script in place, and a changed knob loads
271271
// the new script for sessions that start afterwards.
272272
func TestLoadScriptFollowsTheKnob(t *testing.T) {
273-
rt := &runtime{cfg: &userclass.Config{Dyn: dynconfig.NewHolder(dynconfig.Config{AgentSessionScript: "no-such-script"})}}
273+
rt := &runtime{cfg: &userclass.Config{Dyn: dynconfig.Static(knobs{Script: "no-such-script"})}}
274274
if _, err := rt.loadScript(); err == nil {
275275
t.Fatal("loadScript accepted an unknown script name")
276276
}
277277
if rt.loaded != nil {
278278
t.Fatal("a failed load must not cache a script")
279279
}
280280

281-
rt.cfg.Dyn.Store(dynconfig.Config{})
281+
rt.cfg.Dyn = dynconfig.Static(knobs{})
282282
s, err := rt.loadScript()
283283
if err != nil {
284284
t.Fatal(err)
@@ -295,7 +295,7 @@ func TestLoadScriptFollowsTheKnob(t *testing.T) {
295295

296296
// A knob that names something that does not load is an error, and the
297297
// previous script stays available to sessions that already run on it.
298-
rt.cfg.Dyn.Store(dynconfig.Config{AgentSessionScript: "no-such-script"})
298+
rt.cfg.Dyn = dynconfig.Static(knobs{Script: "no-such-script"})
299299
if _, err := rt.loadScript(); err == nil {
300300
t.Error("loadScript accepted an unknown name after a successful load")
301301
}
@@ -308,7 +308,7 @@ func TestLoadScriptFollowsTheKnob(t *testing.T) {
308308
if err := os.WriteFile(path, []byte(validScript), 0o600); err != nil {
309309
t.Fatal(err)
310310
}
311-
rt.cfg.Dyn.Store(dynconfig.Config{AgentSessionScriptFile: path})
311+
rt.cfg.Dyn = dynconfig.Static(knobs{ScriptFile: path})
312312
next, err := rt.loadScript()
313313
if err != nil {
314314
t.Fatal(err)
@@ -343,7 +343,7 @@ func TestLoadScriptFollowsTheKnob(t *testing.T) {
343343
func TestShutdownFansOut(t *testing.T) {
344344
const sessions = 40
345345
ctl := &fakeControlClient{deleteDelay: 20 * time.Millisecond}
346-
u := newTestUser(t, &fake.Server{}, ctl, dynconfig.Config{})
346+
u := newTestUser(t, &fake.Server{}, ctl, knobs{})
347347
rt := &runtime{cfg: u.cfg}
348348
for i := range sessions {
349349
rt.users.Store(int64(i), &sessionUser{cfg: u.cfg, actorName: "agent-" + strconv.Itoa(i)})
@@ -403,9 +403,9 @@ func TestLoadScriptPrefersFile(t *testing.T) {
403403
if err := os.WriteFile(path, []byte(validScript), 0o600); err != nil {
404404
t.Fatal(err)
405405
}
406-
rt := &runtime{cfg: &userclass.Config{Dyn: dynconfig.NewHolder(dynconfig.Config{
407-
AgentSessionScript: DefaultScript,
408-
AgentSessionScriptFile: path,
406+
rt := &runtime{cfg: &userclass.Config{Dyn: dynconfig.Static(knobs{
407+
Script: DefaultScript,
408+
ScriptFile: path,
409409
})}}
410410
s, err := rt.loadScript()
411411
if err != nil {
@@ -436,7 +436,7 @@ func TestStartUserChecksTemplateMemory(t *testing.T) {
436436
} {
437437
t.Run(tc.name, func(t *testing.T) {
438438
ctl := &fakeControlClient{templateMemory: tc.memory}
439-
u := newTestUser(t, &fake.Server{}, ctl, dynconfig.Config{})
439+
u := newTestUser(t, &fake.Server{}, ctl, knobs{})
440440
rt := &runtime{cfg: u.cfg}
441441

442442
started, err := rt.startUser(context.Background(), &loadedScript{Script: script, ingestBuf: makeIngestBuf(script.Steps)})
@@ -469,7 +469,7 @@ func TestTemplateMemoryRefusalExpires(t *testing.T) {
469469
t.Fatal(err)
470470
}
471471
ctl := &fakeControlClient{templateMemory: "512Mi"}
472-
u := newTestUser(t, &fake.Server{}, ctl, dynconfig.Config{})
472+
u := newTestUser(t, &fake.Server{}, ctl, knobs{})
473473
rt := &runtime{cfg: u.cfg}
474474

475475
if err := rt.checkTemplateMemory(context.Background(), script); err == nil {
@@ -495,7 +495,7 @@ func TestTemplateMemoryRefusalExpires(t *testing.T) {
495495
// start afterwards, not one already walking its steps.
496496
func TestRunningSessionKeepsItsScript(t *testing.T) {
497497
ctl := &fakeControlClient{templateMemory: "1Gi"}
498-
u := newTestUser(t, &fake.Server{}, ctl, dynconfig.Config{})
498+
u := newTestUser(t, &fake.Server{}, ctl, knobs{})
499499
rt := &runtime{cfg: u.cfg}
500500
first, err := rt.loadScript()
501501
if err != nil {
@@ -510,7 +510,7 @@ func TestRunningSessionKeepsItsScript(t *testing.T) {
510510
if err := os.WriteFile(path, []byte(validScript), 0o600); err != nil {
511511
t.Fatal(err)
512512
}
513-
rt.cfg.Dyn.Store(dynconfig.Config{AgentSessionScriptFile: path})
513+
rt.cfg.Dyn = dynconfig.Static(knobs{ScriptFile: path})
514514
second, err := rt.loadScript()
515515
if err != nil {
516516
t.Fatal(err)
@@ -524,7 +524,7 @@ func TestRunningSessionKeepsItsScript(t *testing.T) {
524524
func TestThinkScaling(t *testing.T) {
525525
r := &runtime{
526526
cfg: &userclass.Config{
527-
Dyn: dynconfig.NewHolder(dynconfig.Config{AgentSessionThinkScale: 0.5}),
527+
Dyn: dynconfig.Static(knobs{ThinkScale: 0.5}),
528528
},
529529
}
530530
s := Step{Think: 10 * time.Second}
@@ -536,7 +536,7 @@ func TestThinkScaling(t *testing.T) {
536536
}
537537

538538
// Zero scale reads as 1.0.
539-
r.cfg.Dyn.Store(dynconfig.Config{})
539+
r.cfg.Dyn = dynconfig.Static(knobs{})
540540
for i := 0; i < 100; i++ {
541541
got := r.think(s)
542542
if got < 8*time.Second || got > 12*time.Second {
@@ -659,7 +659,7 @@ func countCalls(calls []string, name string) int {
659659
return n
660660
}
661661

662-
func newTestUser(t *testing.T, srv *fake.Server, ctl *fakeControlClient, dyn dynconfig.Config) *sessionUser {
662+
func newTestUser(t *testing.T, srv *fake.Server, ctl *fakeControlClient, dyn knobs) *sessionUser {
663663
t.Helper()
664664
ts := srv.Start(t)
665665
return &sessionUser{
@@ -668,7 +668,7 @@ func newTestUser(t *testing.T, srv *fake.Server, ctl *fakeControlClient, dyn dyn
668668
HTTPClient: ts.Client(),
669669
RouterURL: ts.URL,
670670
Atespace: "benchmark",
671-
Dyn: dynconfig.NewHolder(dyn),
671+
Dyn: dynconfig.Static(dyn),
672672
Tracer: otel.Tracer("test"),
673673
},
674674
actorName: "agent-test",
@@ -683,7 +683,7 @@ var pingStep = Step{Name: "01_test", Agent: "testing", Think: time.Second, Ops:
683683
func TestRunStep_RetriesStrandedHibernate(t *testing.T) {
684684
srv := &fake.Server{}
685685
ctl := &fakeControlClient{suspendErrs: []error{status.Error(codes.Unavailable, "ate-api-server restarting")}}
686-
u := newTestUser(t, srv, ctl, dynconfig.Config{})
686+
u := newTestUser(t, srv, ctl, knobs{})
687687

688688
if !u.runStep(context.Background(), pingStep) {
689689
t.Fatal("runStep = false; the step's ops succeeded and must count")
@@ -716,7 +716,7 @@ func TestRunStep_RetriesStrandedHibernate(t *testing.T) {
716716
// session must replace it on the first failure, not the third.
717717
func TestRunStep_ReplacesCrashedActorImmediately(t *testing.T) {
718718
ctl := &fakeControlClient{resumeErrs: []error{status.Error(codes.Aborted, "actor benchmark/agent-test crashed")}}
719-
u := newTestUser(t, &fake.Server{}, ctl, dynconfig.Config{ResumeMode: dynconfig.ResumeModeExplicit})
719+
u := newTestUser(t, &fake.Server{}, ctl, knobs{Lifecycle: dynconfig.Lifecycle{ResumeMode: dynconfig.ResumeModeExplicit}})
720720

721721
if u.runStep(context.Background(), pingStep) {
722722
t.Fatal("runStep = true with a crashed actor")
@@ -731,7 +731,7 @@ func TestRunStep_ReplacesCrashedActorImmediately(t *testing.T) {
731731
// a state nothing the driver can call moves the actor out of.
732732
func TestRunStep_ReplacesStuckActorAfterFailedWake(t *testing.T) {
733733
ctl := &fakeControlClient{suspendErrs: []error{status.Error(codes.FailedPrecondition, "MarkSuspending prerequisite not met (got: ACTOR_STATE_CRASHED)")}}
734-
u := newTestUser(t, &fake.Server{Status: 503}, ctl, dynconfig.Config{})
734+
u := newTestUser(t, &fake.Server{Status: 503}, ctl, knobs{})
735735

736736
if u.runStep(context.Background(), pingStep) {
737737
t.Fatal("runStep = true with a failing router")
@@ -750,7 +750,7 @@ func TestRunStep_KeepsActorThroughCapacityShortage(t *testing.T) {
750750
errs[i] = status.Error(codes.ResourceExhausted, "no worker has room for the actor")
751751
}
752752
ctl := &fakeControlClient{resumeErrs: errs}
753-
u := newTestUser(t, &fake.Server{}, ctl, dynconfig.Config{ResumeMode: dynconfig.ResumeModeExplicit})
753+
u := newTestUser(t, &fake.Server{}, ctl, knobs{Lifecycle: dynconfig.Lifecycle{ResumeMode: dynconfig.ResumeModeExplicit}})
754754

755755
for range rounds {
756756
if u.runStep(context.Background(), pingStep) {
@@ -769,7 +769,7 @@ func TestRunStep_KeepsActorThroughRouterCapacityErrors(t *testing.T) {
769769
for _, status := range []int{503, 504, 429} {
770770
t.Run(strconv.Itoa(status), func(t *testing.T) {
771771
ctl := &fakeControlClient{}
772-
u := newTestUser(t, &fake.Server{Status: status}, ctl, dynconfig.Config{})
772+
u := newTestUser(t, &fake.Server{Status: status}, ctl, knobs{})
773773
for range maxConsecutiveStepFailures + 2 {
774774
if u.runStep(context.Background(), pingStep) {
775775
t.Fatal("runStep = true with a failing router")
@@ -786,7 +786,7 @@ func TestRunStep_KeepsActorThroughRouterCapacityErrors(t *testing.T) {
786786
// brings it back, so it is replaced on the first failure.
787787
func TestRunStep_ReplacesActorOnRouterNotFound(t *testing.T) {
788788
ctl := &fakeControlClient{}
789-
u := newTestUser(t, &fake.Server{Status: 404}, ctl, dynconfig.Config{})
789+
u := newTestUser(t, &fake.Server{Status: 404}, ctl, knobs{})
790790
u.runStep(context.Background(), pingStep)
791791
if !u.broken {
792792
t.Error("broken = false after a 404 wake; want immediate replacement")
@@ -798,7 +798,7 @@ func TestRunStep_ReplacesActorOnRouterNotFound(t *testing.T) {
798798
// after maxConsecutiveStepFailures steps.
799799
func TestRunStep_ReplacesActorAfterRepeatedStepFailures(t *testing.T) {
800800
ctl := &fakeControlClient{}
801-
u := newTestUser(t, &fake.Server{Status: 502}, ctl, dynconfig.Config{})
801+
u := newTestUser(t, &fake.Server{Status: 502}, ctl, knobs{})
802802

803803
for i := 1; i <= maxConsecutiveStepFailures; i++ {
804804
u.runStep(context.Background(), pingStep)
@@ -810,7 +810,7 @@ func TestRunStep_ReplacesActorAfterRepeatedStepFailures(t *testing.T) {
810810

811811
func TestControlRPCsCarryADeadline(t *testing.T) {
812812
ctl := &fakeControlClient{}
813-
u := newTestUser(t, &fake.Server{}, ctl, dynconfig.Config{ResumeMode: dynconfig.ResumeModeExplicit})
813+
u := newTestUser(t, &fake.Server{}, ctl, knobs{Lifecycle: dynconfig.Lifecycle{ResumeMode: dynconfig.ResumeModeExplicit}})
814814

815815
u.runStep(context.Background(), pingStep)
816816
u.suspendAndDelete(context.Background())
@@ -845,7 +845,7 @@ func TestDwell(t *testing.T) {
845845
HTTPClient: http.DefaultClient,
846846
RouterURL: ts.URL,
847847
Atespace: "benchmark",
848-
Dyn: dynconfig.NewHolder(dynconfig.Config{}),
848+
Dyn: dynconfig.Static(knobs{}),
849849
},
850850
actorName: "agent-test",
851851
}

0 commit comments

Comments
 (0)