Skip to content

Commit ec4c68a

Browse files
NH-2176 - Fixing current system transaction handling.
1 parent 9a0747f commit ec4c68a

23 files changed

+913
-474
lines changed

src/NHibernate.Test/NHSpecificTest/DtcFailures/DtcFailuresFixture.cs

Lines changed: 362 additions & 146 deletions
Large diffs are not rendered by default.

src/NHibernate.Test/NHSpecificTest/NH1632/Fixture.cs

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ protected override void Configure(Configuration configuration)
2020
{
2121
configuration
2222
.SetProperty(Environment.UseSecondLevelCache, "true")
23-
.SetProperty(Environment.CacheProvider, typeof (HashtableCacheProvider).AssemblyQualifiedName);
23+
.SetProperty(Environment.CacheProvider, typeof(HashtableCacheProvider).AssemblyQualifiedName);
2424
}
2525

2626
[Test]
@@ -40,9 +40,9 @@ public void When_using_DTC_HiLo_knows_to_create_isolated_DTC_transaction()
4040
var generator = sessions.GetIdentifierGenerator(typeof(Person).FullName);
4141
Assert.That(generator, Is.InstanceOf<TableHiLoGenerator>());
4242

43-
using(var session = sessions.OpenSession())
43+
using (var session = sessions.OpenSession())
4444
{
45-
var id = generator.Generate((ISessionImplementor) session, new Person());
45+
var id = generator.Generate((ISessionImplementor)session, new Person());
4646
}
4747

4848
// intentionally dispose without committing
@@ -56,7 +56,7 @@ public void When_using_DTC_HiLo_knows_to_create_isolated_DTC_transaction()
5656
scalar2 = command.ExecuteScalar();
5757
}
5858

59-
Assert.AreNotEqual(scalar1, scalar2,"HiLo must run with in its own transaction");
59+
Assert.AreNotEqual(scalar1, scalar2, "HiLo must run with in its own transaction");
6060
}
6161

6262
[Test]
@@ -84,43 +84,53 @@ public void When_commiting_items_in_DTC_transaction_will_add_items_to_2nd_level_
8484
{
8585
using (var s = sessions.OpenSession())
8686
{
87-
s.Save(new Nums {ID = 29, NumA = 1, NumB = 3});
87+
s.Save(new Nums { ID = 29, NumA = 1, NumB = 3 });
8888
}
8989
tx.Complete();
9090
}
91-
92-
using (var tx = new TransactionScope())
91+
try
9392
{
94-
using (var s = sessions.OpenSession())
93+
94+
using (var tx = new TransactionScope())
9595
{
96-
var nums = s.Load<Nums>(29);
97-
Assert.AreEqual(1, nums.NumA);
98-
Assert.AreEqual(3, nums.NumB);
96+
using (var s = sessions.OpenSession())
97+
{
98+
var nums = s.Load<Nums>(29);
99+
Assert.AreEqual(1, nums.NumA);
100+
Assert.AreEqual(3, nums.NumB);
101+
}
102+
tx.Complete();
99103
}
100-
tx.Complete();
101-
}
102104

103-
//closing the connection to ensure we can't really use it.
104-
var connection = sessions.ConnectionProvider.GetConnection();
105-
sessions.ConnectionProvider.CloseConnection(connection);
105+
//closing the connection to ensure we can't really use it.
106+
var connection = sessions.ConnectionProvider.GetConnection();
107+
sessions.ConnectionProvider.CloseConnection(connection);
108+
// The session is supposed to succeed because the second level cache should have the
109+
// entity to load, allowing the session to not use the connection at all.
110+
// Will fail if transaction manager tries to enlist user supplied connection, due
111+
// to the transaction scope below.
106112

107-
using (var tx = new TransactionScope())
108-
{
109-
using (var s = sessions.WithOptions().Connection(connection).OpenSession())
113+
using (var tx = new TransactionScope())
110114
{
111-
var nums = s.Load<Nums>(29);
112-
Assert.AreEqual(1, nums.NumA);
113-
Assert.AreEqual(3, nums.NumB);
115+
using (var s = sessions.WithOptions().Connection(connection).OpenSession())
116+
{
117+
Nums nums = null;
118+
Assert.DoesNotThrow(() => nums = s.Load<Nums>(29), "Failed loading entity from second level cache.");
119+
Assert.AreEqual(1, nums.NumA);
120+
Assert.AreEqual(3, nums.NumB);
121+
}
122+
tx.Complete();
114123
}
115-
tx.Complete();
116124
}
117-
118-
using (var s = sessions.OpenSession())
119-
using (var tx = s.BeginTransaction())
125+
finally
120126
{
121-
var nums = s.Load<Nums>(29);
122-
s.Delete(nums);
123-
tx.Commit();
127+
using (var s = sessions.OpenSession())
128+
using (var tx = s.BeginTransaction())
129+
{
130+
var nums = s.Load<Nums>(29);
131+
s.Delete(nums);
132+
tx.Commit();
133+
}
124134
}
125135
}
126136

@@ -174,8 +184,8 @@ public void Will_not_save_when_flush_mode_is_never()
174184
[Test]
175185
public void When_using_two_sessions_with_explicit_flush()
176186
{
177-
if (!TestDialect.SupportsConcurrentTransactions)
178-
Assert.Ignore(Dialect.GetType().Name + " does not support concurrent transactions.");
187+
if (!Dialect.SupportsConcurrentWritingConnectionsInSameTransaction)
188+
Assert.Ignore(Dialect.GetType().Name + " does not support concurrent connections in same transaction.");
179189

180190
object id1, id2;
181191
using (var tx = new TransactionScope())
@@ -212,8 +222,8 @@ public void When_using_two_sessions_with_explicit_flush()
212222
[Test]
213223
public void When_using_two_sessions()
214224
{
215-
if (!TestDialect.SupportsConcurrentTransactions)
216-
Assert.Ignore(Dialect.GetType().Name + " does not support concurrent transactions.");
225+
if (!Dialect.SupportsConcurrentWritingConnectionsInSameTransaction)
226+
Assert.Ignore(Dialect.GetType().Name + " does not support concurrent connections in same transaction.");
217227

218228
object id1, id2;
219229
using (var tx = new TransactionScope())

src/NHibernate.Test/NHSpecificTest/NH2176/Fixture.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ protected override void OnSetUp()
1313
using (var s = OpenSession())
1414
using (var tx = s.BeginTransaction())
1515
{
16-
var steve = new Person {Name = "Steve"};
17-
var peter = new Person {Name = "Peter"};
18-
var simon = new Person {Name = "Simon"};
19-
var paul = new Person {Name = "Paul"};
20-
var john = new Person {Name = "John"};
21-
var eric = new Person {Name = "Eric"};
16+
var steve = new Person { Name = "Steve" };
17+
var peter = new Person { Name = "Peter" };
18+
var simon = new Person { Name = "Simon" };
19+
var paul = new Person { Name = "Paul" };
20+
var john = new Person { Name = "John" };
21+
var eric = new Person { Name = "Eric" };
2222

2323
s.Save(steve);
2424
s.Save(peter);
@@ -65,8 +65,8 @@ public void MultipleConsecutiveTransactionScopesCanBeUsedInsideASingleSession()
6565
scope.Complete();
6666
}
6767

68-
// The exeption is caused by a race condition between two threads.
69-
// This can be demonstracted by uncommenting the following line which
68+
// The exception is caused by a race condition between two threads.
69+
// This can be demonstrated by uncommenting the following line which
7070
// causes the test to run without an exception.
7171
//System.Threading.Thread.Sleep(1000);
7272
}

src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
using System;
2-
using System.Data.Common;
1+
using System.Data.Common;
32
using System.Data.Odbc;
43
using System.Data.SqlClient;
54
using System.Configuration;
65
using System.Transactions;
76
using NHibernate.Dialect;
87
using NHibernate.Driver;
9-
using NHibernate.Engine;
108
using NUnit.Framework;
119

1210
using Environment = NHibernate.Cfg.Environment;
@@ -55,44 +53,59 @@ public void ShouldBeAbleToReleaseSuppliedConnectionAfterDistributedTransaction()
5553
{
5654
string connectionString = FetchConnectionStringFromConfiguration();
5755
ISession s;
58-
using (var ts = new TransactionScope())
56+
DbConnection connection = null;
57+
try
5958
{
60-
// Enlisting DummyEnlistment as a durable resource manager will start
61-
// a DTC transaction
62-
System.Transactions.Transaction.Current.EnlistDurable(
63-
DummyEnlistment.Id,
64-
new DummyEnlistment(),
65-
EnlistmentOptions.None);
59+
using (var ts = new TransactionScope())
60+
{
61+
// Enlisting DummyEnlistment as a durable resource manager will start
62+
// a DTC transaction
63+
System.Transactions.Transaction.Current.EnlistDurable(
64+
DummyEnlistment.Id,
65+
new DummyEnlistment(),
66+
EnlistmentOptions.None);
6667

67-
DbConnection connection;
68-
if (sessions.ConnectionProvider.Driver.GetType() == typeof(OdbcDriver))
69-
connection = new OdbcConnection(connectionString);
70-
else
71-
connection = new SqlConnection(connectionString);
68+
if (sessions.ConnectionProvider.Driver.GetType() == typeof(OdbcDriver))
69+
connection = new OdbcConnection(connectionString);
70+
else
71+
connection = new SqlConnection(connectionString);
7272

73-
using (connection)
74-
{
7573
connection.Open();
7674
using (s = Sfi.WithOptions().Connection(connection).OpenSession())
7775
{
78-
s.Save(new MyTable { String = "hello!" });
76+
s.Save(new MyTable {String = "hello!"});
7977
}
80-
connection.Close();
81-
}
78+
// The ts disposal may try to flush the session, which, depending on the native generator
79+
// implementation for current dialect, may have something to do and will then try to use
80+
// the supplied connection. dispose connection here => flaky test, failing for dialects
81+
// not mandating an immediate insert on native generator save.
82+
// Delaying the connection disposal to after ts disposal.
8283

83-
ts.Complete();
84+
ts.Complete();
85+
}
86+
}
87+
finally
88+
{
89+
connection?.Dispose();
8490
}
8591

92+
// It appears neither the second phase of the 2PC nor TransactionCompleted
93+
// event are guaranteed to be executed before exiting transaction scope disposal.
94+
// When having only 2PC, the second phase tends to occur after reaching that point
95+
// here. When having TransactionCompleted event, this event and the second phase
96+
// tend to occur before reaching here. But some other NH cases demonstrate that
97+
// TransactionCompleted may also occur "too late".
98+
s.GetSessionImplementation().TransactionContext?.WaitOne();
99+
86100
// Prior to the patch, an InvalidOperationException exception would occur in the
87101
// TransactionCompleted delegate at this point with the message, "Disconnect cannot
88102
// be called while a transaction is in progress". Although the exception can be
89103
// seen reported in the IDE, NUnit fails to see it. The TransactionCompleted event
90104
// fires *after* the transaction is committed and so it doesn't affect the success
91105
// of the transaction.
92-
93106
Assert.That(s.IsConnected, Is.False);
94-
Assert.That(((ISessionImplementor)s).ConnectionManager.IsConnected, Is.False);
95-
Assert.That(((ISessionImplementor)s).IsClosed, Is.True);
107+
Assert.That(s.GetSessionImplementation().ConnectionManager.IsConnected, Is.False);
108+
Assert.That(s.GetSessionImplementation().IsClosed, Is.True);
96109
}
97110

98111
protected override void OnTearDown()

src/NHibernate.Test/SystemTransactions/TransactionFixture.cs

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,38 @@ namespace NHibernate.Test.SystemTransactions
99
public class TransactionFixture : TestCase
1010
{
1111
protected override IList Mappings
12+
=> new [] { "WZ.hbm.xml" };
13+
14+
protected override void OnTearDown()
1215
{
13-
get { return new string[] { "WZ.hbm.xml" }; }
16+
using (var s = sessions.OpenSession())
17+
using (var t = s.BeginTransaction())
18+
{
19+
s.CreateQuery("delete from System.Object").ExecuteUpdate();
20+
t.Commit();
21+
}
1422
}
1523

1624
[Test]
1725
public void CanUseSystemTransactionsToCommit()
1826
{
1927
object identifier;
20-
using(ISession session = sessions.OpenSession())
21-
using(TransactionScope tx = new TransactionScope())
28+
using(var session = sessions.OpenSession())
29+
using(var tx = new TransactionScope())
2230
{
23-
W s = new W();
31+
var s = new W();
2432
session.Save(s);
2533
identifier = s.Id;
2634
tx.Complete();
2735
}
2836

29-
using (ISession session = sessions.OpenSession())
30-
using (TransactionScope tx = new TransactionScope())
37+
using (var session = sessions.OpenSession())
38+
using (var tx = new TransactionScope())
3139
{
32-
W w = session.Get<W>(identifier);
40+
var w = session.Get<W>(identifier);
3341
Assert.IsNotNull(w);
42+
// Without explicit Flush, this delay the delete to prepare phase, and test flushing from there
43+
// while having already acquired a connection due to the Get.
3444
session.Delete(w);
3545
tx.Complete();
3646
}

src/NHibernate.Test/TestCase.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ private bool CheckSessionsWereClosed()
221221
var allClosed = true;
222222
foreach (var session in _openedSessions)
223223
{
224+
session.GetSessionImplementation().TransactionContext?.WaitOne();
224225
if (session.IsOpen)
225226
{
226227
log.Error($"Test case didn't close session {session.GetSessionImplementation().SessionId}, closing");

src/NHibernate.Test/TestDialect.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,6 @@ public TestDialect(Dialect.Dialect dialect)
3333
public virtual bool SupportsOperatorSome { get { return true; } }
3434
public virtual bool SupportsLocate { get { return true; } }
3535

36-
public virtual bool SupportsDistributedTransactions { get { return true; } }
37-
38-
/// <summary>
39-
/// Whether two transactions can be run at the same time. For example, with SQLite
40-
/// the database is locked when one transaction is run, so running a second transaction
41-
/// will cause a "database is locked" error message.
42-
/// </summary>
43-
public virtual bool SupportsConcurrentTransactions { get { return true; } }
44-
4536
public virtual bool SupportsFullJoin { get { return true; } }
4637

4738
public virtual bool HasBrokenDecimalType { get { return false; } }

src/NHibernate.Test/TestDialects/PostgreSQL83TestDialect.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,5 @@ public override bool SupportsNullCharactersInUtfStrings
2121
{
2222
get { return false; }
2323
}
24-
25-
/// <summary>
26-
/// Npgsql's DTC code seems to be somewhat broken as of 2.0.11.
27-
/// </summary>
28-
public override bool SupportsDistributedTransactions
29-
{
30-
get { return false; }
31-
}
3224
}
3325
}

src/NHibernate.Test/TestDialects/SQLiteTestDialect.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,6 @@ public override bool SupportsLocate
2727
get { return false; }
2828
}
2929

30-
public override bool SupportsDistributedTransactions
31-
{
32-
get { return false; }
33-
}
34-
35-
public override bool SupportsConcurrentTransactions
36-
{
37-
get { return false; }
38-
}
39-
4030
public override bool SupportsFullJoin
4131
{
4232
get { return false; }

0 commit comments

Comments
 (0)