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

  1. Do not call conn.RegisterProtocol yourself unless you are introducing a genuinely new protocol — the built-in one is registered automatically.
  2. Use a unique protocol name for custom/test ALTS record implementations.
  3. 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

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


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 handle

View on GitHub (pinned to 0c51461d27)