Skip to content

Commit ad2183d

Browse files
Merge pull request #2206 from fredericDelaporte/5.1.x-2172
Fix dependent transaction failure in 5.1.x
2 parents 8e58f44 + d08675c commit ad2183d

14 files changed

+311
-18
lines changed

appveyor.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: 5.1.5.{build}
1+
version: 5.1.6.{build}
22
image: Visual Studio 2017
33
environment:
44
matrix:

build-common/NHibernate.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<VersionMajor Condition="'$(VersionMajor)' == ''">5</VersionMajor>
44
<VersionMinor Condition="'$(VersionMinor)' == ''">1</VersionMinor>
5-
<VersionPatch Condition="'$(VersionPatch)' == ''">5</VersionPatch>
5+
<VersionPatch Condition="'$(VersionPatch)' == ''">6</VersionPatch>
66
<VersionSuffix Condition="'$(VersionSuffix)' == ''"></VersionSuffix>
77

88
<VersionPrefix>$(VersionMajor).$(VersionMinor).$(VersionPatch)</VersionPrefix>

build-common/common.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

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

1919
<!-- properties used to connect to database for testing -->
2020
<include buildfile="nhibernate-properties.xml" />

releasenotes.txt

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
Build 5.1.6
2+
=============================
3+
4+
Release notes - NHibernate - Version 5.1.6
5+
6+
** Bug
7+
* #2172 Using DependentTransaction fails
8+
19
Build 5.1.5
210
=============================
311

src/AsyncGenerator.yml

+12
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,18 @@
170170
applyChanges: true
171171
analyzation:
172172
methodConversion:
173+
- conversion: Ignore
174+
name: CanUseDependentTransaction
175+
containingTypeName: DistributedSystemTransactionFixture
176+
- conversion: Ignore
177+
name: CanUseSessionWithManyDependentTransaction
178+
containingTypeName: DistributedSystemTransactionFixture
179+
- conversion: Ignore
180+
name: CanUseDependentTransaction
181+
containingTypeName: SystemTransactionFixture
182+
- conversion: Ignore
183+
name: CanUseSessionWithManyDependentTransaction
184+
containingTypeName: SystemTransactionFixture
173185
- conversion: Copy
174186
hasAttributeName: OneTimeSetUpAttribute
175187
- conversion: Copy

src/NHibernate.Test/Async/SystemTransactions/DistributedSystemTransactionFixture.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@
1313
using System.Threading;
1414
using System.Transactions;
1515
using log4net;
16-
using log4net.Repository.Hierarchy;
1716
using NHibernate.Cfg;
1817
using NHibernate.Engine;
19-
using NHibernate.Linq;
2018
using NHibernate.Test.TransactionTest;
2119
using NUnit.Framework;
20+
using NHibernate.Linq;
2221

2322
namespace NHibernate.Test.SystemTransactions
2423
{

src/NHibernate.Test/Async/SystemTransactions/SystemTransactionFixture.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
using NHibernate.Cfg;
1717
using NHibernate.Driver;
1818
using NHibernate.Engine;
19-
using NHibernate.Linq;
2019
using NHibernate.Test.TransactionTest;
2120
using NUnit.Framework;
21+
using NHibernate.Linq;
2222

2323
namespace NHibernate.Test.SystemTransactions
2424
{

src/NHibernate.Test/SystemTransactions/DistributedSystemTransactionFixture.cs

+111-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
using System.Threading;
44
using System.Transactions;
55
using log4net;
6-
using log4net.Repository.Hierarchy;
76
using NHibernate.Cfg;
87
using NHibernate.Engine;
9-
using NHibernate.Linq;
108
using NHibernate.Test.TransactionTest;
119
using NUnit.Framework;
1210

@@ -711,6 +709,117 @@ public void AdditionalJoinDoesNotThrow()
711709
}
712710
}
713711

712+
[Theory]
713+
public void CanUseDependentTransaction(bool explicitFlush)
714+
{
715+
if (!TestDialect.SupportsDependentTransaction)
716+
Assert.Ignore("Dialect does not support dependent transactions");
717+
IgnoreIfUnsupported(explicitFlush);
718+
719+
try
720+
{
721+
using (var committable = new CommittableTransaction())
722+
{
723+
System.Transactions.Transaction.Current = committable;
724+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
725+
{
726+
System.Transactions.Transaction.Current = clone;
727+
728+
using (var s = OpenSession())
729+
{
730+
if (!AutoJoinTransaction)
731+
s.JoinTransaction();
732+
s.Save(new Person());
733+
734+
if (explicitFlush)
735+
s.Flush();
736+
clone.Complete();
737+
}
738+
}
739+
740+
System.Transactions.Transaction.Current = committable;
741+
committable.Commit();
742+
}
743+
}
744+
finally
745+
{
746+
System.Transactions.Transaction.Current = null;
747+
}
748+
}
749+
750+
[Theory]
751+
public void CanUseSessionWithManyDependentTransaction(bool explicitFlush)
752+
{
753+
if (!TestDialect.SupportsDependentTransaction)
754+
Assert.Ignore("Dialect does not support dependent transactions");
755+
IgnoreIfUnsupported(explicitFlush);
756+
757+
try
758+
{
759+
using (var s = Sfi.WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession())
760+
{
761+
using (var committable = new CommittableTransaction())
762+
{
763+
System.Transactions.Transaction.Current = committable;
764+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
765+
{
766+
System.Transactions.Transaction.Current = clone;
767+
if (!AutoJoinTransaction)
768+
s.JoinTransaction();
769+
// Acquire the connection
770+
var count = s.Query<Person>().Count();
771+
Assert.That(count, Is.EqualTo(0), "Unexpected initial entity count.");
772+
clone.Complete();
773+
}
774+
775+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
776+
{
777+
System.Transactions.Transaction.Current = clone;
778+
if (!AutoJoinTransaction)
779+
s.JoinTransaction();
780+
s.Save(new Person());
781+
782+
if (explicitFlush)
783+
s.Flush();
784+
785+
clone.Complete();
786+
}
787+
788+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
789+
{
790+
System.Transactions.Transaction.Current = clone;
791+
if (!AutoJoinTransaction)
792+
s.JoinTransaction();
793+
var count = s.Query<Person>().Count();
794+
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after committed insert.");
795+
clone.Complete();
796+
}
797+
798+
System.Transactions.Transaction.Current = committable;
799+
committable.Commit();
800+
}
801+
}
802+
}
803+
finally
804+
{
805+
System.Transactions.Transaction.Current = null;
806+
}
807+
808+
DodgeTransactionCompletionDelayIfRequired();
809+
810+
using (var s = OpenSession())
811+
{
812+
using (var tx = new TransactionScope())
813+
{
814+
if (!AutoJoinTransaction)
815+
s.JoinTransaction();
816+
var count = s.Query<Person>().Count();
817+
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after global commit.");
818+
tx.Complete();
819+
}
820+
}
821+
}
822+
714823
private void DodgeTransactionCompletionDelayIfRequired()
715824
{
716825
if (Sfi.ConnectionProvider.Driver.HasDelayedDistributedTransactionCompletion)

src/NHibernate.Test/SystemTransactions/SystemTransactionFixture.cs

+114-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using NHibernate.Cfg;
77
using NHibernate.Driver;
88
using NHibernate.Engine;
9-
using NHibernate.Linq;
109
using NHibernate.Test.TransactionTest;
1110
using NUnit.Framework;
1211

@@ -509,6 +508,120 @@ public void AdditionalJoinDoesNotThrow()
509508
Assert.DoesNotThrow(() => s.JoinTransaction());
510509
}
511510
}
511+
512+
[Theory]
513+
public void CanUseDependentTransaction(bool explicitFlush)
514+
{
515+
if (!TestDialect.SupportsDependentTransaction)
516+
Assert.Ignore("Dialect does not support dependent transactions");
517+
IgnoreIfUnsupported(explicitFlush);
518+
519+
try
520+
{
521+
using (var committable = new CommittableTransaction())
522+
{
523+
System.Transactions.Transaction.Current = committable;
524+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
525+
{
526+
System.Transactions.Transaction.Current = clone;
527+
528+
using (var s = OpenSession())
529+
{
530+
if (!AutoJoinTransaction)
531+
s.JoinTransaction();
532+
s.Save(new Person());
533+
534+
if (explicitFlush)
535+
s.Flush();
536+
clone.Complete();
537+
}
538+
}
539+
540+
System.Transactions.Transaction.Current = committable;
541+
committable.Commit();
542+
}
543+
}
544+
finally
545+
{
546+
System.Transactions.Transaction.Current = null;
547+
}
548+
}
549+
550+
[Theory]
551+
public void CanUseSessionWithManyDependentTransaction(bool explicitFlush)
552+
{
553+
if (!TestDialect.SupportsDependentTransaction)
554+
Assert.Ignore("Dialect does not support dependent transactions");
555+
IgnoreIfUnsupported(explicitFlush);
556+
// ODBC with SQL-Server always causes system transactions to go distributed, which causes their transaction completion to run
557+
// asynchronously. But ODBC enlistment also check the previous transaction in a way that do not guard against it
558+
// being concurrently disposed of. See https://github.com/nhibernate/nhibernate-core/pull/1505 for more details.
559+
if (Sfi.ConnectionProvider.Driver is OdbcDriver)
560+
Assert.Ignore("ODBC sometimes fails on second scope by checking the previous transaction status, which may yield an object disposed exception");
561+
562+
try
563+
{
564+
using (var s = WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession())
565+
{
566+
using (var committable = new CommittableTransaction())
567+
{
568+
System.Transactions.Transaction.Current = committable;
569+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
570+
{
571+
System.Transactions.Transaction.Current = clone;
572+
if (!AutoJoinTransaction)
573+
s.JoinTransaction();
574+
// Acquire the connection
575+
var count = s.Query<Person>().Count();
576+
Assert.That(count, Is.EqualTo(0), "Unexpected initial entity count.");
577+
clone.Complete();
578+
}
579+
580+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
581+
{
582+
System.Transactions.Transaction.Current = clone;
583+
if (!AutoJoinTransaction)
584+
s.JoinTransaction();
585+
s.Save(new Person());
586+
587+
if (explicitFlush)
588+
s.Flush();
589+
590+
clone.Complete();
591+
}
592+
593+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
594+
{
595+
System.Transactions.Transaction.Current = clone;
596+
if (!AutoJoinTransaction)
597+
s.JoinTransaction();
598+
var count = s.Query<Person>().Count();
599+
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after committed insert.");
600+
clone.Complete();
601+
}
602+
603+
System.Transactions.Transaction.Current = committable;
604+
committable.Commit();
605+
}
606+
}
607+
}
608+
finally
609+
{
610+
System.Transactions.Transaction.Current = null;
611+
}
612+
613+
using (var s = OpenSession())
614+
{
615+
using (var tx = new TransactionScope())
616+
{
617+
if (!AutoJoinTransaction)
618+
s.JoinTransaction();
619+
var count = s.Query<Person>().Count();
620+
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after global commit.");
621+
tx.Complete();
622+
}
623+
}
624+
}
512625
}
513626

514627
[TestFixture]

src/NHibernate.Test/TestDialect.cs

+8
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,13 @@ public bool SupportsSqlType(SqlType sqlType)
8080
/// Supports the modulo operator on decimal types
8181
/// </summary>
8282
public virtual bool SupportsModuloOnDecimal => true;
83+
84+
/// <summary>
85+
/// Some databases fail with dependent transaction, typically when their driver tries to access the transaction
86+
/// state from its two PC: the dependent transaction is meant to be disposed of before completing the actual
87+
/// transaction, so it is usually disposed at this point, and its state cannot be read. (Drivers should always
88+
/// clone transactions for avoiding this trouble.)
89+
/// </summary>
90+
public virtual bool SupportsDependentTransaction => true;
8391
}
8492
}

src/NHibernate.Test/TestDialects/PostgreSQL83TestDialect.cs

+6
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,11 @@ public override bool SupportsNullCharactersInUtfStrings
1616
{
1717
get { return false; }
1818
}
19+
20+
/// <summary>
21+
/// Npgsql does not clone the transaction in its context, and uses it in its prepare phase. When that was a
22+
/// dependent transaction, it is then usually already disposed of, causing Npgsql to crash.
23+
/// </summary>
24+
public override bool SupportsDependentTransaction => false;
1925
}
2026
}

src/NHibernate/AdoNet/ConnectionManager.cs

+6
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,13 @@ public DbCommand CreateCommand()
456456
public void EnlistIfRequired(System.Transactions.Transaction transaction)
457457
{
458458
if (transaction == _currentSystemTransaction)
459+
{
460+
// Short-circuit after having stored the transaction : they may be equal, but not the same reference.
461+
// And the previous one may be an already disposed dependent clone, in which case we need to update
462+
// our reference.
463+
_currentSystemTransaction = transaction;
459464
return;
465+
}
460466

461467
_currentSystemTransaction = transaction;
462468

src/NHibernate/Async/Transaction/AdoNetWithSystemTransactionFactory.cs

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
using NHibernate.AdoNet;
1717
using NHibernate.Engine;
1818
using NHibernate.Engine.Transaction;
19-
using NHibernate.Impl;
2019
using NHibernate.Util;
2120

2221
namespace NHibernate.Transaction

0 commit comments

Comments
 (0)