Skip to content

Commit 311f878

Browse files
authored
Change subchannel BalancerAddress when attributes change (#2228)
1 parent b421751 commit 311f878

14 files changed

+299
-158
lines changed

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

+21-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -30,7 +30,9 @@ namespace Grpc.Net.Client.Balancer;
3030
/// </summary>
3131
public sealed class BalancerAddress
3232
{
33-
private BalancerAttributes? _attributes;
33+
// Internal so address attributes can be compared without using the Attributes property.
34+
// The property allocates an empty collection if one isn't already present.
35+
internal BalancerAttributes? _attributes;
3436

3537
/// <summary>
3638
/// Initializes a new instance of the <see cref="BalancerAddress"/> class with the specified <see cref="DnsEndPoint"/>.
@@ -48,7 +50,7 @@ public BalancerAddress(DnsEndPoint endPoint)
4850
/// <param name="host">The host.</param>
4951
/// <param name="port">The port.</param>
5052
[DebuggerStepThrough]
51-
public BalancerAddress(string host, int port) : this(new DnsEndPoint(host, port))
53+
public BalancerAddress(string host, int port) : this(new BalancerEndPoint(host, port))
5254
{
5355
}
5456

@@ -69,5 +71,21 @@ public override string ToString()
6971
{
7072
return $"{EndPoint.Host}:{EndPoint.Port}";
7173
}
74+
75+
private sealed class BalancerEndPoint : DnsEndPoint
76+
{
77+
private string? _cachedToString;
78+
79+
public BalancerEndPoint(string host, int port) : base(host, port)
80+
{
81+
}
82+
83+
public override string ToString()
84+
{
85+
// Improve ToString performance when logging by caching ToString.
86+
// Don't include DnsEndPoint address family.
87+
return _cachedToString ??= $"{Host}:{Port}";
88+
}
89+
}
7290
}
7391
#endif

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

+81-12
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,22 @@ public sealed class BalancerAttributes : IDictionary<string, object?>, IReadOnly
3838
/// <summary>
3939
/// Gets a read-only collection of metadata attributes.
4040
/// </summary>
41-
public static readonly BalancerAttributes Empty = new BalancerAttributes(new ReadOnlyDictionary<string, object?>(new Dictionary<string, object?>()));
41+
public static readonly BalancerAttributes Empty = new BalancerAttributes(new Dictionary<string, object?>(), readOnly: true);
4242

43-
private readonly IDictionary<string, object?> _attributes;
43+
private readonly Dictionary<string, object?> _attributes;
44+
private readonly bool _readOnly;
4445

4546
/// <summary>
4647
/// Initializes a new instance of the <see cref="BalancerAttributes"/> class.
4748
/// </summary>
48-
public BalancerAttributes() : this(new Dictionary<string, object?>())
49+
public BalancerAttributes() : this(new Dictionary<string, object?>(), readOnly: false)
4950
{
5051
}
5152

52-
private BalancerAttributes(IDictionary<string, object?> attributes)
53+
private BalancerAttributes(Dictionary<string, object?> attributes, bool readOnly)
5354
{
5455
_attributes = attributes;
56+
_readOnly = readOnly;
5557
}
5658

5759
object? IDictionary<string, object?>.this[string key]
@@ -62,28 +64,49 @@ private BalancerAttributes(IDictionary<string, object?> attributes)
6264
}
6365
set
6466
{
67+
ValidateReadOnly();
6568
_attributes[key] = value;
6669
}
6770
}
6871

6972
ICollection<string> IDictionary<string, object?>.Keys => _attributes.Keys;
7073
ICollection<object?> IDictionary<string, object?>.Values => _attributes.Values;
7174
int ICollection<KeyValuePair<string, object?>>.Count => _attributes.Count;
72-
bool ICollection<KeyValuePair<string, object?>>.IsReadOnly => _attributes.IsReadOnly;
75+
bool ICollection<KeyValuePair<string, object?>>.IsReadOnly => _readOnly || ((ICollection<KeyValuePair<string, object?>>)_attributes).IsReadOnly;
7376
IEnumerable<string> IReadOnlyDictionary<string, object?>.Keys => _attributes.Keys;
7477
IEnumerable<object?> IReadOnlyDictionary<string, object?>.Values => _attributes.Values;
7578
int IReadOnlyCollection<KeyValuePair<string, object?>>.Count => _attributes.Count;
7679
object? IReadOnlyDictionary<string, object?>.this[string key] => _attributes[key];
77-
void IDictionary<string, object?>.Add(string key, object? value) => _attributes.Add(key, value);
78-
void ICollection<KeyValuePair<string, object?>>.Add(KeyValuePair<string, object?> item) => _attributes.Add(item);
79-
void ICollection<KeyValuePair<string, object?>>.Clear() => _attributes.Clear();
80+
void IDictionary<string, object?>.Add(string key, object? value)
81+
{
82+
ValidateReadOnly();
83+
_attributes.Add(key, value);
84+
}
85+
void ICollection<KeyValuePair<string, object?>>.Add(KeyValuePair<string, object?> item)
86+
{
87+
ValidateReadOnly();
88+
((ICollection<KeyValuePair<string, object?>>)_attributes).Add(item);
89+
}
90+
void ICollection<KeyValuePair<string, object?>>.Clear()
91+
{
92+
ValidateReadOnly();
93+
_attributes.Clear();
94+
}
8095
bool ICollection<KeyValuePair<string, object?>>.Contains(KeyValuePair<string, object?> item) => _attributes.Contains(item);
8196
bool IDictionary<string, object?>.ContainsKey(string key) => _attributes.ContainsKey(key);
82-
void ICollection<KeyValuePair<string, object?>>.CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex) => _attributes.CopyTo(array, arrayIndex);
97+
void ICollection<KeyValuePair<string, object?>>.CopyTo(KeyValuePair<string, object?>[] array, int arrayIndex) => ((ICollection<KeyValuePair<string, object?>>)_attributes).CopyTo(array, arrayIndex);
8398
IEnumerator<KeyValuePair<string, object?>> IEnumerable<KeyValuePair<string, object?>>.GetEnumerator() => _attributes.GetEnumerator();
8499
IEnumerator System.Collections.IEnumerable.GetEnumerator() => ((System.Collections.IEnumerable)_attributes).GetEnumerator();
85-
bool IDictionary<string, object?>.Remove(string key) => _attributes.Remove(key);
86-
bool ICollection<KeyValuePair<string, object?>>.Remove(KeyValuePair<string, object?> item) => _attributes.Remove(item);
100+
bool IDictionary<string, object?>.Remove(string key)
101+
{
102+
ValidateReadOnly();
103+
return _attributes.Remove(key);
104+
}
105+
bool ICollection<KeyValuePair<string, object?>>.Remove(KeyValuePair<string, object?> item)
106+
{
107+
ValidateReadOnly();
108+
return ((ICollection<KeyValuePair<string, object?>>)_attributes).Remove(item);
109+
}
87110
bool IDictionary<string, object?>.TryGetValue(string key, out object? value) => _attributes.TryGetValue(key, out value);
88111
bool IReadOnlyDictionary<string, object?>.ContainsKey(string key) => _attributes.ContainsKey(key);
89112
bool IReadOnlyDictionary<string, object?>.TryGetValue(string key, out object? value) => _attributes.TryGetValue(key, out value);
@@ -121,6 +144,7 @@ public bool TryGetValue<TValue>(BalancerAttributesKey<TValue> key, [MaybeNullWhe
121144
/// <param name="value">The value.</param>
122145
public void Set<TValue>(BalancerAttributesKey<TValue> key, TValue value)
123146
{
147+
ValidateReadOnly();
124148
_attributes[key.Key] = value;
125149
}
126150

@@ -135,10 +159,55 @@ public void Set<TValue>(BalancerAttributesKey<TValue> key, TValue value)
135159
/// </returns>
136160
public bool Remove<TValue>(BalancerAttributesKey<TValue> key)
137161
{
162+
ValidateReadOnly();
138163
return _attributes.Remove(key.Key);
139164
}
140165

141-
internal string DebuggerToString()
166+
private void ValidateReadOnly()
167+
{
168+
if (_readOnly)
169+
{
170+
throw new NotSupportedException("Collection is read-only.");
171+
}
172+
}
173+
174+
internal static bool DeepEquals(BalancerAttributes? x, BalancerAttributes? y)
175+
{
176+
var xValues = x?._attributes;
177+
var yValues = y?._attributes;
178+
179+
if (ReferenceEquals(xValues, yValues))
180+
{
181+
return true;
182+
}
183+
184+
if (xValues == null || yValues == null)
185+
{
186+
return false;
187+
}
188+
189+
if (xValues.Count != yValues.Count)
190+
{
191+
return false;
192+
}
193+
194+
foreach (var kvp in xValues)
195+
{
196+
if (!yValues.TryGetValue(kvp.Key, out var value))
197+
{
198+
return false;
199+
}
200+
201+
if (!Equals(kvp.Value, value))
202+
{
203+
return false;
204+
}
205+
}
206+
207+
return true;
208+
}
209+
210+
private string DebuggerToString()
142211
{
143212
return $"Count = {_attributes.Count}";
144213
}

src/Grpc.Net.Client/Balancer/Internal/BalancerAddressEqualityComparer.cs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#region Copyright notice and license
1+
#region Copyright notice and license
22

33
// Copyright 2019 The gRPC Authors
44
//
@@ -17,8 +17,6 @@
1717
#endregion
1818

1919
#if SUPPORT_LOAD_BALANCING
20-
using System;
21-
using System.Collections.Generic;
2220
using System.Diagnostics.CodeAnalysis;
2321

2422
namespace Grpc.Net.Client.Balancer.Internal;
@@ -44,7 +42,7 @@ public bool Equals(BalancerAddress? x, BalancerAddress? y)
4442
return false;
4543
}
4644

47-
return true;
45+
return BalancerAttributes.DeepEquals(x._attributes, y._attributes);
4846
}
4947

5048
public int GetHashCode([DisallowNull] BalancerAddress obj)

src/Grpc.Net.Client/Balancer/Internal/BalancerHttpHandler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ internal async ValueTask<Stream> OnConnect(SocketsHttpConnectionContext context,
9494
}
9595

9696
Debug.Assert(context.DnsEndPoint.Equals(currentAddress.EndPoint), "Context endpoint should equal address endpoint.");
97-
return await subchannel.Transport.GetStreamAsync(currentAddress, cancellationToken).ConfigureAwait(false);
97+
return await subchannel.Transport.GetStreamAsync(currentAddress.EndPoint, cancellationToken).ConfigureAwait(false);
9898
}
9999
#endif
100100

src/Grpc.Net.Client/Balancer/Internal/ISubchannelTransport.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#endregion
1818

1919
#if SUPPORT_LOAD_BALANCING
20+
using System.Net;
2021
using Grpc.Shared;
2122

2223
namespace Grpc.Net.Client.Balancer.Internal;
@@ -28,11 +29,11 @@ namespace Grpc.Net.Client.Balancer.Internal;
2829
/// </summary>
2930
internal interface ISubchannelTransport : IDisposable
3031
{
31-
BalancerAddress? CurrentAddress { get; }
32+
DnsEndPoint? CurrentEndPoint { get; }
3233
TimeSpan? ConnectTimeout { get; }
3334
TransportStatus TransportStatus { get; }
3435

35-
ValueTask<Stream> GetStreamAsync(BalancerAddress address, CancellationToken cancellationToken);
36+
ValueTask<Stream> GetStreamAsync(DnsEndPoint endPoint, CancellationToken cancellationToken);
3637
ValueTask<ConnectResult> TryConnectAsync(ConnectContext context);
3738

3839
void Disconnect();

src/Grpc.Net.Client/Balancer/Internal/PassiveSubchannelTransport.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -35,43 +35,43 @@ namespace Grpc.Net.Client.Balancer.Internal;
3535
internal class PassiveSubchannelTransport : ISubchannelTransport, IDisposable
3636
{
3737
private readonly Subchannel _subchannel;
38-
private BalancerAddress? _currentAddress;
38+
private DnsEndPoint? _currentEndPoint;
3939

4040
public PassiveSubchannelTransport(Subchannel subchannel)
4141
{
4242
_subchannel = subchannel;
4343
}
4444

45-
public BalancerAddress? CurrentAddress => _currentAddress;
45+
public DnsEndPoint? CurrentEndPoint => _currentEndPoint;
4646
public TimeSpan? ConnectTimeout { get; }
4747
public TransportStatus TransportStatus => TransportStatus.Passive;
4848

4949
public void Disconnect()
5050
{
51-
_currentAddress = null;
51+
_currentEndPoint = null;
5252
_subchannel.UpdateConnectivityState(ConnectivityState.Idle, "Disconnected.");
5353
}
5454

5555
public ValueTask<ConnectResult> TryConnectAsync(ConnectContext context)
5656
{
5757
Debug.Assert(_subchannel._addresses.Count == 1);
58-
Debug.Assert(CurrentAddress == null);
58+
Debug.Assert(CurrentEndPoint == null);
5959

6060
var currentAddress = _subchannel._addresses[0];
6161

6262
_subchannel.UpdateConnectivityState(ConnectivityState.Connecting, "Passively connecting.");
63-
_currentAddress = currentAddress;
63+
_currentEndPoint = currentAddress.EndPoint;
6464
_subchannel.UpdateConnectivityState(ConnectivityState.Ready, "Passively connected.");
6565

6666
return new ValueTask<ConnectResult>(ConnectResult.Success);
6767
}
6868

6969
public void Dispose()
7070
{
71-
_currentAddress = null;
71+
_currentEndPoint = null;
7272
}
7373

74-
public ValueTask<Stream> GetStreamAsync(BalancerAddress address, CancellationToken cancellationToken)
74+
public ValueTask<Stream> GetStreamAsync(DnsEndPoint endPoint, CancellationToken cancellationToken)
7575
{
7676
throw new NotSupportedException();
7777
}

0 commit comments

Comments
 (0)