Repository: grpc/grpc-go
File: internal/resolver/delegatingresolver/delegatingresolver.go (Go)

Code before:
// marks the target resolver as ready. If the update includes at least one TCP
// address and the proxy resolver has not yet been constructed, it initializes
// the proxy resolver. A combined state update is triggered once both resolvers
// are ready. If all addresses are non-TCP, it proceeds without waiting for the
// proxy resolver. If ClientConn.UpdateState returns a non-nil error,
// ResolveNow() is called on the proxy resolver.
func (r *delegatingResolver) updateTargetResolverState(state resolver.State) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	if logger.V(2) {
		logger.Infof("Addresses received from target resolver: %v", state.Addresses)
	}
	r.targetResolverState = &state
	// If no addresses returned by resolver have network type as tcp , do not
	// wait for proxy update.
	if !tcpAddressPresent(r.targetResolverState) {
		return r.cc.UpdateState(*r.targetResolverState)
	}

	if len(r.proxyAddrs) == 0 {
		go func() {
			r.childMu.Lock()
			defer r.childMu.Unlock()
			if _, ok := r.proxyResolver.(nopResolver); ok {
				r.proxyResolver, _ = r.proxyURIResolver(resolver.BuildOptions{})
			}
		}()
	}

	err := r.updateClientConnStateLocked()
	if err != nil {
		go func() {
			r.childMu.Lock()
			defer r.childMu.Unlock()
			if r.proxyResolver != nil {
				r.proxyResolver.ResolveNow(resolver.ResolveNowOptions{})
			}
		}()
	}
	return nil
}

// wrappingClientConn serves as an intermediary between the parent ClientConn
// and the child resolvers created here. It implements the resolver.ClientConn
// interface and is passed in that capacity to the child resolvers.
type wrappingClientConn struct {
	// Callback to deliver resolver state updates
	stateListener func(state resolver.State) error
	parent        *delegatingResolver
}

Review comment (bug):
We should not silently ignore this error. The channel may end up waiting forever without receiving any error. This line would also set `r.proxyResolver` to `nil` causing a panic later. 

I think we should inform the channel about the error. The only way I can think of is to call `ClientConn.ReportError`. We may also want to avoid rebuilding the proxy resolver if it has failed once. @easwars @dfawley what do you think?

Code after:
// the proxy resolver. A combined state update is triggered once both resolvers
// are ready. If all addresses are non-TCP, it proceeds without waiting for the
// proxy resolver. If ClientConn.UpdateState returns a non-nil error,
// ResolveNow() is called on the proxy resolver.
func (r *delegatingResolver) updateTargetResolverState(state resolver.State) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	if logger.V(2) {
		logger.Infof("Addresses received from target resolver: %v", state.Addresses)
	}
	r.targetResolverState = &state
	// If no addresses returned by resolver have network type as tcp , do not
	// wait for proxy update.
	if !isTCPAddressPresent(r.targetResolverState) {
		return r.cc.UpdateState(*r.targetResolverState)
	}

	// The proxy resolver may be rebuilt multiple times, specifically each time
	// the target resolver sends an update, even if the target resolver is built
	// successfully but building the proxy resolver fails.
	if len(r.proxyAddrs) == 0 {
		go func() {
			r.childMu.Lock()
			defer r.childMu.Unlock()
			if _, ok := r.proxyResolver.(nopResolver); !ok {
				return
			}
			proxyResolver, err := r.proxyURIResolver(resolver.BuildOptions{})
			if err != nil {
				r.cc.ReportError(fmt.Errorf("delegating_resolver: unable to build the proxy resolver: %v", err))
				return
			}
			r.proxyResolver = proxyResolver
		}()
	}

	err := r.updateClientConnStateLocked()
	if err != nil {
		go func() {
			r.childMu.Lock()
			defer r.childMu.Unlock()
			if r.proxyResolver != nil {
				r.proxyResolver.ResolveNow(resolver.ResolveNowOptions{})
			}
		}()
	}
	return nil
}

// wrappingClientConn serves as an intermediary between the parent ClientConn