{"record":{"id":"9c8629f3482dab5b","repo":"apache/beam","slug":"batch-groupintobatchesfn-unexpected-timer-family-q","errorCode":null,"errorMessage":"batch.groupIntoBatchesFn: unexpected timer family %q","messagePattern":"batch\\.groupIntoBatchesFn: unexpected timer family %q","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sdks/go/pkg/beam/transforms/batch/batch.go","lineNumber":305,"sourceCode":"\t\tnewBytes = cur\n\t}\n\n\tif fn.BatchSize > 0 && count >= fn.BatchSize {\n\t\tfn.flush(sp, key, emit)\n\t\treturn\n\t}\n\tif fn.BatchSizeBytes > 0 && newBytes >= fn.BatchSizeBytes {\n\t\tfn.flush(sp, key, emit)\n\t\treturn\n\t}\n}\n\nfunc (fn *groupIntoBatchesFn) OnTimer(\n\tctx context.Context, ts beam.EventTime, sp state.Provider, tp timers.Provider,\n\tkey typex.T, timer timers.Context, emit func(typex.T, []typex.V),\n) {\n\tif timer.Family != fn.WindowEnd.Family {\n\t\tpanic(fmt.Sprintf(\"batch.groupIntoBatchesFn: unexpected timer family %q\", timer.Family))\n\t}\n\tfn.codec.init(fn.ValueType.T)\n\tfn.flush(sp, key, emit)\n}\n\nfunc (fn *groupIntoBatchesFn) flush(\n\tsp state.Provider, key typex.T, emit func(typex.T, []typex.V),\n) {\n\tbuf, ok, err := fn.Buffer.Read(sp)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif !ok || len(buf) == 0 {\n\t\treturn\n\t}\n\n\tout := make([]typex.V, len(buf))\n\tfor i, b := range buf {","sourceCodeStart":287,"sourceCodeEnd":323,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/go/pkg/beam/transforms/batch/batch.go#L287-L323","documentation":"This panic fires in groupIntoBatchesFn.OnTimer when a timer callback arrives whose family string does not match the WindowEnd timer spec declared by the DoFn. The transform only schedules one timer family (fn.WindowEnd.Family) for lateness/flush handling, so any other family indicates misconfigured or foreign timers on the same key — an internal consistency check that panics loudly. Typical root causes are leftover timers from a previous pipeline version or a runner delivering timers from an unrelated spec.","triggerScenarios":"OnTimer is invoked with timers.Context whose timer.Family differs from fn.WindowEnd.Family — e.g. after renaming the timer spec while old timers from a previous job version are still queued, or a misconfigured pipeline feeding this DoFn timers it never set.","commonSituations":"Developers hit this after modifying the timer family/identifier in an upgraded pipeline while reusing state (old timers still pending), or when a custom runner/test harness dispatches timers with a default family name.","solutions":["Check timer.Family in logs against fn.WindowEnd.Family; identify which spec scheduled the unexpected timer.","After renaming the timer spec, drain or clear old timers (or use a fresh state/timer namespace) before redeploying.","Make sure only groupIntoBatchesFn schedules timers on the same key/state namespace.","If intentional multi-family timers are needed, extend OnTimer to switch on the family instead of panicking."],"exampleFix":"// before\nif timer.Family != fn.WindowEnd.Family {\n\tpanic(fmt.Sprintf(\"batch.groupIntoBatchesFn: unexpected timer family %q\", timer.Family))\n}\n// after: ignore foreign timers instead of aborting the bundle\nif timer.Family != fn.WindowEnd.Family {\n\treturn // not our timer; ignore\n}","handlingStrategy":"validation","validationCode":"// Before deploying a renamed timer spec, assert no old-family timers remain:\nif timer.Family != expectedFamily {\n\tlog.Printf(\"ignoring foreign timer family %q (expected %q)\", timer.Family, expectedFamily)\n\treturn\n}","typeGuard":"func isWindowEndTimer(fn *groupIntoBatchesFn, t timers.Context) bool {\n\treturn t.Family == fn.WindowEnd.Family\n}","tryCatchPattern":"func safeOnTimer(fn *groupIntoBatchesFn, timer timers.Context) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = fmt.Errorf(\"on-timer dispatch failed: %v\", r)\n\t\t}\n\t}()\n\t// call OnTimer\n\treturn nil\n}","preventionTips":["Never rename timer families while reusing persistent state; drain old timers first.","Only schedule timers for this DoFn on the given key namespace.","Use a new state/timer namespace when changing timer specs.","Switch on family in OnTimer if multiple families are ever expected."],"tags":["go","apache-beam","timer","panic"],"backgroundTag":"internal-invariant-violation","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}