Skip to content

Commit cdff8b7

Browse files
authored
Fix task cancellation exception on shut down (#93)
1 parent 85dd065 commit cdff8b7

2 files changed

Lines changed: 26 additions & 24 deletions

File tree

src/Winton.Extensions.Configuration.Consul/ConsulConfigurationProvider.cs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ public ConsulConfigurationProvider(
4545
public void Dispose()
4646
{
4747
_cancellationTokenSource.Cancel();
48-
_pollTask?.Wait(500);
4948
_cancellationTokenSource.Dispose();
5049
}
5150

@@ -57,20 +56,22 @@ public override void Load()
5756
return;
5857
}
5958

60-
DoLoad().GetAwaiter().GetResult();
59+
var cancellationToken = _cancellationTokenSource.Token;
60+
61+
DoLoad(cancellationToken).GetAwaiter().GetResult();
6162

6263
// Polling starts after the initial load to ensure no concurrent access to the key from this instance
6364
if (_source.ReloadOnChange)
6465
{
65-
_pollTask = Task.Run(PollingLoop);
66+
_pollTask = Task.Run(() => PollingLoop(cancellationToken), cancellationToken);
6667
}
6768
}
6869

69-
private async Task DoLoad()
70+
private async Task DoLoad(CancellationToken cancellationToken)
7071
{
7172
try
7273
{
73-
var result = await GetKvPairs(false).ConfigureAwait(false);
74+
var result = await GetKvPairs(false, cancellationToken).ConfigureAwait(false);
7475

7576
if (result.HasValue())
7677
{
@@ -94,7 +95,7 @@ private async Task DoLoad()
9495
}
9596
}
9697

97-
private async Task<QueryResult<KVPair[]>> GetKvPairs(bool waitForChange)
98+
private async Task<QueryResult<KVPair[]>> GetKvPairs(bool waitForChange, CancellationToken cancellationToken)
9899
{
99100
using var consulClient = _consulClientFactory.Create();
100101
var queryOptions = new QueryOptions
@@ -106,27 +107,25 @@ private async Task<QueryResult<KVPair[]>> GetKvPairs(bool waitForChange)
106107
var result =
107108
await consulClient
108109
.KV
109-
.List(_source.Key, queryOptions, _cancellationTokenSource.Token)
110+
.List(_source.Key, queryOptions, cancellationToken)
110111
.ConfigureAwait(false);
111112

112113
return result.StatusCode switch
113114
{
114115
HttpStatusCode.OK => result,
115116
HttpStatusCode.NotFound => result,
116-
_ =>
117-
throw
118-
new Exception($"Error loading configuration from consul. Status code: {result.StatusCode}.")
119-
};
117+
_ => throw new Exception($"Error loading configuration from consul. Status code: {result.StatusCode}.")
118+
};
120119
}
121120

122-
private async Task PollingLoop()
121+
private async Task PollingLoop(CancellationToken cancellationToken)
123122
{
124123
var consecutiveFailureCount = 0;
125-
while (!_cancellationTokenSource.Token.IsCancellationRequested)
124+
while (!cancellationToken.IsCancellationRequested)
126125
{
127126
try
128127
{
129-
var result = await GetKvPairs(true).ConfigureAwait(false);
128+
var result = await GetKvPairs(true, cancellationToken).ConfigureAwait(false);
130129

131130
if (result.HasValue() && result.LastIndex > _lastIndex)
132131
{
@@ -143,7 +142,7 @@ private async Task PollingLoop()
143142
_source.OnWatchException?.Invoke(
144143
new ConsulWatchExceptionContext(exception, ++consecutiveFailureCount, _source)) ??
145144
TimeSpan.FromSeconds(5);
146-
await Task.Delay(wait, _cancellationTokenSource.Token);
145+
await Task.Delay(wait, cancellationToken);
147146
}
148147
}
149148
}

test/Winton.Extensions.Configuration.Consul.Test/ConsulConfigurationProviderTests.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ public sealed class Dispose : ConsulConfigurationProviderTests
7272
private async Task ShouldCancelPollingTaskWhenReloading()
7373
{
7474
var expectedKvCalls = 0;
75-
var pollingCancelled = new TaskCompletionSource<CancellationToken>();
76-
CancellationToken cancellationToken = default;
75+
var pollingCancelled = new TaskCompletionSource<bool>();
76+
7777
_source.ReloadOnChange = true;
7878
_source.Optional = true;
7979
_kvEndpoint
@@ -87,18 +87,21 @@ private async Task ShouldCancelPollingTaskWhenReloading()
8787
}
8888

8989
expectedKvCalls++;
90-
if (cancellationToken == default)
90+
if (token.CanBeCanceled)
9191
{
92-
cancellationToken = token;
92+
token.Register(() => pollingCancelled.TrySetResult(true));
9393
}
9494
})
95-
.Returns(
96-
pollingCancelled.Task.IsCompleted
97-
? Task.FromCanceled<QueryResult<KVPair[]>>(pollingCancelled.Task.Result)
98-
: Task.FromResult(new QueryResult<KVPair[]> { StatusCode = HttpStatusCode.OK }));
95+
.Returns<string, QueryOptions, CancellationToken>(
96+
(_, __, token) =>
97+
token.IsCancellationRequested
98+
? Task.FromCanceled<QueryResult<KVPair[]>>(token)
99+
: Task.Delay(5).ContinueWith(t => new QueryResult<KVPair[]> { StatusCode = HttpStatusCode.OK }));
99100

100101
_provider.Load();
101-
cancellationToken.Register(() => pollingCancelled.SetResult(cancellationToken));
102+
103+
// allow polling loop to spin up
104+
await Task.Delay(25);
102105

103106
_provider.Dispose();
104107

0 commit comments

Comments
 (0)