grpc/grpc-go · warning

profiling may be initialized at most once

Error message

profiling may be initialized at most once

What it means

Returned by profiling.InitStats when it has already been called once. InitStats uses an atomic CompareAndSwap on statsInitialized (0->1) to enforce single initialization; a second call finds it already 1 and returns errAlreadyInitialized. This is by design—profiling's StreamStats CircularBuffer is process-global and must be set up exactly once.

Source

Thrown at internal/profiling/profiling.go:202

	stat.mu.Unlock()
}

// statsInitialized is 0 before InitStats has been called. Changed to 1 by
// exactly one call to InitStats.
var statsInitialized int32

// Stats for the last defaultStreamStatsBufsize RPCs will be stored in memory.
// This can be configured by the registering server at profiling service
// initialization with google.golang.org/grpc/profiling/service.ProfilingConfig
const defaultStreamStatsSize uint32 = 16 << 10

// StreamStats is a CircularBuffer containing data from the last N RPC calls
// served, where N is set by the user. This will contain both server stats and
// client stats (but each stat will be tagged with whether it's a server or a
// client in its Tags).
var StreamStats *buffer.CircularBuffer

var errAlreadyInitialized = errors.New("profiling may be initialized at most once")

// InitStats initializes all the relevant Stat objects. Must be called exactly
// once per lifetime of a process; calls after the first one will return an
// error.
func InitStats(streamStatsSize uint32) error {
	var err error
	if !atomic.CompareAndSwapInt32(&statsInitialized, 0, 1) {
		return errAlreadyInitialized
	}

	if streamStatsSize == 0 {
		streamStatsSize = defaultStreamStatsSize
	}

	StreamStats, err = buffer.NewCircularBuffer(streamStatsSize)
	if err != nil {
		return err
	}

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Call profiling.InitStats exactly once per process lifecycle, typically at startup.
  2. In tests, guard re-initialization with a sync.Once or reset statsInitialized via test helpers if available.
  3. If using profiling/service.RegisterProfiling, ensure it is only called once across all servers in the binary.
  4. Check IsEnabled and whether StreamStats is already non-nil before calling InitStats.

Example fix

// before: may call InitStats multiple times
func setupProfiling(size uint32) error {
    return profiling.InitStats(size)
}
// after: guard with sync.Once
var initOnce sync.Once
var initErr error
func setupProfiling(size uint32) error {
    initOnce.Do(func() { initErr = profiling.InitStats(size) })
    return initErr
}
Defensive patterns

Strategy: validation

Validate before calling

// Guard InitStats with sync.Once to prevent double initialization.
var profilingOnce sync.Once
var profilingInitErr error
func safeInitStats(size uint32) error {
    profilingOnce.Do(func() { profilingInitErr = profiling.InitStats(size) })
    return profilingInitErr
}

Try / catch

err := profiling.InitStats(size)
if err != nil && strings.Contains(err.Error(), "at most once") {
    // already initialized; safe to ignore
    return nil
}

Prevention

When it happens

Trigger: Calling profiling.InitStats (or profiling/service RegisterProfiling which calls InitStats internally) more than once in the same process. Common in tests that start multiple profiling services, or in long-running servers that reinitialize profiling.

Common situations: Test suites that call InitStats in setup without teardown; multiple gRPC servers in one binary each trying to register the profiling service; hot-reload or restart logic that re-runs initialization code.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/479db05b2265abfe. Report an issue: GitHub.