{"record":{"id":"9decb3f4074f044e","repo":"temporalio/temporal","slug":"schedule-spec-next-time-search-exceeded-the-comput","errorCode":null,"errorMessage":"schedule spec next-time search exceeded the compute iteration limit","messagePattern":"schedule spec next-time search exceeded the compute iteration limit","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"service/worker/scheduler/spec.go","lineNumber":60,"sourceCode":"\t\t// locationCache is a cache for the results of time.LoadLocation. That function accesses\n\t\t// the filesystem and is relatively slow. We assume that it returns a semantically\n\t\t// equivalent value for the same location name. This isn't strictly true, for example if\n\t\t// the time zone database is changed while the process is running. To handle that, we\n\t\t// expire entries after a day. Note that we cache negative results also.\n\t\tlocationCache  cache.Cache\n\t\twarnIterations dynamicconfig.IntPropertyFn\n\t\tmaxIterations  dynamicconfig.IntPropertyFn\n\t}\n\n\tlocationAndError struct {\n\t\tloc *time.Location\n\t\terr error\n\t}\n)\n\n// ErrComputeLimitExceeded is returned by GetNextTime when the search for the next matching time\n// hits the hard compute iteration bound before finding a non-excluded time.\nvar ErrComputeLimitExceeded = errors.New(\"schedule spec next-time search exceeded the compute iteration limit\")\nvar ErrScheduleSpecLimitHit = serviceerror.NewInvalidArgument(\"the schedule calendar specification has too many exclusions. Please modify the specification.\")\n\n// NewSpecBuilder takes the compute-limit getters directly (rather than a *dynamicconfig.Collection)\n// so the dynamic-config plumbing stays in the wiring layer, per the common codebase pattern.\nfunc NewSpecBuilder(warnIterations, maxIterations dynamicconfig.IntPropertyFn) *SpecBuilder {\n\treturn &SpecBuilder{\n\t\twarnIterations: warnIterations,\n\t\tmaxIterations:  maxIterations,\n\t\tlocationCache: cache.New(1000,\n\t\t\t&cache.Options{\n\t\t\t\tTTL: 24 * time.Hour,\n\t\t\t},\n\t\t),\n\t}\n}\n\nfunc (b *SpecBuilder) NewCompiledSpec(spec *schedulepb.ScheduleSpec) (*CompiledSpec, error) {\n\tspec, err := canonicalizeSpec(spec)","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/temporalio/temporal/blob/bde624efd13fbd3843654058db6d9c716166318b/service/worker/scheduler/spec.go#L42-L78","documentation":"ErrComputeLimitExceeded is returned by GetNextTime when searching for the next schedule spec occurrence (skipping excluded times) exceeds a hard iteration bound configured via warnIterations/maxIterations. It guards against specs (e.g. calendars with many exclusion blocks or extremely tight intervals) that would otherwise consume unbounded CPU in the scheduler workflow.","triggerScenarios":"GetNextTime / checkNextScheduleResult is called with a schedule spec whose next matching time cannot be found within maxIterations iterations — typically a calendar spec combined with large exclusion ranges, or an interval spec with a phase that pushes matches far into excluded periods.","commonSituations":"Schedules built programmatically with many calendar entries and broad ExcludeCalendar lists; timezone/DST edge cases generating long gap searches; misconfigured maxIterations dynamic config set too low for legitimate specs; long blocked periods (maintenance windows) covering many candidate times.","solutions":["Simplify the schedule spec: reduce the number of exclusion intervals or replace many calendar entries with fewer broader ones.","Increase the scheduler spec compute limit dynamic config values (warnIterations / maxIterations) if the spec is legitimate but complex.","Narrow exclusion windows so the next-time search finds a match faster.","Reproduce locally with TestGetNextTimeComputeLimitExceeded-style iteration counts to confirm which part of the spec drives the search past the limit."],"exampleFix":"// before\nmaxIterations := dc.GetIntPropertyFn(dynamicconfig.SchedulerSpecComputeMaxIterations) // e.g. 1000, too low for complex spec\n// after\nmaxIterations := dc.GetIntPropertyFn(dynamicconfig.SchedulerSpecComputeMaxIterations) // raised to 100000 for this namespace\n// or reduce spec complexity: collapse 50 exclusion ranges into 3","handlingStrategy":"validation","validationCode":"// before scheduling, sanity-check spec complexity\nif len(spec.ExcludeCalendar)+len(spec.ExcludeTimes) > 100 {\n    return fmt.Errorf(\"spec has %d exclusions; risk of compute-limit failure\", len(spec.ExcludeCalendar)+len(spec.ExcludeTimes))\n}","typeGuard":null,"tryCatchPattern":"result, err := GetNextTime(...)\nif errors.Is(err, scheduler.ErrComputeLimitExceeded) {\n    // alert: spec too complex; simplify exclusions or raise dynamic config limit\n}","preventionTips":["Keep exclusion lists small and merged into the fewest intervals","Tune the scheduler spec compute-limit dynamic config per namespace","Test complex specs with TestGetNextTimeComputeLimitExceeded-style limits before production","Avoid schedules whose next occurrence falls inside long exclusion windows"],"tags":["go","scheduler","compute-limit","iteration"],"backgroundTag":"compute-limit-exceeded","analyzedSha":"bde624efd13fbd3843654058db6d9c716166318b","analyzedAt":"2026-09-01T07:18:39.080Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}