hashicorp/nomad · warning

csi updates already batched

Error message

csi updates already batched

What it means

batchCSIUpdates registers the one-shot callback that flushes batched CSI plugin node updates, guarded by csiBatched under csiMu. This error means the method was invoked a second time on the same batchNodeUpdates, which is only meant to run once during the first fingerprinting round at client startup.

Solutions

  1. Call batchCSIUpdates exactly once per batchNodeUpdates lifetime, from batchFirstFingerprints during client start
  2. Create a new batchNodeUpdates instance for each subsequent batching round
  3. If re-invocation is legitimate in your flow, tolerate or log this sentinel error instead of failing startup

Example fix

// before: same batcher reused after reconnect
if err := b.batchCSIUpdates(fn); err != nil { /* already batched */ }

// after: reset or recreate batcher on reconnect
b = newBatchNodeUpdates()
if err := b.batchCSIUpdates(fn); err != nil {
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

if b.csiBatched {
	return nil // already registered; avoid the sentinel error
}

Try / catch

if err := b.batchCSIUpdates(fn); err != nil {
	if err.Error() == "csi updates already batched" {
		logger.Debug("csi batch already registered; ignoring")
		return nil
	}
	return err
}

Prevention

When it happens

Trigger: Calling batchNodeUpdates.batchCSIUpdates (directly or via batchFirstFingerprints) more than once on the same batcher instance, e.g. on client restart/reconnect while reusing the old batcher.

Common situations: Re-running the first-fingerprint batching path after a server reconnect; duplicate wiring of the CSI fingerprinter in client startup; unit tests invoking the batching functions twice on the same struct.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/6ae0395669afe58e. Report an issue: GitHub.

Appendix: source

Thrown at client/node_updater.go:444

	// Only one of these is expected to be set, but a future implementation that
	// explicitly models monolith plugins with a single fingerprinter may set both
	if info.ControllerInfo != nil {
		b.csiControllerPlugins[plugin] = info
	}

	if info.NodeInfo != nil {
		b.csiNodePlugins[plugin] = info
	}
}

// batchCSIUpdates sends all of the batched CSI updates by calling f  for each
// plugin batched
func (b *batchNodeUpdates) batchCSIUpdates(f csimanager.UpdateNodeCSIInfoFunc) error {
	b.csiMu.Lock()
	defer b.csiMu.Unlock()
	if b.csiBatched {
		return fmt.Errorf("csi updates already batched")
	}

	b.csiBatched = true
	for plugin, info := range b.csiNodePlugins {
		f(plugin, info)
	}
	for plugin, info := range b.csiControllerPlugins {
		f(plugin, info)
	}
	return nil
}

// updateNodeFromDriver implements drivermanager.UpdateNodeDriverInfoFn and is
// used in the driver manager to send driver fingerprints to
func (b *batchNodeUpdates) updateNodeFromDriver(driver string, info *structs.DriverInfo) {
	b.driversMu.Lock()
	defer b.driversMu.Unlock()
	if b.driversBatched {

View on GitHub (pinned to 482b49bf1a)