Skip to content

Commit 8be1396

Browse files
authored
Merge pull request #94 from Giorgi/claude/investigate-deadlock-CT6ly
Add IsDeadlockError
2 parents 415916e + b317a06 commit 8be1396

1 file changed

Lines changed: 47 additions & 4 deletions

File tree

EntityFramework.Exceptions/Tests/OracleTests.cs

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System.Threading.Tasks;
1+
using System.Linq;
2+
using System.Threading.Tasks;
3+
using EntityFramework.Exceptions.Common;
24
using EntityFramework.Exceptions.Oracle;
35
using Microsoft.EntityFrameworkCore;
46
using Testcontainers.Oracle;
@@ -12,10 +14,51 @@ public OracleTests(OracleTestContextFixture fixture) : base(fixture.DemoContext)
1214
{
1315
}
1416

15-
[Fact(Skip = "Skipping as oracle can't trigger deadlock.")]
16-
public override Task Deadlock()
17+
[Fact]
18+
public override async Task Deadlock()
1719
{
18-
return Task.CompletedTask;
20+
var p1 = DemoContext.Products.Add(new() { Name = "Test1" });
21+
var p2 = DemoContext.Products.Add(new() { Name = "Test2" });
22+
await DemoContext.SaveChangesAsync();
23+
24+
var id1 = p1.Entity.Id;
25+
var id2 = p2.Entity.Id;
26+
27+
await using var controlContext = new DemoContext(DemoContext.Options);
28+
await using var transaction1 = await DemoContext.Database.BeginTransactionAsync();
29+
await using var transaction2 = await controlContext.Database.BeginTransactionAsync();
30+
31+
// Each transaction locks one row
32+
await DemoContext.Products.Where(c => c.Id == id1)
33+
.ExecuteUpdateAsync(c => c.SetProperty(p => p.Name, "Test11"));
34+
await controlContext.Products.Where(c => c.Id == id2)
35+
.ExecuteUpdateAsync(c => c.SetProperty(p => p.Name, "Test21"));
36+
37+
// Start both cross-updates concurrently to create a deadlock cycle
38+
var task1 = Task.Run(() => DemoContext.Products
39+
.Where(c => c.Id == id2)
40+
.ExecuteUpdateAsync(c => c.SetProperty(p => p.Name, "Test22")));
41+
var task2 = Task.Run(() => controlContext.Products
42+
.Where(c => c.Id == id1)
43+
.ExecuteUpdateAsync(c => c.SetProperty(p => p.Name, "Test12")));
44+
45+
// Oracle only rolls back the victim's statement, not its transaction,
46+
// so the non-victim remains blocked. Use WhenAny to catch the victim first.
47+
var completedTask = await Task.WhenAny(task1, task2);
48+
await Assert.ThrowsAsync<DeadlockException>(() => completedTask);
49+
50+
// Roll back the victim's transaction to release its earlier locks
51+
// and unblock the other session.
52+
if (completedTask == task1)
53+
{
54+
await transaction1.RollbackAsync();
55+
await task2;
56+
}
57+
else
58+
{
59+
await transaction2.RollbackAsync();
60+
await task1;
61+
}
1962
}
2063
}
2164

0 commit comments

Comments
 (0)