diff --git a/src/Microsoft.Extensions.ServiceDiscovery/Http/HttpServiceEndPointResolver.cs b/src/Microsoft.Extensions.ServiceDiscovery/Http/HttpServiceEndPointResolver.cs index e9e80bc76b..da7fee6dd4 100644 --- a/src/Microsoft.Extensions.ServiceDiscovery/Http/HttpServiceEndPointResolver.cs +++ b/src/Microsoft.Extensions.ServiceDiscovery/Http/HttpServiceEndPointResolver.cs @@ -122,7 +122,7 @@ private void CleanupResolvers() { lock (_lock) { - if (_cleanupTask is { IsCompleted: true }) + if (_cleanupTask is null or { IsCompleted: true }) { _cleanupTask = CleanupResolversAsyncCore(); } @@ -159,9 +159,9 @@ private sealed class ResolverEntry : IAsyncDisposable { private readonly ServiceEndPointResolver _resolver; private readonly IServiceEndPointSelector _selector; - private const ulong CountMask = unchecked((ulong)-1); - private const ulong RecentUseFlag = 1UL << 61; - private const ulong DisposingFlag = 1UL << 62; + private const ulong CountMask = ~(RecentUseFlag | DisposingFlag); + private const ulong RecentUseFlag = 1UL << 62; + private const ulong DisposingFlag = 1UL << 63; private ulong _status; private TaskCompletionSource? _onDisposed; diff --git a/src/Microsoft.Extensions.ServiceDiscovery/ServiceEndPointResolverRegistry.cs b/src/Microsoft.Extensions.ServiceDiscovery/ServiceEndPointResolverRegistry.cs index 8d039f2d74..fb71bb9b85 100644 --- a/src/Microsoft.Extensions.ServiceDiscovery/ServiceEndPointResolverRegistry.cs +++ b/src/Microsoft.Extensions.ServiceDiscovery/ServiceEndPointResolverRegistry.cs @@ -121,7 +121,7 @@ private void CleanupResolvers() { lock (_lock) { - if (_cleanupTask is { IsCompleted: true }) + if (_cleanupTask is null or { IsCompleted: true }) { _cleanupTask = CleanupResolversAsyncCore(); } @@ -155,9 +155,9 @@ private ResolverEntry CreateResolver(string serviceName) private sealed class ResolverEntry(ServiceEndPointResolver resolver) : IAsyncDisposable { private readonly ServiceEndPointResolver _resolver = resolver; - private const ulong CountMask = unchecked((ulong)-1); - private const ulong RecentUseFlag = 1UL << 61; - private const ulong DisposingFlag = 1UL << 62; + private const ulong CountMask = ~(RecentUseFlag | DisposingFlag); + private const ulong RecentUseFlag = 1UL << 62; + private const ulong DisposingFlag = 1UL << 63; private ulong _status; private TaskCompletionSource? _onDisposed; diff --git a/tests/Microsoft.Extensions.ServiceDiscovery.Tests/ServiceEndPointResolverTests.cs b/tests/Microsoft.Extensions.ServiceDiscovery.Tests/ServiceEndPointResolverTests.cs index c435f965fe..ff964eb28f 100644 --- a/tests/Microsoft.Extensions.ServiceDiscovery.Tests/ServiceEndPointResolverTests.cs +++ b/tests/Microsoft.Extensions.ServiceDiscovery.Tests/ServiceEndPointResolverTests.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Primitives; using Microsoft.Extensions.ServiceDiscovery.Abstractions; +using Microsoft.Extensions.ServiceDiscovery.Http; using Xunit; namespace Microsoft.Extensions.ServiceDiscovery.Tests; @@ -135,6 +136,80 @@ public async Task ResolveServiceEndPoint() } } + [Fact] + public async Task ResolveServiceEndPointOneShot() + { + var cts = new[] { new CancellationTokenSource() }; + var innerResolver = new FakeEndPointResolver( + resolveAsync: (collection, ct) => + { + collection.AddChangeToken(new CancellationChangeToken(cts[0].Token)); + collection.EndPoints.Add(ServiceEndPoint.Create(new IPEndPoint(IPAddress.Parse("127.1.1.1"), 8080))); + + if (cts[0].Token.IsCancellationRequested) + { + cts[0] = new(); + collection.EndPoints.Add(ServiceEndPoint.Create(new IPEndPoint(IPAddress.Parse("127.1.1.2"), 8888))); + } + return default; + }, + disposeAsync: () => default); + var resolverProvider = new FakeEndPointResolverProvider(name => (true, innerResolver)); + var services = new ServiceCollection() + .AddSingleton(resolverProvider) + .AddServiceDiscoveryCore() + .BuildServiceProvider(); + var resolver = services.GetRequiredService(); + + Assert.NotNull(resolver); + var initialEndPoints = await resolver.GetEndPointsAsync("http://basket", CancellationToken.None).ConfigureAwait(false); + Assert.NotNull(initialEndPoints); + var sep = Assert.Single(initialEndPoints); + var ip = Assert.IsType(sep.EndPoint); + Assert.Equal(IPAddress.Parse("127.1.1.1"), ip.Address); + Assert.Equal(8080, ip.Port); + + await services.DisposeAsync().ConfigureAwait(false); + } + + [Fact] + public async Task ResolveHttpServiceEndPointOneShot() + { + var cts = new[] { new CancellationTokenSource() }; + var innerResolver = new FakeEndPointResolver( + resolveAsync: (collection, ct) => + { + collection.AddChangeToken(new CancellationChangeToken(cts[0].Token)); + collection.EndPoints.Add(ServiceEndPoint.Create(new IPEndPoint(IPAddress.Parse("127.1.1.1"), 8080))); + + if (cts[0].Token.IsCancellationRequested) + { + cts[0] = new(); + collection.EndPoints.Add(ServiceEndPoint.Create(new IPEndPoint(IPAddress.Parse("127.1.1.2"), 8888))); + } + return default; + }, + disposeAsync: () => default); + var fakeResolverProvider = new FakeEndPointResolverProvider(name => (true, innerResolver)); + var services = new ServiceCollection() + .AddSingleton(fakeResolverProvider) + .AddServiceDiscoveryCore() + .BuildServiceProvider(); + var selectorProvider = services.GetRequiredService(); + var resolverProvider = services.GetRequiredService(); + await using var resolver = new HttpServiceEndPointResolver(resolverProvider, selectorProvider, TimeProvider.System); + + Assert.NotNull(resolver); + var httpRequest = new HttpRequestMessage(HttpMethod.Get, "http://basket"); + var endPoint = await resolver.GetEndpointAsync(httpRequest, CancellationToken.None).ConfigureAwait(false); + Assert.NotNull(endPoint); + var ip = Assert.IsType(endPoint.EndPoint); + Assert.Equal(IPAddress.Parse("127.1.1.1"), ip.Address); + Assert.Equal(8080, ip.Port); + + await services.DisposeAsync().ConfigureAwait(false); + } + [Fact] public async Task ResolveServiceEndPoint_ThrowOnReload() {