Skip to content

Fix failing reprocessing tests on certain providers. #387

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 60 additions & 42 deletions Extensions/Xtensive.Orm.Reprocessing.Tests/Tests/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,58 @@

namespace Xtensive.Orm.Reprocessing.Tests.ReprocessingContext
{
public class Context
public class Context : IDisposable
{
private readonly Domain domain;

public int Count;
private AutoResetEvent wait1 = new AutoResetEvent(false);
private AutoResetEvent wait2 = new AutoResetEvent(false);

public void Deadlock(bool first, IsolationLevel? isolationLevel, TransactionOpenMode? transactionOpenMode)
public bool Disposed { get; private set; }

/// <summary>
/// Root runner.
/// </summary>
public void Run(
IsolationLevel? isolationLevel,
TransactionOpenMode? transactionOpenMode,
Action<bool, IsolationLevel?, TransactionOpenMode?> action)
{
TestContext.WriteLine("Context.DeadLock entered");
domain.WithStrategy(ExecuteActionStrategy.HandleUniqueConstraintViolation).WithIsolationLevel(isolationLevel.GetValueOrDefault(IsolationLevel.RepeatableRead)).WithTransactionOpenMode(transactionOpenMode.GetValueOrDefault(TransactionOpenMode.New)).Execute(
domain.Execute(
session => {
session.Remove(session.Query.All<Foo>());
session.Remove(session.Query.All<Bar>());
session.Remove(session.Query.All<Bar2>());
_ = new Bar(session);
_ = new Foo(session);
});

Parallel.Invoke(
() => action(true, isolationLevel, transactionOpenMode),
() => action(false, isolationLevel, transactionOpenMode));
}

#region Actions

// The actions that can be passed to root runner method (Run)
// Some might be wrapped by other actons,
// others used only directrly from runner method

public void Deadlock(bool first, IsolationLevel? isolationLevel, TransactionOpenMode? transactionOpenMode)
{
domain.WithStrategy(ExecuteActionStrategy.HandleUniqueConstraintViolation)
.WithIsolationLevel(isolationLevel.GetValueOrDefault(IsolationLevel.RepeatableRead))
.WithTransactionOpenMode(transactionOpenMode.GetValueOrDefault(TransactionOpenMode.New))
.Execute(session => {
_ = Interlocked.Increment(ref Count);
_ = new Bar2(session, DateTime.Now, Guid.NewGuid()) { Name = Guid.NewGuid().ToString() };
if (first) {
_ = session.Query.All<Foo>().Lock(LockMode.Exclusive, LockBehavior.Wait).ToArray();
if (wait1 != null) {
_ = wait1.Set();
_ = wait2.WaitOne();
wait1.Dispose();
wait1 = null;
}
_ = session.Query.All<Bar>().Lock(LockMode.Exclusive, LockBehavior.Wait).ToArray();
Expand All @@ -40,12 +73,12 @@ public void Deadlock(bool first, IsolationLevel? isolationLevel, TransactionOpen
if (wait2 != null) {
_ = wait2.Set();
_ = wait1.WaitOne();
wait2.Dispose();
wait2 = null;
}
_ = session.Query.All<Foo>().Lock(LockMode.Exclusive, LockBehavior.Wait).ToArray();
}
});
TestContext.WriteLine("Context.DeadLock left");
}

public void External(
Expand All @@ -54,7 +87,6 @@ public void External(
TransactionOpenMode? transactionOpenMode,
Action<Session, bool, IsolationLevel?, TransactionOpenMode?> action)
{
TestContext.WriteLine("Context.External entered");
using (var session = domain.OpenSession())
using (var tran = isolationLevel == null ? null : session.OpenTransaction()) {
if (tran != null) {
Expand All @@ -76,7 +108,6 @@ public void External(
tran.Complete();
}
}
TestContext.WriteLine("Context.External left");
}

public void Parent(
Expand All @@ -86,7 +117,6 @@ public void Parent(
IExecuteActionStrategy strategy,
Action<bool, IsolationLevel?, TransactionOpenMode?> action)
{
TestContext.WriteLine("Context.Parent1 entered");
domain.WithStrategy(strategy)
.WithIsolationLevel(isolationLevel.GetValueOrDefault(IsolationLevel.RepeatableRead))
.WithTransactionOpenMode(transactionOpenMode.GetValueOrDefault(TransactionOpenMode.New))
Expand All @@ -106,7 +136,6 @@ public void Parent(
}
action(first, isolationLevel, transactionOpenMode);
});
TestContext.WriteLine("Context.Parent1 left");
}

public void Parent(
Expand All @@ -117,7 +146,6 @@ public void Parent(
IExecuteActionStrategy strategy,
Action<bool, IsolationLevel?, TransactionOpenMode?> action)
{
TestContext.WriteLine("Context.Parent2 entered");
domain.WithStrategy(strategy)
.WithSession(session)
.WithIsolationLevel(isolationLevel.GetValueOrDefault(IsolationLevel.RepeatableRead))
Expand All @@ -138,38 +166,15 @@ public void Parent(
}
action(first, isolationLevel, transactionOpenMode);
});
TestContext.WriteLine("Context.Parent2 left");
}

public void Run(
IsolationLevel? isolationLevel,
TransactionOpenMode? transactionOpenMode,
Action<bool, IsolationLevel?, TransactionOpenMode?> action)
{
TestContext.WriteLine("Context.Run entered");
domain.Execute(
session => {
session.Remove(session.Query.All<Foo>());
session.Remove(session.Query.All<Bar>());
session.Remove(session.Query.All<Bar2>());
_ = new Bar(session);
_ = new Foo(session);
});
TestContext.WriteLine("Context.Run executed Domain.Execute");
TestContext.WriteLine("Context.Run Parallel.Invoke started");
Parallel.Invoke(
() => action(true, isolationLevel, transactionOpenMode),
() => action(false, isolationLevel, transactionOpenMode));
TestContext.WriteLine("Context.Run Parallel.Invoke ended");
TestContext.WriteLine("Context.Run left");
}

public void UniqueConstraintViolation(
bool first, IsolationLevel? isolationLevel, TransactionOpenMode? transactionOpenMode)
{
TestContext.WriteLine("Context.UniqueConstraintViolation entered");
domain.WithStrategy(ExecuteActionStrategy.HandleUniqueConstraintViolation).WithIsolationLevel(isolationLevel.GetValueOrDefault(IsolationLevel.RepeatableRead)).WithTransactionOpenMode(transactionOpenMode.GetValueOrDefault(TransactionOpenMode.New)).Execute(
session => {
domain.WithStrategy(ExecuteActionStrategy.HandleUniqueConstraintViolation)
.WithIsolationLevel(isolationLevel.GetValueOrDefault(IsolationLevel.RepeatableRead))
.WithTransactionOpenMode(transactionOpenMode.GetValueOrDefault(TransactionOpenMode.New))
.Execute(session => {
_ = Interlocked.Increment(ref Count);
session.EnsureTransactionIsStarted();
_ = new Bar2(session, DateTime.Now, Guid.NewGuid()) { Name = Guid.NewGuid().ToString() };
Expand All @@ -179,27 +184,29 @@ public void UniqueConstraintViolation(
if (wait1 != null && wait2 != null) {
_ = wait1.Set();
_ = wait2.WaitOne();
wait1.Dispose();
wait1 = null;
}
}
else if (wait2 != null && wait2 != null) {
_ = wait2.Set();
_ = wait1.WaitOne();
wait2.Dispose();
wait2 = null;
}
_ = new Foo(session) { Name = name };
}
session.SaveChanges();
});
TestContext.WriteLine("Context.UniqueConstraintViolation left");
}

public void UniqueConstraintViolationPrimaryKey(
bool first, IsolationLevel? isolationLevel, TransactionOpenMode? transactionOpenMode)
{
TestContext.WriteLine("Context.UniqueConstraintViolationPrimaryKey entered");
domain.WithStrategy(ExecuteActionStrategy.HandleUniqueConstraintViolation).WithIsolationLevel(isolationLevel.GetValueOrDefault(IsolationLevel.RepeatableRead)).WithTransactionOpenMode(transactionOpenMode.GetValueOrDefault(TransactionOpenMode.New)).Execute(
session => {
domain.WithStrategy(ExecuteActionStrategy.HandleUniqueConstraintViolation)
.WithIsolationLevel(isolationLevel.GetValueOrDefault(IsolationLevel.RepeatableRead))
.WithTransactionOpenMode(transactionOpenMode.GetValueOrDefault(TransactionOpenMode.New))
.Execute(session => {
_ = Interlocked.Increment(ref Count);
session.EnsureTransactionIsStarted();
_ = new Bar2(session, DateTime.Now, Guid.NewGuid()) { Name = Guid.NewGuid().ToString() };
Expand All @@ -211,25 +218,36 @@ public void UniqueConstraintViolationPrimaryKey(
if (w1 != null && w2 != null) {
_ = w1.Set();
_ = w2.WaitOne();
wait1.Dispose();
wait1 = null;
}
}
else if (w1 != null && w2 != null) {
_ = w2.Set();
_ = w1.WaitOne();
wait2.Dispose();
wait2 = null;
}
_ = new Foo(session, id) { Name = Guid.NewGuid().ToString() };
}
session.SaveChanges();
});
TestContext.WriteLine("Context.UniqueConstraintViolationPrimaryKey left");
}
#endregion

public Context(Domain domain)
{
this.domain = domain;
}

public void Dispose()
{
if (Disposed)
return;
Disposed = true;
wait1?.Dispose();
wait2?.Dispose();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,77 +10,77 @@
using NUnit.Framework;
using TestCommon.Model;
using Xtensive.Orm.Reprocessing.Tests.ReprocessingContext;
using Xtensive.Orm.Tests;

namespace Xtensive.Orm.Reprocessing.Tests
{
[TestFixture, Timeout(DefaultTestTimeout * 4)]
[TestFixture]
public class DeadlockReprocessing : ReprocessingBaseTest
{
protected override void CheckRequirements()
{
base.CheckRequirements();
Require.ProviderIsNot(StorageProvider.Firebird, "Throws timeout operation instead of deadlock, which is not reprocessible.");
}

[Test, Timeout(DefaultTestTimeout)]
public void SimpleDeadlockTest()
{
Console.WriteLine("Test started");

var context = new Context(Domain);
context.Run(IsolationLevel.Serializable, null, context.Deadlock);
Assert.That(context.Count, Is.EqualTo(3));
Assert.That(Bar2Count(), Is.EqualTo(2));
using (var context = new Context(Domain)) {
context.Run(IsolationLevel.Serializable, null, context.Deadlock);
Assert.That(context.Count, Is.EqualTo(3));
Assert.That(Bar2Count(), Is.EqualTo(2));
}
}

[Test, Timeout(DefaultTestTimeout)]
public void NestedSerializableDeadlockTest()
{
Console.WriteLine("Test started");

var context = new Context(Domain);
context.Run(
IsolationLevel.Serializable,
null,
(b, level, open) => context.Parent(b, level, open, ExecuteActionStrategy.HandleReprocessableException, context.Deadlock));
Assert.That(context.Count, Is.EqualTo(3));
Assert.That(Bar2Count(), Is.EqualTo(4));
using (var context = new Context(Domain)) {
context.Run(
IsolationLevel.Serializable,
null,
(b, level, open) => context.Parent(b, level, open, ExecuteActionStrategy.HandleReprocessableException, context.Deadlock));
Assert.That(context.Count, Is.EqualTo(3));
Assert.That(Bar2Count(), Is.EqualTo(4));
}
}

[Test, Timeout(DefaultTestTimeout)]
public void NestedSnapshotDeadlockTest()
{
Console.WriteLine("Test started");

var context = new Context(Domain);
context.Run(
IsolationLevel.Snapshot,
null,
(b, level, open) => context.Parent(b, level, open, ExecuteActionStrategy.HandleReprocessableException, context.Deadlock));
Assert.That(context.Count, Is.EqualTo(3));
Assert.That(Bar2Count(), Is.EqualTo(4));
using (var context = new Context(Domain)) {
context.Run(
IsolationLevel.Snapshot,
null,
(b, level, open) => context.Parent(b, level, open, ExecuteActionStrategy.HandleReprocessableException, context.Deadlock));
Assert.That(context.Count, Is.EqualTo(3));
Assert.That(Bar2Count(), Is.EqualTo(4));
}
}

[Test, Timeout(DefaultTestTimeout)]
public void NestedNestedSerializableSerializableTest()
{
Console.WriteLine("Test started");

//nested nested serializable deadlock
var context = new Context(Domain);
context.Run(
IsolationLevel.Serializable,
null,
(b, level, open) =>
context.Parent(
b,
level,
open,
ExecuteActionStrategy.HandleReprocessableException,
(b1, level1, open1) =>
context.Parent(b1, level1, open1, ExecuteActionStrategy.HandleReprocessableException, context.Deadlock)));
Assert.That(context.Count, Is.EqualTo(3));
Assert.That(Bar2Count(), Is.EqualTo(6));
using (var context = new Context(Domain)) {
context.Run(
IsolationLevel.Serializable,
null,
(b, level, open) =>
context.Parent(
b,
level,
open,
ExecuteActionStrategy.HandleReprocessableException,
(b1, level1, open1) =>
context.Parent(b1, level1, open1, ExecuteActionStrategy.HandleReprocessableException, context.Deadlock)));
Assert.That(context.Count, Is.EqualTo(3));
Assert.That(Bar2Count(), Is.EqualTo(6));
}
}

private int Bar2Count()
{
return Domain.Execute(session => session.Query.All<Bar2>().Count());
}
private int Bar2Count() => Domain.Execute(session => session.Query.All<Bar2>().Count());
}
}

Expand Down
Loading