grpc/grpc-go · error
xdsclient: eventHandler is nil
Error message
xdsclient: eventHandler is nil
What it means
The final mandatory field checked by `newXDSChannel` (channel.go:79) is `eventHandler` — an implementation of the xdsChannelEventHandler interface that receives callbacks for ADS stream events (resource updates, errors, resource-does-not-exist). Because the channel has no other way to deliver these events, a nil handler is rejected at channel.go:87-88.
Source
Thrown at internal/xds/clients/xdsclient/channel.go:88
eventHandler xdsChannelEventHandler // Callbacks for ADS stream events.
backoff func(int) time.Duration // Backoff function to use for stream retries. Defaults to exponential backoff, if unset.
watchExpiryTimeout time.Duration // Timeout for ADS resource watch expiry.
logPrefix string // Prefix to use for logging.
}
// newXDSChannel creates a new xdsChannel instance with the provided options.
// It performs basic validation on the provided options and initializes the
// xdsChannel with the necessary components.
func newXDSChannel(opts xdsChannelOpts) (*xdsChannel, error) {
switch {
case opts.transport == nil:
return nil, errors.New("xdsclient: transport is nil")
case opts.serverConfig == nil:
return nil, errors.New("xdsclient: serverConfig is nil")
case opts.clientConfig == nil:
return nil, errors.New("xdsclient: clientConfig is nil")
case opts.eventHandler == nil:
return nil, errors.New("xdsclient: eventHandler is nil")
}
xc := &xdsChannel{
transport: opts.transport,
serverConfig: opts.serverConfig,
clientConfig: opts.clientConfig,
eventHandler: opts.eventHandler,
closed: syncutil.NewEvent(),
}
l := grpclog.Component("xds")
logPrefix := opts.logPrefix + fmt.Sprintf("[xds-channel %p] ", xc)
xc.logger = igrpclog.NewPrefixLogger(l, logPrefix)
if opts.backoff == nil {
opts.backoff = backoff.DefaultExponential.Backoff
}
np := internal.NodeProto(opts.clientConfig.Node)View on GitHub (pinned to 0c51461d27)
Solutions
- Pass a non-nil xdsChannelEventHandler (typically the owning authority) in xdsChannelOpts.eventHandler.
- For tests, supply a no-op handler struct that satisfies the interface rather than leaving it nil.
- Audit any custom code that constructs xdsChannelOpts to confirm eventHandler is set.
Example fix
// before
opts := xdsChannelOpts{transport: t, serverConfig: sc, clientConfig: cc}
xc, err := newXDSChannel(opts) // err: eventHandler is nil
// after
type noopHandler struct{}
func (noopHandler) OnResourceUpdate(rt ResourceType, n string, d xdsresource.ResourceData) {}
func (noopHandler) OnResourceError(rt ResourceType, n string, err error) {}
func (noopHandler) OnStreamError(err error) {}
opts := xdsChannelOpts{transport: t, serverConfig: sc, clientConfig: cc, eventHandler: noopHandler{}} Defensive patterns
Strategy: type-guard
Type guard
// Compile-time assertion that your handler satisfies the interface.
var _ xdsChannelEventHandler = (*myHandler)(nil)
type myHandler struct{}
func (*myHandler) OnResourceUpdate(rt ResourceType, n string, d xdsresource.ResourceData) {}
func (*myHandler) OnResourceError(rt ResourceType, n string, err error) {}
func (*myHandler) OnStreamError(err error) {}
func (*myHandler) adsResourceDoesNotExist(rt ResourceType, n string) {} Prevention
- Always supply a non-nil event handler (or the owning authority) when constructing a channel.
- For tests, use a tiny no-op handler struct rather than nil.
- Use compile-time interface checks (var _ Interface = ...) to catch signature drift early.
When it happens
Trigger: Triggered when `newXChannel` is called with `opts.eventHandler == nil`. The XDSClient normally supplies an authority (which implements the interface) as the handler; a nil means the wiring was skipped.
Common situations: Calling newXDSChannel from a test stub without supplying an event handler; refactoring the authority wiring so the handler is no longer passed; a custom resource-type integration that bypasses the authority abstraction.
Related errors
- xdsclient: transport is nil
- xdsclient: serverConfig is nil
- xdsclient: clientConfig is nil
- xds_wrr_locality: invalid LBConfig: child policy field must
- lrsclient: transport builder is nil
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/098c3afd2daf4590.
Report an issue: GitHub.