Skip to content

Commit

Permalink
Merge pull request #2206 from fredericDelaporte/5.1.x-2172
Browse files Browse the repository at this point in the history
Fix dependent transaction failure in 5.1.x
  • Loading branch information
fredericDelaporte committed Sep 14, 2019
2 parents 8e58f44 + d08675c commit ad2183d
Show file tree
Hide file tree
Showing 14 changed files with 311 additions and 18 deletions.
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

0 comments on commit ad2183d

Please sign in to comment.