Skip to content

Commit 5d167af

Browse files
NH-2176 - best attempt I can imagine for fixing current transaction handling.
1 parent 88491fc commit 5d167af

File tree

5 files changed

+152
-109
lines changed

5 files changed

+152
-109
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public void TransactionInsertWithRollBackTask()
169169
}
170170
}
171171

172-
[Test, Ignore("Not fixed.")]
172+
[Test/*, Ignore("Not fixed.")*/]
173173
[Description(@"Two session in two txscope
174174
(without an explicit NH transaction and without an explicit flush)
175175
and with a rollback in the second dtc and a ForceRollback outside nh-session-scope.")]

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

Lines changed: 39 additions & 29 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.OpenSession(connection))
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.OpenSession(connection))
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

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

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Data.Odbc;
44
using System.Data.SqlClient;
55
using System.Configuration;
6+
using System.Threading;
67
using System.Transactions;
78
using NHibernate.Dialect;
89
using NHibernate.Driver;
@@ -55,41 +56,54 @@ public void ShouldBeAbleToReleaseSuppliedConnectionAfterDistributedTransaction()
5556
{
5657
string connectionString = FetchConnectionStringFromConfiguration();
5758
ISession s;
58-
using (var ts = new TransactionScope())
59+
DbConnection connection = null;
60+
try
5961
{
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);
62+
using (var ts = new TransactionScope())
63+
{
64+
// Enlisting DummyEnlistment as a durable resource manager will start
65+
// a DTC transaction
66+
System.Transactions.Transaction.Current.EnlistDurable(
67+
DummyEnlistment.Id,
68+
new DummyEnlistment(),
69+
EnlistmentOptions.None);
6670

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

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

83-
ts.Complete();
87+
ts.Complete();
88+
}
89+
}
90+
finally
91+
{
92+
connection?.Dispose();
8493
}
8594

95+
// Here it appears the second phase of the 2PC is not guaranteed to be executed
96+
// before exiting transaction scope disposal... So long for the current pattern,
97+
// it looks doomed. Sleeping here allow to keep that test succeeding by letting
98+
// enough time for the second phase to disconnect the session.
99+
//Thread.Sleep(100);
100+
86101
// Prior to the patch, an InvalidOperationException exception would occur in the
87102
// TransactionCompleted delegate at this point with the message, "Disconnect cannot
88103
// be called while a transaction is in progress". Although the exception can be
89104
// seen reported in the IDE, NUnit fails to see it. The TransactionCompleted event
90105
// fires *after* the transaction is committed and so it doesn't affect the success
91106
// of the transaction.
92-
93107
Assert.That(s.IsConnected, Is.False);
94108
Assert.That(((ISessionImplementor)s).ConnectionManager.IsConnected, Is.False);
95109
Assert.That(((ISessionImplementor)s).IsClosed, Is.True);

src/NHibernate/AdoNet/ConnectionManager.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Data.Common;
44
using System.Runtime.Serialization;
55
using System.Security;
6-
using System.Security.Permissions;
76

87
using NHibernate.Engine;
98

@@ -30,6 +29,12 @@ public interface Callback
3029

3130
[NonSerialized]
3231
private DbConnection connection;
32+
[NonSerialized]
33+
private DbConnection _dtcBackupConnection;
34+
[NonSerialized]
35+
private System.Transactions.Transaction _connectionAmbientTransaction;
36+
[NonSerialized]
37+
private System.Transactions.Transaction _dtcBackupConnectionAmbientTransaction;
3338
// Whether we own the connection, i.e. connect and disconnect automatically.
3439
private bool ownConnection;
3540

@@ -185,6 +190,7 @@ public DbConnection GetConnection()
185190
if (ownConnection)
186191
{
187192
connection = Factory.ConnectionProvider.GetConnection();
193+
_connectionAmbientTransaction = System.Transactions.Transaction.Current;
188194
if (Factory.Statistics.IsStatisticsEnabled)
189195
{
190196
Factory.StatisticsImplementor.Connect();
@@ -380,10 +386,29 @@ public IBatcher Batcher
380386
get { return batcher; }
381387
}
382388

389+
/// <summary>
390+
/// Tell if according to current ambient transaction, the connection should be explicitly enlisted
391+
/// for ensuring it to participate in transaction. Always <see langword="false" /> for supplied
392+
/// connection, enlistment is user business in such case.
393+
/// </summary>
394+
public bool RequireExplicitEnlistment
395+
=> ownConnection && connection != null &&
396+
_connectionAmbientTransaction != System.Transactions.Transaction.Current;
397+
383398
public IDisposable FlushingFromDtcTransaction
384399
{
385400
get
386401
{
402+
if (ownConnection)
403+
{
404+
if (Batcher.HasOpenResources)
405+
throw new InvalidOperationException("Batcher still has opened ressources at time of Flush from DTC.");
406+
// Swap out current connection for avoiding using it concurrently to its own 2PC
407+
_dtcBackupConnection = connection;
408+
_dtcBackupConnectionAmbientTransaction = _connectionAmbientTransaction;
409+
connection = null;
410+
_connectionAmbientTransaction = null;
411+
}
387412
flushingFromDtcTransaction = true;
388413
return new StopFlushingFromDtcTransaction(this);
389414
}
@@ -401,6 +426,17 @@ public StopFlushingFromDtcTransaction(ConnectionManager manager)
401426
public void Dispose()
402427
{
403428
manager.flushingFromDtcTransaction = false;
429+
430+
if (manager.ownConnection)
431+
{
432+
// Release the connection potentially acquired for flushing from DTC.
433+
manager.DisconnectOwnConnection();
434+
// Swap back current connection
435+
manager.connection = manager._dtcBackupConnection;
436+
manager._connectionAmbientTransaction = manager._dtcBackupConnectionAmbientTransaction;
437+
manager._dtcBackupConnection = null;
438+
manager._dtcBackupConnectionAmbientTransaction = null;
439+
}
404440
}
405441
}
406442

0 commit comments

Comments
 (0)