Skip to content

Commit a046c79

Browse files
[Scheduler] Add a new flag to allow only migration of schedules without running workflows (#10505)
## What changed? - Added a new dynamic config flag to control whether V1 schedules will migrate with running workflows. ## Why? - When V1 schedules migrate to V2 with running workflows, V2 attaches a completion callback to the running workflow (as V2 exclusively uses Nexus completion callbacks to monitor workflow status). Certain third-party SDKs are known to have issues with one of the events written to the history of the running workflow, which can cause them to panic (Coinbase SDK). This allows us to dial up migration for these customers without triggering that edge case. ## How did you test it? - [ ] built - [ ] run locally and tested manually - [ ] covered by existing tests - [ ] added new unit test(s) - [x] added new functional test(s) ## Potential risks - Slower migration cadence, potentially
1 parent 23c74ad commit a046c79

5 files changed

Lines changed: 201 additions & 22 deletions

File tree

common/dynamicconfig/constants.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3000,6 +3000,14 @@ EnableCHASMSchedulerMigration is true. The decision is re-evaluated when a
30003000
scheduler workflow starts or continues-as-new.`,
30013001
)
30023002

3003+
EnableCHASMSchedulerMigrationWithRunningWorkflows = NewNamespaceBoolSetting(
3004+
"history.enableCHASMSchedulerMigrationWithRunningWorkflows",
3005+
false,
3006+
`EnableCHASMSchedulerMigrationWithRunningWorkflows, when set to false, prevents schedules with
3007+
running workflows from being migrated. This works around a known bug in 3P SDKs involving updating
3008+
existing workflows to attach callbacks.`,
3009+
)
3010+
30033011
EnableCHASMSchedulerSentinels = NewNamespaceBoolSetting(
30043012
"history.enableCHASMSchedulerSentinels",
30053013
true,

service/worker/scheduler/fx.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type (
5454
enabledForNs dynamicconfig.BoolPropertyFnWithNamespaceFilter
5555
enableCHASMMigration dynamicconfig.BoolPropertyFnWithNamespaceFilter
5656
chasmMigrationRolloutPercent dynamicconfig.IntPropertyFnWithNamespaceFilter
57+
migrateWithRunningWorkflows dynamicconfig.BoolPropertyFnWithNamespaceFilter
5758
globalNSStartWorkflowRPS dynamicconfig.TypedSubscribableWithNamespaceFilter[float64]
5859
maxBlobSize dynamicconfig.IntPropertyFnWithNamespaceFilter
5960
localActivitySleepLimit dynamicconfig.DurationPropertyFnWithNamespaceFilter
@@ -91,6 +92,7 @@ func NewResult(
9192
enabledForNs: dynamicconfig.WorkerEnableScheduler.Get(dc),
9293
enableCHASMMigration: dynamicconfig.EnableCHASMSchedulerMigration.Get(dc),
9394
chasmMigrationRolloutPercent: dynamicconfig.CHASMSchedulerMigrationRolloutPercent.Get(dc),
95+
migrateWithRunningWorkflows: dynamicconfig.EnableCHASMSchedulerMigrationWithRunningWorkflows.Get(dc),
9496
globalNSStartWorkflowRPS: dynamicconfig.SchedulerNamespaceStartWorkflowRPS.Subscribe(dc),
9597
maxBlobSize: dynamicconfig.BlobSizeLimitError.Get(dc),
9698
localActivitySleepLimit: dynamicconfig.SchedulerLocalActivitySleepLimit.Get(dc),
@@ -112,7 +114,10 @@ func (s *workerComponent) Register(registry sdkworker.Registry, ns *namespace.Na
112114
return s.enableCHASMMigration(nsName) &&
113115
dynamicconfig.RolloutAccepts(key, s.chasmMigrationRolloutPercent(nsName))
114116
}
115-
return schedulerWorkflowWithSpecBuilder(ctx, args, s.specBuilder, enableMigration)
117+
migrateWithRunningWorkflows := func() bool {
118+
return s.migrateWithRunningWorkflows(nsName)
119+
}
120+
return schedulerWorkflowWithSpecBuilder(ctx, args, s.specBuilder, enableMigration, migrateWithRunningWorkflows)
116121
}
117122
registry.RegisterWorkflowWithOptions(wfFunc, workflow.RegisterOptions{Name: WorkflowType})
118123

service/worker/scheduler/workflow.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,10 @@ type (
114114
// without modifying state).
115115
specBuilder *SpecBuilder
116116
cspec *CompiledSpec
117-
// enableCHASMMigration is re-evaluated every iteration inside the
118-
// "tweakables" MutableSideEffect.
119-
enableCHASMMigration func() bool
117+
// enableCHASMMigration and migrateWithRunningWorkflows are re-evaluated
118+
// every iteration inside the "tweakables" MutableSideEffect.
119+
enableCHASMMigration func() bool
120+
migrateWithRunningWorkflows func() bool
120121

121122
tweakables TweakablePolicies
122123

@@ -161,7 +162,8 @@ type (
161162
Version SchedulerWorkflowVersion // Used to keep track of schedules version to release new features and for backward compatibility
162163
// version 0 corresponds to the schedule version that comes before introducing the Version parameter
163164

164-
EnableCHASMMigration bool // Whether to automatically migrate this schedule to CHASM (V2)
165+
EnableCHASMMigration bool // Whether to automatically migrate this schedule to CHASM (V2)
166+
MigrateWithRunningWorkflows bool // Whether to migrate this schedule to CHASM (V2) while it has running workflows
165167

166168
// When introducing a new field with new workflow logic, consider generating a new
167169
// history for TestReplays using generate_history.sh.
@@ -226,10 +228,11 @@ var (
226228
)
227229

228230
func SchedulerWorkflow(ctx workflow.Context, args *schedulespb.StartScheduleArgs) error {
229-
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(), func() bool { return false })
231+
disabled := func() bool { return false }
232+
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(), disabled, disabled)
230233
}
231234

232-
func schedulerWorkflowWithSpecBuilder(ctx workflow.Context, args *schedulespb.StartScheduleArgs, specBuilder *SpecBuilder, enableCHASMMigration func() bool) error {
235+
func schedulerWorkflowWithSpecBuilder(ctx workflow.Context, args *schedulespb.StartScheduleArgs, specBuilder *SpecBuilder, enableCHASMMigration func() bool, migrateWithRunningWorkflows func() bool) error {
233236
scheduler := &scheduler{
234237
StartScheduleArgs: args,
235238
ctx: ctx,
@@ -239,8 +242,9 @@ func schedulerWorkflowWithSpecBuilder(ctx workflow.Context, args *schedulespb.St
239242
"namespace": args.State.Namespace,
240243
metrics.ScheduleBackendTag: metrics.ScheduleBackendLegacy,
241244
}),
242-
specBuilder: specBuilder,
243-
enableCHASMMigration: enableCHASMMigration,
245+
specBuilder: specBuilder,
246+
enableCHASMMigration: enableCHASMMigration,
247+
migrateWithRunningWorkflows: migrateWithRunningWorkflows,
244248
}
245249
return scheduler.run()
246250
}
@@ -324,7 +328,8 @@ func (s *scheduler) run() error {
324328
)
325329
}
326330

327-
if s.tweakables.EnableCHASMMigration {
331+
if !s.State.PendingMigration && s.tweakables.EnableCHASMMigration &&
332+
(s.tweakables.MigrateWithRunningWorkflows || len(s.Info.RunningWorkflows) == 0) {
328333
s.State.PendingMigration = true
329334
}
330335
if s.State.PendingMigration {
@@ -1271,6 +1276,7 @@ func (s *scheduler) updateTweakables() {
12711276
p := CurrentTweakablePolicies
12721277
// Re-evaluates migration dynamic config each iteration.
12731278
p.EnableCHASMMigration = s.enableCHASMMigration()
1279+
p.MigrateWithRunningWorkflows = s.migrateWithRunningWorkflows()
12741280
return p
12751281
}
12761282
eq := func(a, b any) bool { return a.(TweakablePolicies) == b.(TweakablePolicies) }

service/worker/scheduler/workflow_test.go

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,14 +2275,19 @@ func (s *workflowSuite) TestMigrateSuccess() {
22752275
// Mock MigrateSchedule activity to succeed.
22762276
s.env.OnActivity(new(activities).MigrateScheduleToChasm, mock.Anything, mock.Anything).Once().Return(nil)
22772277

2278-
// Send migrate signal after the first iteration.
2278+
// Enable migration and request it via signal after the first iteration.
2279+
enableMigration := false
22792280
s.env.RegisterDelayedCallback(func() {
2281+
enableMigration = true
22802282
s.env.SignalWorkflow(SignalNameMigrateToChasm, nil)
22812283
}, 1*time.Second)
22822284

22832285
CurrentTweakablePolicies.IterationsBeforeContinueAsNew = 100
22842286
s.env.SetStartTime(baseStartTime)
2285-
s.env.ExecuteWorkflow(SchedulerWorkflow, &schedulespb.StartScheduleArgs{
2287+
s.env.ExecuteWorkflow(func(ctx workflow.Context, args *schedulespb.StartScheduleArgs) error {
2288+
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(),
2289+
func() bool { return enableMigration }, func() bool { return true })
2290+
}, &schedulespb.StartScheduleArgs{
22862291
Schedule: &schedulepb.Schedule{
22872292
Spec: &schedulepb.ScheduleSpec{
22882293
Interval: []*schedulepb.IntervalSpec{{
@@ -2314,8 +2319,10 @@ func (s *workflowSuite) TestMigrateFailure() {
23142319
return errors.New("migration failed")
23152320
})
23162321

2317-
// Send migrate signal after the first iteration.
2322+
// Enable migration and request it via signal after the first iteration.
2323+
enableMigration := false
23182324
s.env.RegisterDelayedCallback(func() {
2325+
enableMigration = true
23192326
s.env.SignalWorkflow(SignalNameMigrateToChasm, nil)
23202327
}, 1*time.Second)
23212328

@@ -2328,7 +2335,10 @@ func (s *workflowSuite) TestMigrateFailure() {
23282335

23292336
CurrentTweakablePolicies.IterationsBeforeContinueAsNew = 100
23302337
s.env.SetStartTime(baseStartTime)
2331-
s.env.ExecuteWorkflow(SchedulerWorkflow, &schedulespb.StartScheduleArgs{
2338+
s.env.ExecuteWorkflow(func(ctx workflow.Context, args *schedulespb.StartScheduleArgs) error {
2339+
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(),
2340+
func() bool { return enableMigration }, func() bool { return true })
2341+
}, &schedulespb.StartScheduleArgs{
23322342
Schedule: &schedulepb.Schedule{
23332343
Spec: &schedulepb.ScheduleSpec{
23342344
Interval: []*schedulepb.IntervalSpec{{
@@ -2370,14 +2380,19 @@ func (s *workflowSuite) TestMigrateFailureThenRetrySuccess() {
23702380
return nil
23712381
})
23722382

2373-
// Send migrate signal after the first iteration.
2383+
// Enable migration and request it via signal after the first iteration.
2384+
enableMigration := false
23742385
s.env.RegisterDelayedCallback(func() {
2386+
enableMigration = true
23752387
s.env.SignalWorkflow(SignalNameMigrateToChasm, nil)
23762388
}, 1*time.Second)
23772389

23782390
CurrentTweakablePolicies.IterationsBeforeContinueAsNew = 100
23792391
s.env.SetStartTime(baseStartTime)
2380-
s.env.ExecuteWorkflow(SchedulerWorkflow, &schedulespb.StartScheduleArgs{
2392+
s.env.ExecuteWorkflow(func(ctx workflow.Context, args *schedulespb.StartScheduleArgs) error {
2393+
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(),
2394+
func() bool { return enableMigration }, func() bool { return true })
2395+
}, &schedulespb.StartScheduleArgs{
23812396
Schedule: &schedulepb.Schedule{
23822397
Spec: &schedulepb.ScheduleSpec{
23832398
Interval: []*schedulepb.IntervalSpec{{
@@ -2410,8 +2425,10 @@ func (s *workflowSuite) TestMigrateFailureThenSignal() {
24102425
return errors.New("migration failed")
24112426
})
24122427

2413-
// Send migrate signal after the first iteration.
2428+
// Enable migration and request it via signal after the first iteration.
2429+
enableMigration := false
24142430
s.env.RegisterDelayedCallback(func() {
2431+
enableMigration = true
24152432
s.env.SignalWorkflow(SignalNameMigrateToChasm, nil)
24162433
}, 1*time.Second)
24172434
// After migration failure, send a pause patch and verify it's processed,
@@ -2434,7 +2451,10 @@ func (s *workflowSuite) TestMigrateFailureThenSignal() {
24342451

24352452
CurrentTweakablePolicies.IterationsBeforeContinueAsNew = 100
24362453
s.env.SetStartTime(baseStartTime)
2437-
s.env.ExecuteWorkflow(SchedulerWorkflow, &schedulespb.StartScheduleArgs{
2454+
s.env.ExecuteWorkflow(func(ctx workflow.Context, args *schedulespb.StartScheduleArgs) error {
2455+
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(),
2456+
func() bool { return enableMigration }, func() bool { return true })
2457+
}, &schedulespb.StartScheduleArgs{
24382458
Schedule: &schedulepb.Schedule{
24392459
Spec: &schedulepb.ScheduleSpec{
24402460
Interval: []*schedulepb.IntervalSpec{{
@@ -2476,7 +2496,7 @@ func (s *workflowSuite) TestMigrateDynamicConfig() {
24762496

24772497
s.env.SetStartTime(baseStartTime)
24782498
s.env.ExecuteWorkflow(func(ctx workflow.Context, args *schedulespb.StartScheduleArgs) error {
2479-
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(), func() bool { return true })
2499+
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(), func() bool { return true }, func() bool { return true })
24802500
}, &schedulespb.StartScheduleArgs{
24812501
Schedule: &schedulepb.Schedule{
24822502
Spec: &schedulepb.ScheduleSpec{
@@ -2525,7 +2545,7 @@ func (s *workflowSuite) TestMigrateDynamicConfigFlipsMidRun() {
25252545

25262546
s.env.SetStartTime(baseStartTime)
25272547
s.env.ExecuteWorkflow(func(ctx workflow.Context, args *schedulespb.StartScheduleArgs) error {
2528-
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(), func() bool { return enabled })
2548+
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(), func() bool { return enabled }, func() bool { return true })
25292549
}, &schedulespb.StartScheduleArgs{
25302550
Schedule: &schedulepb.Schedule{
25312551
Spec: &schedulepb.ScheduleSpec{
@@ -2564,7 +2584,7 @@ func (s *workflowSuite) TestMigrateDynamicConfigFailure() {
25642584

25652585
s.env.SetStartTime(baseStartTime)
25662586
s.env.ExecuteWorkflow(func(ctx workflow.Context, args *schedulespb.StartScheduleArgs) error {
2567-
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(), func() bool { return true })
2587+
return schedulerWorkflowWithSpecBuilder(ctx, args, NewSpecBuilder(), func() bool { return true }, func() bool { return true })
25682588
}, &schedulespb.StartScheduleArgs{
25692589
Schedule: &schedulepb.Schedule{
25702590
Spec: &schedulepb.ScheduleSpec{

0 commit comments

Comments
 (0)