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

Code before:
}

// updateTargetResolverState is the StateListener function provided to the
// target resolver via wrappingClientConn. It updates the resolver state by
// storing the target addresses, endpoints, and service configuration, and 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)
	}

	go func() {
		r.childMu.Lock()
		defer r.childMu.Unlock()
		if _, ok := r.proxyResolver.(nopResolver); ok || r.proxyResolver == nil {
			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

Review comment (suggestion):
To avoid spawning a goroutine in most cases, we can check if the `r.proxyAddrs` list is non-empty. The `r.mu` lock that is already held should be sufficient for this check.
```go
if len(r.proxyAddrs) == 0 {
    go func() {
         //  omitted
    }
}
```

Code after:
// 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 !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
}