Skip to content

Commit 0c4524e

Browse files
Merge pull request #2207 from fredericDelaporte/5.0.x-2172
Fix dependent transaction failure in 5.0.x
2 parents 6e6e402 + e0d6eac commit 0c4524e

13 files changed

+317
-20
lines changed

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)' == ''">0</VersionMinor>
5-
<VersionPatch Condition="'$(VersionPatch)' == ''">7</VersionPatch>
5+
<VersionPatch Condition="'$(VersionPatch)' == ''">8</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
@@ -30,8 +30,8 @@
3030

3131
<!-- This is used only for build folder -->
3232
<!-- TODO: Either remove or refactor to use NHibernate.props -->
33-
<property name="project.version" value="5.0.7" overwrite="false" />
34-
<property name="project.version.numeric" value="5.0.7" overwrite="false" />
33+
<property name="project.version" value="5.0.8" overwrite="false" />
34+
<property name="project.version.numeric" value="5.0.8" overwrite="false" />
3535

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

releasenotes.txt

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

412
Release notes - NHibernate - Version 5.0.7

src/AsyncGenerator.yml

+12
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,18 @@
164164
applyChanges: true
165165
analyzation:
166166
methodConversion:
167+
- conversion: Ignore
168+
name: CanUseDependentTransaction
169+
containingTypeName: DistributedSystemTransactionFixture
170+
- conversion: Ignore
171+
name: CanUseSessionWithManyDependentTransaction
172+
containingTypeName: DistributedSystemTransactionFixture
173+
- conversion: Ignore
174+
name: CanUseDependentTransaction
175+
containingTypeName: SystemTransactionFixture
176+
- conversion: Ignore
177+
name: CanUseSessionWithManyDependentTransaction
178+
containingTypeName: SystemTransactionFixture
167179
- conversion: Copy
168180
hasAttributeName: OneTimeSetUpAttribute
169181
- conversion: Copy

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
using log4net.Repository.Hierarchy;
1717
using NHibernate.Cfg;
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
{
@@ -780,4 +780,4 @@ protected override void Configure(Configuration configuration)
780780
protected override bool AppliesTo(ISessionFactoryImplementor factory)
781781
=> base.AppliesTo(factory) && factory.ConnectionProvider.Driver.SupportsEnlistmentWhenAutoEnlistmentIsDisabled;
782782
}
783-
}
783+
}

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
using System.Threading;
1515
using System.Transactions;
1616
using NHibernate.Cfg;
17+
using NHibernate.Driver;
1718
using NHibernate.Engine;
18-
using NHibernate.Linq;
1919
using NHibernate.Test.TransactionTest;
2020
using NUnit.Framework;
21+
using NHibernate.Linq;
2122

2223
namespace NHibernate.Test.SystemTransactions
2324
{
@@ -529,4 +530,4 @@ public class SystemTransactionWithoutAutoJoinTransactionAsync : SystemTransactio
529530
{
530531
protected override bool AutoJoinTransaction => false;
531532
}
532-
}
533+
}

src/NHibernate.Test/SystemTransactions/DistributedSystemTransactionFixture.cs

+112-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using log4net.Repository.Hierarchy;
77
using NHibernate.Cfg;
88
using NHibernate.Engine;
9-
using NHibernate.Linq;
109
using NHibernate.Test.TransactionTest;
1110
using NUnit.Framework;
1211

@@ -728,6 +727,117 @@ public void AdditionalJoinDoesNotThrow()
728727
}
729728
}
730729

730+
[Theory]
731+
public void CanUseDependentTransaction(bool explicitFlush)
732+
{
733+
if (!TestDialect.SupportsDependentTransaction)
734+
Assert.Ignore("Dialect does not support dependent transactions");
735+
IgnoreIfUnsupported(explicitFlush);
736+
737+
try
738+
{
739+
using (var committable = new CommittableTransaction())
740+
{
741+
System.Transactions.Transaction.Current = committable;
742+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
743+
{
744+
System.Transactions.Transaction.Current = clone;
745+
746+
using (var s = OpenSession())
747+
{
748+
if (!AutoJoinTransaction)
749+
s.JoinTransaction();
750+
s.Save(new Person());
751+
752+
if (explicitFlush)
753+
s.Flush();
754+
clone.Complete();
755+
}
756+
}
757+
758+
System.Transactions.Transaction.Current = committable;
759+
committable.Commit();
760+
}
761+
}
762+
finally
763+
{
764+
System.Transactions.Transaction.Current = null;
765+
}
766+
}
767+
768+
[Theory]
769+
public void CanUseSessionWithManyDependentTransaction(bool explicitFlush)
770+
{
771+
if (!TestDialect.SupportsDependentTransaction)
772+
Assert.Ignore("Dialect does not support dependent transactions");
773+
IgnoreIfUnsupported(explicitFlush);
774+
775+
try
776+
{
777+
using (var s = Sfi.WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession())
778+
{
779+
using (var committable = new CommittableTransaction())
780+
{
781+
System.Transactions.Transaction.Current = committable;
782+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
783+
{
784+
System.Transactions.Transaction.Current = clone;
785+
if (!AutoJoinTransaction)
786+
s.JoinTransaction();
787+
// Acquire the connection
788+
var count = s.Query<Person>().Count();
789+
Assert.That(count, Is.EqualTo(0), "Unexpected initial entity count.");
790+
clone.Complete();
791+
}
792+
793+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
794+
{
795+
System.Transactions.Transaction.Current = clone;
796+
if (!AutoJoinTransaction)
797+
s.JoinTransaction();
798+
s.Save(new Person());
799+
800+
if (explicitFlush)
801+
s.Flush();
802+
803+
clone.Complete();
804+
}
805+
806+
using (var clone = committable.DependentClone(DependentCloneOption.RollbackIfNotComplete))
807+
{
808+
System.Transactions.Transaction.Current = clone;
809+
if (!AutoJoinTransaction)
810+
s.JoinTransaction();
811+
var count = s.Query<Person>().Count();
812+
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after committed insert.");
813+
clone.Complete();
814+
}
815+
816+
System.Transactions.Transaction.Current = committable;
817+
committable.Commit();
818+
}
819+
}
820+
}
821+
finally
822+
{
823+
System.Transactions.Transaction.Current = null;
824+
}
825+
826+
DodgeTransactionCompletionDelayIfRequired();
827+
828+
using (var s = OpenSession())
829+
{
830+
using (var tx = new TransactionScope())
831+
{
832+
if (!AutoJoinTransaction)
833+
s.JoinTransaction();
834+
var count = s.Query<Person>().Count();
835+
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after global commit.");
836+
tx.Complete();
837+
}
838+
}
839+
}
840+
731841
private void DodgeTransactionCompletionDelayIfRequired()
732842
{
733843
if (Sfi.ConnectionProvider.Driver.HasDelayedDistributedTransactionCompletion)
@@ -820,4 +930,4 @@ public void SessionIsNotEnlisted()
820930
}
821931
}
822932
}
823-
}
933+
}

src/NHibernate.Test/SystemTransactions/SystemTransactionFixture.cs

+116-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
using System.Threading;
55
using System.Transactions;
66
using NHibernate.Cfg;
7+
using NHibernate.Driver;
78
using NHibernate.Engine;
8-
using NHibernate.Linq;
99
using NHibernate.Test.TransactionTest;
1010
using NUnit.Framework;
1111

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

507621
[TestFixture]
@@ -539,4 +653,4 @@ public void SessionIsNotEnlisted()
539653
}
540654
}
541655
}
542-
}
656+
}

src/NHibernate.Test/TestDialect.cs

+8
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,13 @@ public bool SupportsSqlType(SqlType sqlType)
7272
return false;
7373
}
7474
}
75+
76+
/// <summary>
77+
/// Some databases fail with dependent transaction, typically when their driver tries to access the transaction
78+
/// state from its two PC: the dependent transaction is meant to be disposed of before completing the actual
79+
/// transaction, so it is usually disposed at this point, and its state cannot be read. (Drivers should always
80+
/// clone transactions for avoiding this trouble.)
81+
/// </summary>
82+
public virtual bool SupportsDependentTransaction => true;
7583
}
7684
}

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
}

0 commit comments

Comments
 (0)