grpc/grpc-go · error
protocol is already registered
Error message
protocol %v is already registered
What it means
Returned by conn.RegisterProtocol when an ALTS record protocol name has already been registered in the package-level 'protocols' map. RegisterProtocol is idempotent-fail: the second registration of the same name is rejected. The init() in handshaker.go registers 'ALTSRP_GCM_AES128_REKEY' exactly once; a duplicate would come from user code or a double init.
Solutions
- Do not call conn.RegisterProtocol yourself unless you are introducing a genuinely new protocol — the built-in one is registered automatically.
- Use a unique protocol name for custom/test ALTS record implementations.
- In tests, avoid re-importing or re-initializing the handshaker package; if a global map reset is needed, guard registration with a sync.Once or check existence first.
Example fix
// before
conn.RegisterProtocol("ALTSRP_GCM_AES128_REKEY", myFunc) // already registered -> error
// after: use a unique name for custom protocols
conn.RegisterProtocol("MY_CUSTOM_ALTSRP", myFunc) Defensive patterns
Strategy: validation
Validate before calling
// If you must register a custom protocol, check first to avoid the duplicate error.
func safeRegisterProtocol(name string, f conn.ALTSRecordFunc) error {
// conn.RegisterProtocol returns an error on duplicate; the public API already does this,
// so just handle the returned error instead of ignoring it.
if err := conn.RegisterProtocol(name, f); err != nil {
log.Printf("protocol registration skipped: %v", err)
return nil // treat duplicate as non-fatal
}
return nil
} Prevention
- Do not call conn.RegisterProtocol for the built-in ALTSRP_GCM_AES128_REKEY — it is registered in init().
- Use unique names for test/custom protocols.
- Guard test-only registration with sync.Once or a build-tagged init.
When it happens
Trigger: Calling conn.RegisterProtocol("ALTSRP_GCM_AES128_REKEY", fn) from application/test code after handshaker.init() already registered it, or importing two packages that both register the same protocol name.
Common situations: Test code that registers a custom or mock ALTS record protocol with a name colliding with the built-in one; a fork or custom build that adds its own init() registering ALTSRP_GCM_AES128_REKEY; running tests that re-execute registration without resetting the map.
Related errors
- balancergroup: unregistered balancer name
- child policy not registered
- client-side RPC versions is not compatible with this…
- could not get resolver for default scheme
- external processor unexpectedly sent duplicate response…
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/402deb1f064b2f34.
Report an issue: GitHub.
Appendix: source
Thrown at credentials/alts/internal/conn/record.go:108
pool, err := imem.NewDirtyBinaryTieredBufferPool(
8,
12, // Go page size, 4KB
14, // 16KB (max HTTP/2 frame size used by gRPC)
15, // 32KB (default buffer size for gRPC)
16, // 64KB
17, // 128KB
19, // 512KB, max write buffer size
)
if err != nil {
panic(fmt.Sprintf("Failed to create write buffer pool: %v", err))
}
writeBufPool = pool
}
// RegisterProtocol register a ALTS record encryption protocol.
func RegisterProtocol(protocol string, f ALTSRecordFunc) error {
if _, ok := protocols[protocol]; ok {
return fmt.Errorf("protocol %v is already registered", protocol)
}
protocols[protocol] = f
return nil
}
// conn represents a secured connection. It implements the net.Conn interface.
type conn struct {
net.Conn
reader readyreader.Reader
crypto ALTSRecordCrypto
// buf holds data that has been read from the connection and decrypted,
// but has not yet been returned by Read. It is a sub-slice of protected.
buf []byte
payloadLengthLimit int
// protectedHandle buffer holds data read from the network but have not yet
// been decrypted. This data might not compose a complete frame.
//
// The buffer pointer to points to a buffer from the readBufPool. The handleView on GitHub (pinned to 0c51461d27)