{"record":{"id":"069faa6617b18a87","repo":"netdata/netdata","slug":"metrix-write-outside-active-cycle","errorCode":null,"errorMessage":"metrix: write outside active cycle","messagePattern":"metrix: write outside active cycle","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/go/pkg/metrix/stateset.go","lineNumber":112,"sourceCode":"\tfor _, st := range schema.states {\n\t\tstates[st] = false\n\t}\n\tfor _, active := range actives {\n\t\tif _, ok := schema.index[active]; !ok {\n\t\t\tpanic(errStateSetUnknownState)\n\t\t}\n\t\tstates[active] = true\n\t}\n\treturn StateSetPoint{States: states}\n}\n\n// recordStateSetObserve writes one full-state stateset sample into the active frame.\nfunc (c *storeCore) recordStateSetObserve(desc *instrumentDescriptor, scope HostScope, point StateSetPoint, sets []LabelSet) {\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\n\tif c.active == nil {\n\t\tpanic(errCycleInactive)\n\t}\n\n\tschema := desc.stateSet\n\tif schema == nil {\n\t\tpanic(errStateSetSchema)\n\t}\n\n\tlabels, labelsKey, err := labelsFromSet(sets, c)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif labelsContainKey(labels, desc.name) {\n\t\tpanic(errStateSetLabelKey)\n\t}\n\tscope, ok := c.prepareHostScopeForWriteLocked(scope)\n\tif !ok {\n\t\treturn\n\t}","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/netdata/netdata/blob/4864de85e26f6734d92cfc27ccbeff5921f49938/src/go/pkg/metrix/stateset.go#L94-L130","documentation":"This panic fires in storeCore.recordStateSetObserve (stateset.go:112) when c.active is nil, i.e. no collection cycle is currently active. The snapshot storeCore buffers writes into per-cycle frames created when a cycle begins; stateset observations arriving before Begin or after the cycle is closed/flushed have no frame to write into, so the write outside an active cycle panics. The same guard exists on gauge, counter, histogram, and summary write paths.","triggerScenarios":"Calling ObserveStateSet from a goroutine or callback that runs outside the collector's Collect/scrape window — e.g. an event handler or background updater firing after the cycle ended, or writes issued before the store's cycle was started.","commonSituations":"Spawning goroutines that record metrics asynchronously while the scrape cycle advances; collectors that emit from timers or SNMP trap callbacks not synchronized with the collection loop; test code writing to the store without beginning a cycle.","solutions":["Move the write inside the active collection cycle (inside the Collect function between cycle begin and end).","If writes originate in async callbacks, buffer them in a channel/queue and drain them during the next cycle instead of writing directly.","In tests, wrap writes with the store's cycle begin/end helpers."],"exampleFix":"// before\ngo func() {\n    ss.ObserveStateSet(p) // fires after cycle end -> panic\n}()\n\n// after\nch <- p                 // async producer\n// inside Collect():\nfor p := range drain(ch) {\n    ss.ObserveStateSet(p)\n}","handlingStrategy":"validation","validationCode":"// Buffer async producers and drain inside the cycle:\n// producer (any time):   events <- p\n// inside Collect():      for p := range drain(events) { ss.ObserveStateSet(p) }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Perform all metric writes inside the Collect/scrape function, between cycle begin and end.","Never write to the store from raw goroutines, timers, or event callbacks; hand values to the collection loop instead.","In tests, always wrap writes with the store's cycle begin/end helpers."],"tags":["metrix","go","stateset","lifecycle","concurrency","panic"],"backgroundTag":null,"analyzedSha":"4864de85e26f6734d92cfc27ccbeff5921f49938","analyzedAt":"2026-08-15T09:12:38.226Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}