Skip to content

Commit 282fa2a

Browse files
authored
Remove Subchannel.State and track state inside load balancer (#1590)
1 parent 82f8908 commit 282fa2a

File tree

3 files changed

+41
-15
lines changed

3 files changed

+41
-15
lines changed

src/Grpc.Net.Client/Balancer/PickResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public void Complete(CompletionContext context)
8383
/// A result created with a <see cref="Balancer.Subchannel"/> won't necessarily be used by a gRPC call.
8484
/// The subchannel's state may change at the same time the picker is making a decision. That means the
8585
/// decision may be made with outdated information. For example, a picker may return a subchannel
86-
/// with a <see cref="Subchannel.State"/> that is <see cref="ConnectivityState.Ready"/>, but
86+
/// with a state that is <see cref="ConnectivityState.Ready"/>, but
8787
/// becomes <see cref="ConnectivityState.Idle"/> when the subchannel is about to be used. In that situation
8888
/// the gRPC call waits for the load balancer to react to the new state and create a new picker.
8989
/// </para>

src/Grpc.Net.Client/Balancer/Subchannel.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ public sealed class Subchannel : IDisposable
4848
internal ISubchannelTransport Transport { get; set; } = default!;
4949
internal int Id { get; }
5050

51+
/// <summary>
52+
/// Connectivity state is internal rather than public because it can be updated by multiple threads while
53+
/// a load balancer is building the picker.
54+
/// Load balancers that care about multiple subchannels should track state by subscribing to
55+
/// Subchannel.OnStateChanged and storing results.
56+
/// </summary>
57+
internal ConnectivityState State => _state;
58+
5159
private readonly ConnectionManager _manager;
5260
private readonly ILogger _logger;
5361

@@ -60,11 +68,6 @@ public sealed class Subchannel : IDisposable
6068
/// </summary>
6169
public BalancerAddress? CurrentAddress => Transport.CurrentAddress;
6270

63-
/// <summary>
64-
/// Gets the connectivity state.
65-
/// </summary>
66-
public ConnectivityState State => _state;
67-
6871
/// <summary>
6972
/// Gets the metadata attributes.
7073
/// </summary>
@@ -327,7 +330,7 @@ public override string ToString()
327330
{
328331
lock (Lock)
329332
{
330-
return $"Id: {Id}, Addresses: {string.Join(", ", _addresses)}, State: {State}, Current address: {CurrentAddress}";
333+
return $"Id: {Id}, Addresses: {string.Join(", ", _addresses)}, State: {_state}, Current address: {CurrentAddress}";
331334
}
332335
}
333336

@@ -345,7 +348,7 @@ public IReadOnlyList<BalancerAddress> GetAddresses()
345348

346349
/// <summary>
347350
/// Disposes the <see cref="Subchannel"/>.
348-
/// The subchannel <see cref="State"/> is updated to <see cref="ConnectivityState.Shutdown"/>.
351+
/// The subchannel state is updated to <see cref="ConnectivityState.Shutdown"/>.
349352
/// After dispose the subchannel should no longer be returned by the latest <see cref="SubchannelPicker"/>.
350353
/// </summary>
351354
public void Dispose()

src/Grpc.Net.Client/Balancer/SubchannelsLoadBalancer.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,14 @@ private void ResolverError(Status status)
9494
return null;
9595
}
9696

97-
private int? FindSubchannel(List<AddressSubchannel> addressSubchannels, Subchannel subchannel)
97+
private AddressSubchannel? FindSubchannel(List<AddressSubchannel> addressSubchannels, Subchannel subchannel)
9898
{
9999
for (var i = 0; i < addressSubchannels.Count; i++)
100100
{
101101
var s = addressSubchannels[i];
102102
if (Equals(s.Subchannel, subchannel))
103103
{
104-
return i;
104+
return s;
105105
}
106106
}
107107

@@ -189,7 +189,7 @@ private void UpdateBalancingState(Status status)
189189
for (var i = 0; i < _addressSubchannels.Count; i++)
190190
{
191191
var addressSubchannel = _addressSubchannels[i];
192-
if (addressSubchannel.Subchannel.State == ConnectivityState.Ready)
192+
if (addressSubchannel.LastKnownState == ConnectivityState.Ready)
193193
{
194194
readySubchannels.Add(addressSubchannel.Subchannel);
195195
}
@@ -201,7 +201,7 @@ private void UpdateBalancingState(Status status)
201201
var isConnecting = false;
202202
foreach (var subchannel in _addressSubchannels)
203203
{
204-
var state = subchannel.Subchannel.State;
204+
var state = subchannel.LastKnownState;
205205

206206
if (state == ConnectivityState.Connecting || state == ConnectivityState.Idle)
207207
{
@@ -239,13 +239,14 @@ private void UpdateChannelState(ConnectivityState state, SubchannelPicker subcha
239239

240240
private void UpdateSubchannelState(Subchannel subchannel, SubchannelState state)
241241
{
242-
var index = FindSubchannel(_addressSubchannels, subchannel);
243-
if (index == null)
242+
var addressSubchannel = FindSubchannel(_addressSubchannels, subchannel);
243+
if (addressSubchannel == null)
244244
{
245245
SubchannelsLoadBalancerLog.IgnoredSubchannelStateChange(_logger, subchannel.Id);
246246
return;
247247
}
248248

249+
addressSubchannel.UpdateKnownState(state.State);
249250
SubchannelsLoadBalancerLog.ProcessingSubchannelStateChanged(_logger, subchannel.Id, state.State, state.Status);
250251

251252
UpdateBalancingState(state.Status);
@@ -297,7 +298,29 @@ protected override void Dispose(bool disposing)
297298
/// <returns>A subchannel picker.</returns>
298299
protected abstract SubchannelPicker CreatePicker(IReadOnlyList<Subchannel> readySubchannels);
299300

300-
private record AddressSubchannel(Subchannel Subchannel, BalancerAddress Address);
301+
private class AddressSubchannel
302+
{
303+
private ConnectivityState _lastKnownState;
304+
305+
public AddressSubchannel(Subchannel subchannel, BalancerAddress address)
306+
{
307+
Subchannel = subchannel;
308+
Address = address;
309+
_lastKnownState = ConnectivityState.Idle;
310+
}
311+
312+
// Track connectivity state that has been updated to load balancer.
313+
// This is used instead of state on subchannel because subchannel state
314+
// can be updated from other threads while load balancer is running.
315+
public ConnectivityState LastKnownState => _lastKnownState;
316+
public Subchannel Subchannel { get; }
317+
public BalancerAddress Address { get; }
318+
319+
public void UpdateKnownState(ConnectivityState knownState)
320+
{
321+
_lastKnownState = knownState;
322+
}
323+
}
301324
}
302325

303326
internal static class SubchannelsLoadBalancerLog

0 commit comments

Comments
 (0)