Skip to content

Commit 7f3a961

Browse files
committed
fix(transactions): abort failed queued upgrades
1 parent 0013a07 commit 7f3a961

2 files changed

Lines changed: 64 additions & 0 deletions

File tree

src/Orleans.Transactions/State/ReaderWriterLock.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ public async Task<TResult> EnterLock<TResult>(
9090
{
9191
if (!resolvable)
9292
{
93+
Rollback(transactionId);
94+
lockWorker.Notify();
9395
throw new OrleansTransactionLockUpgradeException(transactionId.ToString());
9496
}
9597
else

test/Transactions/Orleans.Transactions.Tests/TransactionRecoveryLatencyTests.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,68 @@ await Assert.ThrowsAsync<OrleansCascadingAbortException>(
308308
Assert.Equal(0, conflictingOperationCount);
309309
}
310310

311+
[Fact]
312+
public async Task UnresolvableQueuedWriteUpgradeAbortsItsPendingOperations()
313+
{
314+
var queue = new GatedCancelTransactionQueue(
315+
CreateParticipant("resource", ParticipantId.Role.Resource),
316+
new TestActivationLifetime());
317+
var currentTransactionId = Guid.NewGuid();
318+
var upgradingTransactionId = Guid.NewGuid();
319+
var conflictingTransactionId = Guid.NewGuid();
320+
var higherPriority = new DateTime(2026, 8, 21, 12, 0, 0, DateTimeKind.Utc);
321+
var lowerPriority = higherPriority.AddTicks(1);
322+
var transactionTimeout = TimeSpan.FromMinutes(1);
323+
var upgradingOperationCount = 0;
324+
var conflictingOperationCount = 0;
325+
326+
await queue.RWLock.EnterLock(
327+
currentTransactionId,
328+
higherPriority,
329+
transactionTimeout,
330+
new AccessCounter(),
331+
isRead: false,
332+
exclusiveLock: false,
333+
static () => 0);
334+
var queuedRead = queue.RWLock.EnterLock(
335+
upgradingTransactionId,
336+
lowerPriority,
337+
transactionTimeout,
338+
new AccessCounter(),
339+
isRead: true,
340+
exclusiveLock: false,
341+
() => ++upgradingOperationCount);
342+
var conflictingRead = queue.RWLock.EnterLock(
343+
conflictingTransactionId,
344+
higherPriority,
345+
transactionTimeout,
346+
new AccessCounter(),
347+
isRead: true,
348+
exclusiveLock: false,
349+
() => ++conflictingOperationCount);
350+
351+
await Assert.ThrowsAsync<OrleansTransactionLockUpgradeException>(
352+
() => queue.RWLock.EnterLock(
353+
upgradingTransactionId,
354+
lowerPriority,
355+
transactionTimeout,
356+
new AccessCounter { Reads = 1 },
357+
isRead: false,
358+
exclusiveLock: false,
359+
static () => 0));
360+
await Assert.ThrowsAsync<OrleansCascadingAbortException>(
361+
() => queuedRead.WaitAsync(TestContext.Current.CancellationToken));
362+
Assert.Equal(0, upgradingOperationCount);
363+
Assert.False(conflictingRead.IsCompleted);
364+
365+
queue.RWLock.Rollback(currentTransactionId);
366+
queue.RWLock.Notify();
367+
368+
Assert.Equal(1, await conflictingRead.WaitAsync(TestContext.Current.CancellationToken));
369+
Assert.Equal(0, upgradingOperationCount);
370+
Assert.Equal(1, conflictingOperationCount);
371+
}
372+
311373
[Fact]
312374
public async Task LocalAbortCompletesManagerDecisionAfterDispatchAndBeforeCleanupSettles()
313375
{

0 commit comments

Comments
 (0)