apache/beam · error

Init hooks have already run. Register function during init()

Error message

Init hooks have already run. Register function during init() instead.

What it means

runtime.RegisterFunction caches a function value by its reflect-based symbol name so serialized pipelines can resolve it in workers where the symbol table is unavailable. Registration is only safe before beam.Init() runs; afterward the cache is considered frozen and the library panics to prevent nondeterministic late registration.

Source

Thrown at sdks/go/pkg/beam/core/runtime/symbols.go:78

	if r, err := symtab.New(os.Args[0]); err == nil {
		return r
	}
	return failResolver(false)
}

// SymbolResolver resolves a symbol to an unsafe address.
type SymbolResolver interface {
	// Sym2Addr returns the address pointer for a given symbol.
	Sym2Addr(string) (uintptr, error)
}

// RegisterFunction allows function registration. It is beneficial for performance
// and is needed for functions -- such as custom coders -- serialized during unit
// tests, where the underlying symbol table is not available. It should be called
// in `init()` only.
func RegisterFunction(fn any) {
	if initialized {
		panic("Init hooks have already run. Register function during init() instead.")
	}

	key := reflectx.FunctionName(fn)
	// If the function was registered already, the key and value will be the same anyway.
	cache[key] = fn
}

// RegisterFunctionWithName registers fn under the given name,
// overriding the automatically derived symbol name. This is necessary
// for closures produced by Go generic functions where multiple type
// instantiations generate closures with the same compiler-assigned
// name (e.g. "pkg.Func[...].func1") — without distinct names the
// last registration wins and cross-worker deserialization resolves
// the wrong function.
//
// Callers must ensure that name is stable across process invocations
// (pipeline driver and workers must agree). A typical choice is
// "<package>.<GenericFunc>[<TypeParam>].enc".

View on GitHub (pinned to 12126d8942)

Solutions

  1. Move all RegisterFunction calls into package init() functions.
  2. Ensure beam.Init() is called only after every package whose init() performs registration is linked/imported.
  3. In tests, register custom coders/functions via init() or separate registration-capable test helpers that run before Init.

Example fix

// before
func main() {
    beam.Init()
    runtime.RegisterFunction(myFn) // panics
}
// after
func init() {
    runtime.RegisterFunction(myFn)
}
func main() {
    beam.Init()
}
Defensive patterns

Strategy: validation

Validate before calling

// ensure call site is a package init() function:
func init() { runtime.RegisterFunction(myFn) }

Prevention

When it happens

Trigger: Calling runtime.RegisterFunction(fn) (directly or via RegisterDoFn/RegisterCoder wrappers) after beam.Init() has executed — e.g. registering coders/DoFns inside main after Init, in a test after TestMain ran Init, or from lazily initialized code.

Common situations: Tests that call beam.Init() in TestMain and register custom coders inside individual tests; moving Init to the top of main ahead of init()-time registration; plugins loaded at runtime that try to register symbols.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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