Skip to content

Fix dependent transaction failure in 5.1.x #2206

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
Sep 14, 2019
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
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 5.1.5.{build}
version: 5.1.6.{build}
image: Visual Studio 2017
environment:
matrix:
Expand Down
2 changes: 1 addition & 1 deletion build-common/NHibernate.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<VersionMajor Condition="'$(VersionMajor)' == ''">5</VersionMajor>
<VersionMinor Condition="'$(VersionMinor)' == ''">1</VersionMinor>
<VersionPatch Condition="'$(VersionPatch)' == ''">5</VersionPatch>
<VersionPatch Condition="'$(VersionPatch)' == ''">6</VersionPatch>
<VersionSuffix Condition="'$(VersionSuffix)' == ''"></VersionSuffix>

<VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix>
Expand Down
4 changes: 2 additions & 2 deletions build-common/common.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

<!-- This is used only for build folder -->
<!-- TODO: Either remove or refactor to use NHibernate.props -->
<property name="project.version" value="5.1.5" overwrite="false" />
<property name="project.version.numeric" value="5.1.5" overwrite="false" />
<property name="project.version" value="5.1.6" overwrite="false" />
<property name="project.version.numeric" value="5.1.6" overwrite="false" />

<!-- properties used to connect to database for testing -->
<include buildfile="nhibernate-properties.xml" />
Expand Down
8 changes: 8 additions & 0 deletions releasenotes.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
Build 5.1.6
=============================

Release notes - NHibernate - Version 5.1.6

** Bug
* #2172 Using DependentTransaction fails

Build 5.1.5
=============================

Expand Down
12 changes: 12 additions & 0 deletions src/AsyncGenerator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,18 @@
applyChanges: true
analyzation:
methodConversion:
- conversion: Ignore
name: CanUseDependentTransaction
containingTypeName: DistributedSystemTransactionFixture
- conversion: Ignore
name: CanUseSessionWithManyDependentTransaction
containingTypeName: DistributedSystemTransactionFixture
- conversion: Ignore
name: CanUseDependentTransaction
containingTypeName: SystemTransactionFixture
- conversion: Ignore
name: CanUseSessionWithManyDependentTransaction
containingTypeName: SystemTransactionFixture
- conversion: Copy
hasAttributeName: OneTimeSetUpAttribute
- conversion: Copy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
using System.Threading;
using System.Transactions;
using log4net;
using log4net.Repository.Hierarchy;
using NHibernate.Cfg;
using NHibernate.Engine;
using NHibernate.Linq;
using NHibernate.Test.TransactionTest;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.SystemTransactions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
using NHibernate.Cfg;
using NHibernate.Driver;
using NHibernate.Engine;
using NHibernate.Linq;
using NHibernate.Test.TransactionTest;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.SystemTransactions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
using System.Threading;
using System.Transactions;
using log4net;
using log4net.Repository.Hierarchy;
using NHibernate.Cfg;
using NHibernate.Engine;
using NHibernate.Linq;
using NHibernate.Test.TransactionTest;
using NUnit.Framework;

Expand Down Expand Up @@ -711,6 +709,117 @@ public void AdditionalJoinDoesNotThrow()
}
}

[Theory]
public void CanUseDependentTransaction(bool explicitFlush)
{
if (!TestDialect.SupportsDependentTransaction)
Assert.Ignore("Dialect does not support dependent transactions");
IgnoreIfUnsupported(explicitFlush);

try
{
using (var committable = new CommittableTransaction())
{
System.Transactions.Transaction.Current = committable;
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
{
System.Transactions.Transaction.Current = clone;

using (var s = OpenSession())
{
if (!AutoJoinTransaction)
s.JoinTransaction();
s.Save(new Person());

if (explicitFlush)
s.Flush();
clone.Complete();
}
}

System.Transactions.Transaction.Current = committable;
committable.Commit();
}
}
finally
{
System.Transactions.Transaction.Current = null;
}
}

[Theory]
public void CanUseSessionWithManyDependentTransaction(bool explicitFlush)
{
if (!TestDialect.SupportsDependentTransaction)
Assert.Ignore("Dialect does not support dependent transactions");
IgnoreIfUnsupported(explicitFlush);

try
{
using (var s = Sfi.WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession())
{
using (var committable = new CommittableTransaction())
{
System.Transactions.Transaction.Current = committable;
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
{
System.Transactions.Transaction.Current = clone;
if (!AutoJoinTransaction)
s.JoinTransaction();
// Acquire the connection
var count = s.Query<Person>().Count();
Assert.That(count, Is.EqualTo(0), "Unexpected initial entity count.");
clone.Complete();
}

using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
{
System.Transactions.Transaction.Current = clone;
if (!AutoJoinTransaction)
s.JoinTransaction();
s.Save(new Person());

if (explicitFlush)
s.Flush();

clone.Complete();
}

using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
{
System.Transactions.Transaction.Current = clone;
if (!AutoJoinTransaction)
s.JoinTransaction();
var count = s.Query<Person>().Count();
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after committed insert.");
clone.Complete();
}

System.Transactions.Transaction.Current = committable;
committable.Commit();
}
}
}
finally
{
System.Transactions.Transaction.Current = null;
}

DodgeTransactionCompletionDelayIfRequired();

using (var s = OpenSession())
{
using (var tx = new TransactionScope())
{
if (!AutoJoinTransaction)
s.JoinTransaction();
var count = s.Query<Person>().Count();
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after global commit.");
tx.Complete();
}
}
}

private void DodgeTransactionCompletionDelayIfRequired()
{
if (Sfi.ConnectionProvider.Driver.HasDelayedDistributedTransactionCompletion)
Expand Down
115 changes: 114 additions & 1 deletion src/NHibernate.Test/SystemTransactions/SystemTransactionFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using NHibernate.Cfg;
using NHibernate.Driver;
using NHibernate.Engine;
using NHibernate.Linq;
using NHibernate.Test.TransactionTest;
using NUnit.Framework;

Expand Down Expand Up @@ -509,6 +508,120 @@ public void AdditionalJoinDoesNotThrow()
Assert.DoesNotThrow(() => s.JoinTransaction());
}
}

[Theory]
public void CanUseDependentTransaction(bool explicitFlush)
{
if (!TestDialect.SupportsDependentTransaction)
Assert.Ignore("Dialect does not support dependent transactions");
IgnoreIfUnsupported(explicitFlush);

try
{
using (var committable = new CommittableTransaction())
{
System.Transactions.Transaction.Current = committable;
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
{
System.Transactions.Transaction.Current = clone;

using (var s = OpenSession())
{
if (!AutoJoinTransaction)
s.JoinTransaction();
s.Save(new Person());

if (explicitFlush)
s.Flush();
clone.Complete();
}
}

System.Transactions.Transaction.Current = committable;
committable.Commit();
}
}
finally
{
System.Transactions.Transaction.Current = null;
}
}

[Theory]
public void CanUseSessionWithManyDependentTransaction(bool explicitFlush)
{
if (!TestDialect.SupportsDependentTransaction)
Assert.Ignore("Dialect does not support dependent transactions");
IgnoreIfUnsupported(explicitFlush);
// ODBC with SQL-Server always causes system transactions to go distributed, which causes their transaction completion to run
// asynchronously. But ODBC enlistment also check the previous transaction in a way that do not guard against it
// being concurrently disposed of. See https://github.com/nhibernate/nhibernate-core/pull/1505 for more details.
if (Sfi.ConnectionProvider.Driver is OdbcDriver)
Assert.Ignore("ODBC sometimes fails on second scope by checking the previous transaction status, which may yield an object disposed exception");

try
{
using (var s = WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession())
{
using (var committable = new CommittableTransaction())
{
System.Transactions.Transaction.Current = committable;
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
{
System.Transactions.Transaction.Current = clone;
if (!AutoJoinTransaction)
s.JoinTransaction();
// Acquire the connection
var count = s.Query<Person>().Count();
Assert.That(count, Is.EqualTo(0), "Unexpected initial entity count.");
clone.Complete();
}

using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
{
System.Transactions.Transaction.Current = clone;
if (!AutoJoinTransaction)
s.JoinTransaction();
s.Save(new Person());

if (explicitFlush)
s.Flush();

clone.Complete();
}

using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
{
System.Transactions.Transaction.Current = clone;
if (!AutoJoinTransaction)
s.JoinTransaction();
var count = s.Query<Person>().Count();
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after committed insert.");
clone.Complete();
}

System.Transactions.Transaction.Current = committable;
committable.Commit();
}
}
}
finally
{
System.Transactions.Transaction.Current = null;
}

using (var s = OpenSession())
{
using (var tx = new TransactionScope())
{
if (!AutoJoinTransaction)
s.JoinTransaction();
var count = s.Query<Person>().Count();
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after global commit.");
tx.Complete();
}
}
}
}

[TestFixture]
Expand Down
8 changes: 8 additions & 0 deletions src/NHibernate.Test/TestDialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,13 @@ public bool SupportsSqlType(SqlType sqlType)
/// Supports the modulo operator on decimal types
/// </summary>
public virtual bool SupportsModuloOnDecimal => true;

/// <summary>
/// Some databases fail with dependent transaction, typically when their driver tries to access the transaction
/// state from its two PC: the dependent transaction is meant to be disposed of before completing the actual
/// transaction, so it is usually disposed at this point, and its state cannot be read. (Drivers should always
/// clone transactions for avoiding this trouble.)
/// </summary>
public virtual bool SupportsDependentTransaction => true;
}
}
6 changes: 6 additions & 0 deletions src/NHibernate.Test/TestDialects/PostgreSQL83TestDialect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,11 @@ public override bool SupportsNullCharactersInUtfStrings
{
get { return false; }
}

/// <summary>
/// Npgsql does not clone the transaction in its context, and uses it in its prepare phase. When that was a
/// dependent transaction, it is then usually already disposed of, causing Npgsql to crash.
/// </summary>
public override bool SupportsDependentTransaction => false;
}
}
6 changes: 6 additions & 0 deletions src/NHibernate/AdoNet/ConnectionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,13 @@ public DbCommand CreateCommand()
public void EnlistIfRequired(System.Transactions.Transaction transaction)
{
if (transaction == _currentSystemTransaction)
{
// Short-circuit after having stored the transaction : they may be equal, but not the same reference.
// And the previous one may be an already disposed dependent clone, in which case we need to update
// our reference.
_currentSystemTransaction = transaction;
return;
}

_currentSystemTransaction = transaction;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using NHibernate.AdoNet;
using NHibernate.Engine;
using NHibernate.Engine.Transaction;
using NHibernate.Impl;
using NHibernate.Util;

namespace NHibernate.Transaction
Expand Down
Loading