• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20365

27 Jul 2026 06:04AM UTC coverage: 89.199% (+0.07%) from 89.125%
#20365

push

github

web-flow
core: Implement LB Delay Observability (Proposal A121) (#12807)

This PR implements **Attempt-Level RPC Delay Observability** across the core channel transport, built-in load balancers, xDS policies, and the OpenTelemetry telemetry plugin, aligned with [gRPC Proposal A121](https://github.com/grpc/proposal/pull/556).

38276 of 42911 relevant lines covered (89.2%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.4
/../xds/src/main/java/io/grpc/xds/LazyLoadBalancer.java
1
/*
2
 * Copyright 2024 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.xds;
18

19
import com.google.common.base.Preconditions;
20
import io.grpc.ConnectivityState;
21
import io.grpc.LoadBalancer;
22
import io.grpc.Status;
23
import io.grpc.util.ForwardingLoadBalancer;
24

25
/**
26
 * A load balancer that starts in IDLE instead of CONNECTING. Once it starts connecting, it
27
 * instantiates its delegate.
28
 */
29
final class LazyLoadBalancer extends ForwardingLoadBalancer {
30
  private LoadBalancer delegate;
31

32
  public LazyLoadBalancer(Helper helper, LoadBalancer.Factory delegateFactory) {
1✔
33
    this.delegate = new LazyDelegate(helper, delegateFactory);
1✔
34
  }
1✔
35

36
  @Override
37
  protected LoadBalancer delegate() {
38
    return delegate;
1✔
39
  }
40

41
  @Override
42
  public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
43
    return delegate.acceptResolvedAddresses(resolvedAddresses);
1✔
44
  }
45

46
  private final class LazyDelegate extends LoadBalancer {
47
    private final Helper helper;
48
    private final LoadBalancer.Factory delegateFactory;
49
    private ResolvedAddresses addresses;
50
    private Status error;
51
    private boolean updatedBalancingState;
52

53
    public LazyDelegate(Helper helper, LoadBalancer.Factory delegateFactory) {
1✔
54
      this.helper = Preconditions.checkNotNull(helper, "helper");
1✔
55
      this.delegateFactory = Preconditions.checkNotNull(delegateFactory, "delegateFactory");
1✔
56
    }
1✔
57

58
    private LoadBalancer activate() {
59
      if (delegate != this) {
1✔
60
        return delegate;
1✔
61
      }
62
      delegate = delegateFactory.newLoadBalancer(helper);
1✔
63
      if (addresses != null) {
1✔
64
        delegate.acceptResolvedAddresses(addresses);
1✔
65
      }
66
      if (error != null) {
1✔
67
        delegate.handleNameResolutionError(error);
×
68
      }
69
      return delegate;
1✔
70
    }
71

72
    @Override
73
    public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
74
      this.addresses = resolvedAddresses;
1✔
75
      this.error = null;
1✔
76
      initializeBalancingState();
1✔
77
      return Status.OK;
1✔
78
    }
79

80
    @Override
81
    public void handleNameResolutionError(Status error) {
82
      // Preserve addresses, because even old addresses may be used by the real policy
83
      this.error = error;
×
84
      initializeBalancingState();
×
85
    }
×
86

87
    private void initializeBalancingState() {
88
      if (updatedBalancingState) {
1✔
89
        return;
×
90
      }
91
      helper.updateBalancingState(ConnectivityState.IDLE, new LazyPicker());
1✔
92
      updatedBalancingState = true;
1✔
93
    }
1✔
94

95
    @Override
96
    public void requestConnection() {
97
      activate().requestConnection();
1✔
98
    }
1✔
99

100
    @Override
101
    public void shutdown() {
102
      delegate = new NoopLoadBalancer();
1✔
103
    }
1✔
104

105
    private final class LazyPicker extends SubchannelPicker {
1✔
106
      @Override
107
      public PickResult pickSubchannel(PickSubchannelArgs args) {
108
        // activate() is a no-op after shutdown()
109
        helper.getSynchronizationContext().execute(LazyDelegate.this::activate);
1✔
110
        return PickResult.withNoResult(
1✔
111
            "connecting", "lazy: waiting for connection");
112
      }
113
    }
114
  }
115

116
  public static final class Factory extends LoadBalancer.Factory {
117
    private final LoadBalancer.Factory delegate;
118

119
    public Factory(LoadBalancer.Factory delegate) {
1✔
120
      this.delegate = Preconditions.checkNotNull(delegate, "delegate");
1✔
121
    }
1✔
122

123
    @Override public LoadBalancer newLoadBalancer(Helper helper) {
124
      return new LazyLoadBalancer(helper, delegate);
1✔
125
    }
126
  }
127

128
  private static final class NoopLoadBalancer extends LoadBalancer {
129
    @Override
130
    public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
131
      return Status.OK;
×
132
    }
133

134
    @Override
135
    public void handleNameResolutionError(Status error) {}
×
136

137
    @Override
138
    public void shutdown() {}
×
139
  }
140
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc