apache/beam · error

RegisterHeapCaptureHook

Error message

RegisterHeapCaptureHook: %s registered twice

What it means

RegisterHeapCaptureHook registers a heap-profile CaptureHookFactory in heapCaptureHookRegistry and panics on duplicate names. The fail-fast design catches conflicting registrations at process startup rather than producing ambiguous profiling output later.

Solutions

  1. Grep for RegisterHeapCaptureHook with the offending name and remove the duplicate call.
  2. Centralize registration in one package's init and blank-import it where needed.
  3. Run go mod graph/tidy to remove duplicate versions of the hook package.
  4. Guard dynamic registration with sync.Once or an existence check.

Example fix

// before
func init() { perf.RegisterHeapCaptureHook("heap", h) }
func main() { perf.RegisterHeapCaptureHook("heap", h) } // panics
// after
var heapOnce sync.Once
func registerHeapHook() { heapOnce.Do(func() { perf.RegisterHeapCaptureHook("heap", h) }) }
Defensive patterns

Strategy: validation

Validate before calling

if _, exists := heapRegistered["heap"]; !exists {
    perf.RegisterHeapCaptureHook("heap", myFactory)
}

Try / catch

func registerHeap(name string, f perf.CaptureHookFactory) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("heap hook %q already registered: %v", name, r)
        }
    }()
    perf.RegisterHeapCaptureHook(name, f)
}

Prevention

When it happens

Trigger: Calling perf.RegisterHeapCaptureHook(name, factory) when the name already exists in heapCaptureHookRegistry — duplicate init() calls, registering in both test bootstrap and application init, or two module versions of the same hook package linked into one binary.

Common situations: Multiple heap profiling hooks with the same identifier from different packages; repeated test suite initialization in one process; dependency upgrades introducing a second copy of the hook package.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/5b71884d58dceeba. Report an issue: GitHub.

Appendix: source

Thrown at sdks/go/pkg/beam/x/hooks/perf/perf.go:210

			// Rewrite the registration with the current arguments
			enabledTraceCaptureHooks[i] = enc
			hooks.EnableHook("trace", enabledTraceCaptureHooks...)
			return
		}
	}
	enabledTraceCaptureHooks = append(enabledTraceCaptureHooks, enc)
	hooks.EnableHook("trace", enabledTraceCaptureHooks...)
}

var heapCaptureHookRegistry = make(map[string]CaptureHookFactory)
var enabledHeapCaptureHooks []string

// RegisterHeapCaptureHook registers a CaptureHookFactory for the
// supplied identifier. It panics if the same identifier is
// registered twice.
func RegisterHeapCaptureHook(name string, c CaptureHookFactory) {
	if _, exists := heapCaptureHookRegistry[name]; exists {
		panic(fmt.Sprintf("RegisterHeapCaptureHook: %s registered twice", name))
	}
	heapCaptureHookRegistry[name] = c
}

// EnableHeapCaptureHook actives a registered heap profile capture hook for a given pipeline.
func EnableHeapCaptureHook(name string, opts ...string) {
	_, exists := heapCaptureHookRegistry[name]
	if !exists {
		panic(fmt.Sprintf("EnableHeapCaptureHook: %s not registered", name))
	}

	enc := hooks.Encode(name, opts)

	for i, h := range enabledHeapCaptureHooks {
		n, _ := hooks.Decode(h)
		if h == n {
			// Rewrite the registration with the current arguments
			enabledHeapCaptureHooks[i] = enc

View on GitHub (pinned to 12126d8942)