Skip to content

Commit 5a597c4

Browse files
only check response time for refresh trigger engine
1 parent 9363ea8 commit 5a597c4

File tree

6 files changed

+148
-261
lines changed

6 files changed

+148
-261
lines changed

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs

Lines changed: 96 additions & 218 deletions
Large diffs are not rendered by default.

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/ConfigurationClientExtensions.cs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public static async Task<KeyValueChange> GetKeyValueChange(this ConfigurationCli
7474
};
7575
}
7676

77-
public static async Task<Page<ConfigurationSetting>> GetPageChange(this ConfigurationClient client, KeyValueSelector keyValueSelector, IEnumerable<PageWatcher> pageWatchers, IConfigurationSettingPageIterator pageIterator, bool makeConditionalRequest, CancellationToken cancellationToken)
77+
public static async Task<bool> HavePageChange(this ConfigurationClient client, KeyValueSelector keyValueSelector, IEnumerable<PageWatcher> pageWatchers, IConfigurationSettingPageIterator pageIterator, bool makeConditionalRequest, CancellationToken cancellationToken)
7878
{
7979
if (pageWatchers == null)
8080
{
@@ -99,31 +99,29 @@ public static async Task<Page<ConfigurationSetting>> GetPageChange(this Configur
9999

100100
AsyncPageable<ConfigurationSetting> pageable = client.GetConfigurationSettingsAsync(selector, cancellationToken);
101101

102+
using IEnumerator<PageWatcher> existingPageWatcherEnumerator = pageWatchers.GetEnumerator();
103+
102104
IAsyncEnumerable<Page<ConfigurationSetting>> pages = makeConditionalRequest
103105
? pageable.AsPages(pageIterator, pageWatchers.Select(p => p.Etag))
104106
: pageable.AsPages(pageIterator);
105107

106-
List<PageWatcher> existingWatcherList = pageWatchers.ToList();
107-
108-
int i = 0;
109-
110108
await foreach (Page<ConfigurationSetting> page in pages.ConfigureAwait(false))
111109
{
112110
using Response response = page.GetRawResponse();
113111
DateTimeOffset timestamp = response.GetDate();
114112

115-
if (i >= existingWatcherList.Count ||
113+
if (!existingPageWatcherEnumerator.MoveNext() ||
116114
(response.Status == (int)HttpStatusCode.OK &&
117-
timestamp >= existingWatcherList[i].LastUpdateTime &&
118-
!existingWatcherList[i].Etag.IfNoneMatch.Equals(response.Headers.ETag)))
115+
// if the server response time is later than last server response time, the change is considered detected
116+
timestamp >= existingPageWatcherEnumerator.Current.LastServerResponseTime &&
117+
!existingPageWatcherEnumerator.Current.Etag.IfNoneMatch.Equals(response.Headers.ETag)))
119118
{
120-
return page;
119+
return true;
121120
}
122-
123-
i++;
124121
}
125122

126-
return null;
123+
// Need to check if pages were deleted and no change was found within the new shorter list of page
124+
return existingPageWatcherEnumerator.MoveNext();
127125
}
128126
}
129127
}

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/PageWatcher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@ namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
99
internal class PageWatcher
1010
{
1111
public MatchConditions Etag { get; set; }
12-
public DateTimeOffset LastUpdateTime { get; set; }
12+
public DateTimeOffset LastServerResponseTime { get; set; }
1313
}
1414
}

src/Microsoft.Extensions.Configuration.AzureAppConfiguration/RequestTracingOptions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
//
44
using Microsoft.Extensions.Configuration.AzureAppConfiguration.Extensions;
55
using Microsoft.Extensions.Configuration.AzureAppConfiguration.FeatureManagement;
6-
using Microsoft.Extensions.Configuration.AzureAppConfiguration.SnapshotReference;
76
using System.Net.Mime;
87
using System.Text;
98

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
using Azure.Data.AppConfiguration;
5+
using System;
6+
7+
namespace Microsoft.Extensions.Configuration.AzureAppConfiguration
8+
{
9+
internal class WatchedSetting
10+
{
11+
public ConfigurationSetting Setting { get; set; }
12+
public DateTimeOffset LastServerResponseTime { get; set; }
13+
}
14+
}

tests/Tests.AzureAppConfiguration/Unit/AfdTests.cs

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -270,29 +270,18 @@ public async Task AfdTests_WatchedSettingRefreshAll()
270270
var mockAsyncPageable1 = new MockAsyncPageable(keyValueCollection1, null, 3, responses);
271271

272272
var keyValueCollection2 = new List<ConfigurationSetting>(_kvCollection);
273-
keyValueCollection2[3].Value = "old-value";
273+
keyValueCollection2[3].Value = "updated-value";
274274
string page2_etag2 = Guid.NewGuid().ToString();
275275
var responses2 = new List<MockResponse>()
276276
{
277-
new MockResponse(200, page1_etag, DateTimeOffset.Parse("2025-10-17T09:00:00+08:00")),
278-
new MockResponse(200, page2_etag2, DateTimeOffset.Parse("2025-10-17T09:00:00+08:00")) // stale
277+
new MockResponse(200, page1_etag, DateTimeOffset.Parse("2025-10-17T09:00:01+08:00")),
278+
new MockResponse(200, page2_etag2, DateTimeOffset.Parse("2025-10-17T09:00:01+08:00"))
279279
};
280280
var mockAsyncPageable2 = new MockAsyncPageable(keyValueCollection2, null, 3, responses2);
281281

282-
var keyValueCollection3 = new List<ConfigurationSetting>(_kvCollection);
283-
keyValueCollection3[3].Value = "new-value";
284-
string page2_etag3 = Guid.NewGuid().ToString();
285-
var responses3 = new List<MockResponse>()
286-
{
287-
new MockResponse(200, page1_etag, DateTimeOffset.Parse("2025-10-17T09:00:03+08:00")),
288-
new MockResponse(200, page2_etag3, DateTimeOffset.Parse("2025-10-17T09:00:03+08:00")) // up-to-date
289-
};
290-
var mockAsyncPageable3 = new MockAsyncPageable(keyValueCollection3, null, 3, responses3);
291-
292282
mockClient.SetupSequence(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
293283
.Returns(mockAsyncPageable1)
294-
.Returns(mockAsyncPageable2)
295-
.Returns(mockAsyncPageable3);
284+
.Returns(mockAsyncPageable2);
296285

297286
string etag1 = Guid.NewGuid().ToString();
298287
var setting = ConfigurationModelFactory.ConfigurationSetting(
@@ -303,21 +292,28 @@ public async Task AfdTests_WatchedSettingRefreshAll()
303292
contentType: "text");
304293

305294
string etag2 = Guid.NewGuid().ToString();
295+
var oldSetting = ConfigurationModelFactory.ConfigurationSetting(
296+
"Sentinel",
297+
"old-value",
298+
"label",
299+
eTag: new ETag(etag2),
300+
contentType: "text");
301+
302+
string etag3 = Guid.NewGuid().ToString();
306303
var newSetting = ConfigurationModelFactory.ConfigurationSetting(
307304
"Sentinel",
308305
"new-value",
309306
"label",
310-
eTag: new ETag(etag2),
307+
eTag: new ETag(etag3),
311308
contentType: "text");
312309

313310
mockClient.SetupSequence(c => c.GetConfigurationSettingAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<CancellationToken>()))
314311
.ReturnsAsync(Response.FromValue(setting, new MockResponse(200, etag1, DateTimeOffset.Parse("2025-10-17T09:00:00+08:00"))))
315-
.ReturnsAsync(Response.FromValue(newSetting, new MockResponse(200, etag2, DateTimeOffset.Parse("2025-10-17T09:00:01+08:00"))))
316-
.ReturnsAsync(Response.FromValue(newSetting, new MockResponse(200, etag2, DateTimeOffset.Parse("2025-10-17T09:00:01+08:00"))));
312+
.ReturnsAsync(Response.FromValue(newSetting, new MockResponse(200, etag3, DateTimeOffset.Parse("2025-10-17T09:00:01+08:00"))));
317313

318314
mockClient.SetupSequence(c => c.GetConfigurationSettingAsync(It.IsAny<ConfigurationSetting>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
319-
.ReturnsAsync(Response.FromValue(newSetting, new MockResponse(200, etag2, DateTimeOffset.Parse("2025-10-17T09:00:01+08:00"))))
320-
.ReturnsAsync(Response.FromValue(newSetting, new MockResponse(200, etag2, DateTimeOffset.Parse("2025-10-17T09:00:01+08:00"))));
315+
.ReturnsAsync(Response.FromValue(oldSetting, new MockResponse(200, etag2, DateTimeOffset.Parse("2025-10-17T08:59:59+08:00")))) // stale, should not refresh
316+
.ReturnsAsync(Response.FromValue(newSetting, new MockResponse(200, etag3, DateTimeOffset.Parse("2025-10-17T09:00:01+08:00"))));
321317

322318
var afdEndpoint = new Uri("https://test.b01.azurefd.net");
323319
IConfigurationRefresher refresher = null;
@@ -343,14 +339,14 @@ public async Task AfdTests_WatchedSettingRefreshAll()
343339

344340
await refresher.RefreshAsync();
345341

346-
Assert.Equal("TestValue4", config["TestKey4"]); // should not refresh, because page 2 is out of date
342+
Assert.Equal("TestValue4", config["TestKey4"]); // should not refresh, because sentinel is stale
347343
Assert.Equal("sentinel-value", config["Sentinel"]);
348344

349345
await Task.Delay(1500);
350346

351347
await refresher.RefreshAsync();
352348

353-
Assert.Equal("new-value", config["TestKey4"]);
349+
Assert.Equal("updated-value", config["TestKey4"]);
354350
Assert.Equal("new-value", config["Sentinel"]);
355351
}
356352

@@ -370,28 +366,30 @@ public async Task AfdTests_RegisterAllRefresh()
370366
var mockAsyncPageable1 = new MockAsyncPageable(keyValueCollection1, null, 3, responses);
371367

372368
var keyValueCollection2 = new List<ConfigurationSetting>(_kvCollection);
373-
keyValueCollection2[3].Value = "new-value";
369+
keyValueCollection2[3].Value = "old-value";
374370
string page2_etag2 = Guid.NewGuid().ToString();
375371
var responses2 = new List<MockResponse>()
376372
{
377-
new MockResponse(200, page1_etag, DateTimeOffset.Parse("2025-10-17T09:00:00+08:00")), // stale
378-
new MockResponse(200, page2_etag2, DateTimeOffset.Parse("2025-10-17T09:00:02+08:00"))
373+
new MockResponse(200, page1_etag, DateTimeOffset.Parse("2025-10-17T09:00:00+08:00")),
374+
new MockResponse(200, page2_etag2, DateTimeOffset.Parse("2025-10-17T08:59:59+08:00")) // stale, should not refresh
379375
};
380376
var mockAsyncPageable2 = new MockAsyncPageable(keyValueCollection2, null, 3, responses2);
381377

378+
var keyValueCollection3 = new List<ConfigurationSetting>(_kvCollection);
379+
keyValueCollection3[3].Value = "new-value";
382380
var responses3 = new List<MockResponse>()
383381
{
384-
new MockResponse(200, page1_etag, DateTimeOffset.Parse("2025-10-17T09:00:00+08:00")), // stale
382+
new MockResponse(200, page1_etag, DateTimeOffset.Parse("2025-10-17T09:00:00+08:00")), // up-to-date, should refresh
385383
new MockResponse(200, page2_etag2, DateTimeOffset.Parse("2025-10-17T09:00:02+08:00"))
386384
};
387-
var mockAsyncPageable3 = new MockAsyncPageable(keyValueCollection2, null, 3, responses2);
385+
var mockAsyncPageable3 = new MockAsyncPageable(keyValueCollection3, null, 3, responses3);
388386

389387
var responses4 = new List<MockResponse>()
390388
{
391389
new MockResponse(200, page1_etag, DateTimeOffset.Parse("2025-10-17T09:00:03+08:00")), // up-to-date
392390
new MockResponse(200, page2_etag2, DateTimeOffset.Parse("2025-10-17T09:00:03+08:00"))
393391
};
394-
var mockAsyncPageable4 = new MockAsyncPageable(keyValueCollection2, null, 3, responses4);
392+
var mockAsyncPageable4 = new MockAsyncPageable(keyValueCollection3, null, 3, responses4);
395393

396394
mockClient.SetupSequence(c => c.GetConfigurationSettingsAsync(It.IsAny<SettingSelector>(), It.IsAny<CancellationToken>()))
397395
.Returns(mockAsyncPageable1)
@@ -423,7 +421,7 @@ public async Task AfdTests_RegisterAllRefresh()
423421

424422
await refresher.RefreshAsync();
425423

426-
Assert.Equal("TestValue4", config["TestKey4"]); // should not refresh, because page 1 is stale
424+
Assert.Equal("TestValue4", config["TestKey4"]); // should not refresh, because page 2 is stale
427425

428426
await Task.Delay(1500);
429427

0 commit comments

Comments
 (0)