{"record":{"id":"4d5294bb5666077c","repo":"hashicorp/nomad","slug":"driver-updates-already-batched","errorCode":null,"errorMessage":"driver updates already batched","messagePattern":"driver updates already batched","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"client/node_updater.go","lineNumber":476,"sourceCode":"// used in the driver manager to send driver fingerprints to\nfunc (b *batchNodeUpdates) updateNodeFromDriver(driver string, info *structs.DriverInfo) {\n\tb.driversMu.Lock()\n\tdefer b.driversMu.Unlock()\n\tif b.driversBatched {\n\t\tb.driverCB(driver, info)\n\t\treturn\n\t}\n\n\tb.drivers[driver] = info\n}\n\n// batchDriverUpdates sends all of the batched driver node updates by calling f\n// for each driver batched\nfunc (b *batchNodeUpdates) batchDriverUpdates(f drivermanager.UpdateNodeDriverInfoFn) error {\n\tb.driversMu.Lock()\n\tdefer b.driversMu.Unlock()\n\tif b.driversBatched {\n\t\treturn fmt.Errorf(\"driver updates already batched\")\n\t}\n\n\tb.driversBatched = true\n\tfor driver, info := range b.drivers {\n\t\tf(driver, info)\n\t}\n\treturn nil\n}\n\n// updateNodeFromDevices implements devicemanager.UpdateNodeDevicesFn and is\n// used in the device manager to send device fingerprints to\nfunc (b *batchNodeUpdates) updateNodeFromDevices(devices []*structs.NodeDeviceResource) {\n\tb.devicesMu.Lock()\n\tdefer b.devicesMu.Unlock()\n\tif b.devicesBatched {\n\t\tb.devicesCB(devices)\n\t\treturn\n\t}","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/node_updater.go#L458-L494","documentation":"batchDriverUpdates registers the one-shot callback that flushes batched driver node updates, guarded by driversBatched under driversMu. This error indicates a second invocation on the same batchNodeUpdates, which by design supports only a single first-fingerprint driver update flush.","triggerScenarios":"Calling batchNodeUpdates.batchDriverUpdates (directly or via batchFirstFingerprints) more than once on the same batcher instance.","commonSituations":"Client restart logic that re-triggers batchFirstFingerprints on the original batcher; accidental double registration of the driver manager fingerprinter; tests sharing a batchNodeUpdates across subtests.","solutions":["Invoke batchDriverUpdates only once per batchNodeUpdates, during initial client startup","Instantiate a new batchNodeUpdates for any additional fingerprint round","Check call flow so reconnect/restart paths construct fresh batchers rather than reusing the startup one"],"exampleFix":"// before: retrying batched first fingerprints with same batcher\nfor round := 0; round < 2; round++ {\n    b.batchDriverUpdates(fn) // fails on round 1\n}\n\n// after: one batcher per round\nfor round := 0; round < 2; round++ {\n    b := newBatchNodeUpdates()\n    b.batchDriverUpdates(fn)\n}","handlingStrategy":"try-catch","validationCode":"if b.driversBatched {\n\treturn nil // batch already registered\n}","typeGuard":null,"tryCatchPattern":"if err := b.batchDriverUpdates(fn); err != nil {\n\tif err.Error() == \"driver updates already batched\" {\n\t\tlogger.Debug(\"driver batch already registered; ignoring\")\n\t\treturn nil\n\t}\n\treturn err\n}","preventionTips":["Call batchDriverUpdates once per batchNodeUpdates lifetime only","Reset/recreate batchers on client restart instead of reusing them","Keep driver fingerprint batching wired only to initial startup","Cover startup flow with a test asserting batcher one-shot semantics"],"tags":["nomad-client","drivers","fingerprinting","one-shot"],"backgroundTag":"already-initialized","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}