Skip to content

Commit dbd4e9b

Browse files
committed
[10.x] fix(azure): retry torn membership boundary reads
(cherry picked from commit 9fd511c)
1 parent 800275a commit dbd4e9b

2 files changed

Lines changed: 31 additions & 7 deletions

File tree

‎src/Azure/Orleans.Clustering.AzureStorage/OrleansSiloInstanceManager.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,15 @@ private static bool CanAcceptSnapshot(List<(SiloInstanceTableEntry Entity, strin
338338
var closing = entries[^1].Entity.RowKey == SiloInstanceTableEntry.TABLE_VERSION_ROW_MAX;
339339
// Both markers absent identifies the legacy format, which uses legacy read semantics.
340340
// The first membership write atomically establishes both markers with the new version.
341-
// Once either marker is observed, both are required to validate the snapshot.
341+
// A query overlapping that write can see a partial pair, so retry partial or mismatched pairs.
342342
if (!opening && !closing)
343343
{
344344
return true;
345345
}
346346

347347
if (!opening || !closing)
348348
{
349-
throw new KeyNotFoundException("The membership query must include both ordered boundary version rows.");
349+
return false;
350350
}
351351

352352
var before = entries[0].Entity.MembershipVersion

‎test/Extensions/Orleans.Azure.Tests/AzureMembershipPaginationTests.cs‎

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,19 +125,43 @@ public async Task PerpetualChurnFailsAfterBoundWithClusterContext()
125125
[Theory]
126126
[InlineData(true, false)]
127127
[InlineData(false, true)]
128-
public async Task SingleMissingBoundaryRowFails(bool opening, bool closing)
128+
public async Task SingleMissingBoundaryRowRetries(bool opening, bool closing)
129129
{
130130
var storage = new ScriptedMembershipTableReadStorage();
131-
var query = FencedQuery(1, Silo("silo-1", "s1"));
131+
var query = FencedQuery(1, Silo("silo-1", "stale"));
132132
if (!opening) query.Entries.RemoveAt(0);
133133
if (!closing) query.Entries.RemoveAt(query.Entries.Count - 1);
134134
storage.AddQuery(query);
135+
storage.AddQuery(FencedQuery(2, Silo("silo-1", "current")));
135136

136-
var exception = await Assert.ThrowsAsync<KeyNotFoundException>(
137+
var result = await CreateManager(storage).FindAllSiloEntries(TestContext.Current.CancellationToken);
138+
139+
Assert.Equal("current", result.Single(entry => entry.Entity.RowKey == "silo-1").ETag);
140+
Assert.Equal("2", result.Single(entry => entry.Entity.RowKey == SiloInstanceTableEntry.TABLE_VERSION_ROW).Entity.MembershipVersion);
141+
Assert.Equal(2, storage.QueryCount);
142+
}
143+
144+
[Theory]
145+
[InlineData(true, false)]
146+
[InlineData(false, true)]
147+
public async Task PersistentSingleBoundaryRowFailsAfterBound(bool opening, bool closing)
148+
{
149+
var storage = new ScriptedMembershipTableReadStorage();
150+
for (var attempt = 0; attempt < OrleansSiloInstanceManager.MaxMembershipSnapshotAttempts; attempt++)
151+
{
152+
var query = FencedQuery(1, Silo("silo-1", "s1"));
153+
if (!opening) query.Entries.RemoveAt(0);
154+
if (!closing) query.Entries.RemoveAt(query.Entries.Count - 1);
155+
storage.AddQuery(query);
156+
}
157+
158+
var exception = await Assert.ThrowsAsync<InconsistentStateException>(
137159
() => CreateManager(storage).FindAllSiloEntries(TestContext.Current.CancellationToken));
138160

139-
Assert.Equal("The membership query must include both ordered boundary version rows.", exception.Message);
140-
Assert.Equal(1, storage.QueryCount);
161+
Assert.Equal(
162+
$"Unable to read a consistent membership snapshot for cluster '{ClusterId}' from table '{TableName}' after {OrleansSiloInstanceManager.MaxMembershipSnapshotAttempts} attempts.",
163+
exception.Message);
164+
Assert.Equal(OrleansSiloInstanceManager.MaxMembershipSnapshotAttempts, storage.QueryCount);
141165
}
142166

143167
[Theory]

0 commit comments

Comments
 (0)