Skip to content

Commit 909cde7

Browse files
NH-4011 - Moving DtcFailures to system transaction tests, and completing non distributed tests
1 parent f9c2a3a commit 909cde7

20 files changed

+1316
-437
lines changed

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//------------------------------------------------------------------------------
99

1010

11+
using System.Data;
1112
using NUnit.Framework;
1213

1314
namespace NHibernate.Test.NHSpecificTest.NH1632
@@ -38,6 +39,9 @@ protected override void Configure(Configuration configuration)
3839
[Test]
3940
public async Task When_using_DTC_HiLo_knows_to_create_isolated_DTC_transactionAsync()
4041
{
42+
if (!Dialect.SupportsConcurrentWritingConnections)
43+
Assert.Ignore(Dialect.GetType().Name + " does not support concurrent writing connections, can not isolate work.");
44+
4145
object scalar1, scalar2;
4246

4347
using (var session = Sfi.OpenSession())
@@ -47,18 +51,19 @@ public async Task When_using_DTC_HiLo_knows_to_create_isolated_DTC_transactionAs
4751
scalar1 = await (command.ExecuteScalarAsync());
4852
}
4953

50-
using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
54+
using (new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
5155
{
5256
var generator = Sfi.GetIdentifierGenerator(typeof(Person).FullName);
5357
Assert.That(generator, Is.InstanceOf<TableHiLoGenerator>());
5458

5559
using (var session = OpenSession())
5660
{
57-
var id = await (generator.GenerateAsync((ISessionImplementor)session, new Person(), CancellationToken.None));
61+
// Force connection acquisition for having it enlisted.
62+
Assert.That(session.Connection.State, Is.EqualTo(ConnectionState.Open));
63+
await (generator.GenerateAsync((ISessionImplementor)session, new Person(), CancellationToken.None));
5864
}
5965

6066
// intentionally dispose without committing
61-
tx.Dispose();
6267
}
6368

6469
using (var session = Sfi.OpenSession())

src/NHibernate.Test/Async/NHSpecificTest/DtcFailures/DtcFailuresFixture.cs renamed to src/NHibernate.Test/Async/SystemTransactions/DistributedSystemTransactionFixture.cs

Lines changed: 111 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -9,72 +9,30 @@
99

1010

1111
using System;
12-
using System.Collections;
1312
using System.Linq;
14-
using System.Reflection;
1513
using System.Threading;
1614
using System.Transactions;
1715
using log4net;
1816
using log4net.Repository.Hierarchy;
19-
using NHibernate.Cfg;
20-
using NHibernate.Cfg.MappingSchema;
2117
using NHibernate.Linq;
22-
using NHibernate.Tool.hbm2ddl;
18+
using NHibernate.Test.TransactionTest;
2319
using NUnit.Framework;
2420

25-
namespace NHibernate.Test.NHSpecificTest.DtcFailures
21+
namespace NHibernate.Test.SystemTransactions
2622
{
2723
using System.Threading.Tasks;
2824
[TestFixture]
29-
public class DtcFailuresFixtureAsync : TestCase
25+
public class DistributedSystemTransactionFixtureAsync : SystemTransactionFixtureBase
3026
{
31-
private static readonly ILog _log = LogManager.GetLogger(typeof(DtcFailuresFixtureAsync));
32-
33-
protected override IList Mappings
34-
=> new[] { "NHSpecificTest.DtcFailures.Mappings.hbm.xml" };
35-
36-
protected override string MappingsAssembly
37-
=> "NHibernate.Test";
27+
private static readonly ILog _log = LogManager.GetLogger(typeof(DistributedSystemTransactionFixtureAsync));
3828

3929
protected override bool AppliesTo(Dialect.Dialect dialect)
40-
=> dialect.SupportsDistributedTransactions;
41-
42-
protected override void CreateSchema()
43-
{
44-
// Copied from Configure method.
45-
var config = new Configuration();
46-
if (TestConfigurationHelper.hibernateConfigFile != null)
47-
config.Configure(TestConfigurationHelper.hibernateConfigFile);
48-
49-
// Our override so we can set nullability on database column without NHibernate knowing about it.
50-
config.BeforeBindMapping += BeforeBindMapping;
51-
52-
// Copied from AddMappings methods.
53-
var assembly = Assembly.Load(MappingsAssembly);
54-
foreach (var file in Mappings)
55-
config.AddResource(MappingsAssembly + "." + file, assembly);
56-
57-
// Copied from CreateSchema method, but we use our own config.
58-
new SchemaExport(config).Create(false, true);
59-
}
30+
=> dialect.SupportsDistributedTransactions && base.AppliesTo(dialect);
6031

6132
protected override void OnTearDown()
6233
{
6334
DodgeTransactionCompletionDelayIfRequired();
64-
65-
using (var s = OpenSession())
66-
using (var t = s.BeginTransaction())
67-
{
68-
s.CreateQuery("delete from System.Object").ExecuteUpdate();
69-
t.Commit();
70-
}
71-
}
72-
73-
private void BeforeBindMapping(object sender, BindMappingEventArgs e)
74-
{
75-
var prop = e.Mapping.RootClasses[0].Properties.OfType<HbmProperty>().Single(p => p.Name == "NotNullData");
76-
prop.notnull = true;
77-
prop.notnullSpecified = true;
35+
base.OnTearDown();
7836
}
7937

8038
[Test]
@@ -91,7 +49,7 @@ public async Task SupportsEnlistingInDistributedAsync()
9149

9250
using (var s = OpenSession())
9351
{
94-
await (s.SaveAsync(new Person { CreatedAt = DateTime.Now }));
52+
await (s.SaveAsync(new Person()));
9553
// Ensure the connection is acquired (thus enlisted)
9654
Assert.DoesNotThrowAsync(() => s.FlushAsync(), "Failure enlisting a connection in a distributed transaction.");
9755
}
@@ -105,7 +63,7 @@ public async Task SupportsPromotingToDistributedAsync()
10563
{
10664
using (var s = OpenSession())
10765
{
108-
await (s.SaveAsync(new Person { CreatedAt = DateTime.Now }));
66+
await (s.SaveAsync(new Person()));
10967
// Ensure the connection is acquired (thus enlisted)
11068
await (s.FlushAsync());
11169
}
@@ -119,7 +77,7 @@ public async Task SupportsPromotingToDistributedAsync()
11977
}
12078

12179
[Test]
122-
public async Task WillNotCrashOnDtcPrepareFailureAsync()
80+
public async Task WillNotCrashOnPrepareFailureAsync()
12381
{
12482
var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
12583
var disposeCalled = false;
@@ -163,7 +121,7 @@ public async Task CanRollbackTransactionAsync([Values(false, true)] bool explici
163121
using (var s = OpenSession())
164122
{
165123
ForceEscalationToDistributedTx.Escalate(true); //will rollback tx
166-
await (s.SaveAsync(new Person { CreatedAt = DateTime.Today }));
124+
await (s.SaveAsync(new Person()));
167125

168126
if (explicitFlush)
169127
await (s.FlushAsync());
@@ -189,7 +147,7 @@ public async Task CanRollbackTransactionAsync([Values(false, true)] bool explici
189147
}
190148
}
191149

192-
await (AssertNoPersonsAsync());
150+
AssertNoPersons();
193151
}
194152

195153
[Test]
@@ -199,14 +157,14 @@ public async Task CanRollbackTransactionFromScopeAsync([Values(false, true)] boo
199157
using (var s = OpenSession())
200158
{
201159
ForceEscalationToDistributedTx.Escalate();
202-
await (s.SaveAsync(new Person { CreatedAt = DateTime.Today }));
160+
await (s.SaveAsync(new Person()));
203161

204162
if (explicitFlush)
205163
await (s.FlushAsync());
206164
// No Complete call for triggering rollback.
207165
}
208166

209-
await (AssertNoPersonsAsync());
167+
AssertNoPersons();
210168
}
211169

212170
[Test]
@@ -219,7 +177,7 @@ public async Task RollbackOutsideNhAsync([Values(false, true)] bool explicitFlus
219177
{
220178
using (var s = OpenSession())
221179
{
222-
var person = new Person { CreatedAt = DateTime.Now };
180+
var person = new Person();
223181
await (s.SaveAsync(person));
224182

225183
if (explicitFlush)
@@ -237,7 +195,7 @@ public async Task RollbackOutsideNhAsync([Values(false, true)] bool explicitFlus
237195
_log.Debug("Transaction aborted.");
238196
}
239197

240-
await (AssertNoPersonsAsync());
198+
AssertNoPersons();
241199
}
242200

243201
[Test]
@@ -248,17 +206,16 @@ public async Task TransactionInsertWithRollBackFromScopeAsync([Values(false, tru
248206
{
249207
using (var s = OpenSession())
250208
{
251-
var person = new Person { CreatedAt = DateTime.Now };
209+
var person = new Person();
252210
await (s.SaveAsync(person));
253211
ForceEscalationToDistributedTx.Escalate();
254-
person.CreatedAt = DateTime.Now;
255212

256213
if (explicitFlush)
257214
await (s.FlushAsync());
258215
}
259216
// No Complete call for triggering rollback.
260217
}
261-
await (AssertNoPersonsAsync());
218+
AssertNoPersons();
262219
}
263220

264221
[Test]
@@ -271,10 +228,9 @@ public async Task TransactionInsertWithRollBackTaskAsync([Values(false, true)] b
271228
{
272229
using (var s = OpenSession())
273230
{
274-
var person = new Person { CreatedAt = DateTime.Now };
231+
var person = new Person();
275232
await (s.SaveAsync(person));
276233
ForceEscalationToDistributedTx.Escalate(true); //will rollback tx
277-
person.CreatedAt = DateTime.Now;
278234

279235
if (explicitFlush)
280236
await (s.FlushAsync());
@@ -289,7 +245,7 @@ public async Task TransactionInsertWithRollBackTaskAsync([Values(false, true)] b
289245
_log.Debug("Transaction aborted.");
290246
}
291247

292-
await (AssertNoPersonsAsync());
248+
AssertNoPersons();
293249
}
294250

295251
[Test]
@@ -396,7 +352,7 @@ public async Task CanDeleteItemInDtcAsync([Values(false, true)] bool explicitFlu
396352
{
397353
using (var s = OpenSession())
398354
{
399-
id = await (s.SaveAsync(new Person { CreatedAt = DateTime.Today }));
355+
id = await (s.SaveAsync(new Person()));
400356

401357
ForceEscalationToDistributedTx.Escalate();
402358

@@ -432,7 +388,7 @@ public async Task CanDeleteItemInDtcAsync([Values(false, true)] bool explicitFlu
432388

433389
DodgeTransactionCompletionDelayIfRequired();
434390

435-
await (AssertNoPersonsAsync());
391+
AssertNoPersons();
436392
}
437393

438394
[Test]
@@ -455,14 +411,82 @@ public async Task NH1744Async()
455411
}
456412
}
457413

414+
[Test]
415+
public async Task CanUseSessionWithManyScopesAsync([Values(false, true)] bool explicitFlush)
416+
{
417+
// Note that this fails with ConnectionReleaseMode.OnClose and SqlServer:
418+
// System.Data.SqlClient.SqlException : Microsoft Distributed Transaction Coordinator (MS DTC) has stopped this transaction.
419+
using (var s = OpenSession())
420+
//using (var s = Sfi.WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession())
421+
{
422+
using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
423+
{
424+
ForceEscalationToDistributedTx.Escalate();
425+
// Acquire the connection
426+
var count = await (s.Query<Person>().CountAsync());
427+
Assert.That(count, Is.EqualTo(0), "Unexpected initial entity count.");
428+
tx.Complete();
429+
}
430+
// No dodge here please! Allow to check chaining usages do not fail.
431+
using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
432+
{
433+
await (s.SaveAsync(new Person()));
434+
435+
ForceEscalationToDistributedTx.Escalate();
436+
437+
if (explicitFlush)
438+
await (s.FlushAsync());
439+
440+
tx.Complete();
441+
}
442+
443+
DodgeTransactionCompletionDelayIfRequired();
444+
445+
using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
446+
{
447+
ForceEscalationToDistributedTx.Escalate();
448+
var count = await (s.Query<Person>().CountAsync());
449+
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after committed insert.");
450+
tx.Complete();
451+
}
452+
using (new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
453+
{
454+
await (s.SaveAsync(new Person()));
455+
456+
ForceEscalationToDistributedTx.Escalate();
457+
458+
if (explicitFlush)
459+
await (s.FlushAsync());
460+
461+
// No complete for rollback-ing.
462+
}
463+
464+
DodgeTransactionCompletionDelayIfRequired();
465+
466+
// Do not reuse the session after a rollback, its state does not allow it.
467+
// http://nhibernate.info/doc/nhibernate-reference/manipulatingdata.html#manipulatingdata-endingsession-commit
468+
}
469+
470+
using (var s = OpenSession())
471+
{
472+
using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
473+
{
474+
ForceEscalationToDistributedTx.Escalate();
475+
var count = await (s.Query<Person>().CountAsync());
476+
Assert.That(count, Is.EqualTo(1), "Unexpected entity count after rollback-ed insert.");
477+
tx.Complete();
478+
}
479+
}
480+
}
481+
458482
[Test]
459483
public async Task CanUseSessionOutsideOfScopeAfterScopeAsync([Values(false, true)] bool explicitFlush)
460484
{
461485
using (var s = Sfi.WithOptions().ConnectionReleaseMode(ConnectionReleaseMode.OnClose).OpenSession())
462486
{
463487
using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
464488
{
465-
await (s.SaveAsync(new Person { CreatedAt = DateTime.Today }));
489+
await (s.SaveAsync(new Person()));
466490

467491
ForceEscalationToDistributedTx.Escalate();
468492

@@ -498,7 +522,7 @@ public async Task DelayedTransactionCompletionAsync([Values(false, true)] bool e
498522
{
499523
using (var s = OpenSession())
500524
{
501-
await (s.SaveAsync(new Person { CreatedAt = DateTime.Today }));
525+
await (s.SaveAsync(new Person()));
502526

503527
ForceEscalationToDistributedTx.Escalate();
504528

@@ -511,6 +535,15 @@ public async Task DelayedTransactionCompletionAsync([Values(false, true)] bool e
511535
var count = await (controlSession.Query<Person>().CountAsync());
512536
if (count != i)
513537
{
538+
// See https://github.com/npgsql/npgsql/issues/1571#issuecomment-308651461 discussion with a Microsoft
539+
// employee: MSDTC consider a transaction to be committed once it has collected all participant votes
540+
// for committing from prepare phase. It then immediately notifies all participants of the outcome.
541+
// This causes TransactionScope.Dispose to leave while the second phase of participants may still
542+
// be executing. This means the transaction from the db view point can still be pending and not yet
543+
// committed. This is by design of MSDTC and we have to cope with that. Some data provider may have
544+
// a global locking mechanism causing any subsequent use to wait for the end of the commit phase,
545+
// but this is not the usual case. Some other, as Npgsql < v3.2.5, may crash due to this, because
546+
// they re-use the connection for the second phase.
514547
Thread.Sleep(100);
515548
var countSecondTry = await (controlSession.Query<Person>().CountAsync());
516549
Assert.Warn($"Unexpected entity count: {count} instead of {i}. " +
@@ -522,13 +555,20 @@ public async Task DelayedTransactionCompletionAsync([Values(false, true)] bool e
522555
}
523556
}
524557

525-
private async Task AssertNoPersonsAsync(CancellationToken cancellationToken = default(CancellationToken))
558+
[Test]
559+
public async Task DoNotDeadlockOnAfterTransactionWaitAsync()
526560
{
527-
using (var s = OpenSession())
528-
using (s.BeginTransaction())
561+
var interceptor = new AfterTransactionWaitingInterceptor();
562+
using (var s = Sfi.WithOptions().Interceptor(interceptor).OpenSession())
563+
using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
529564
{
530-
Assert.AreEqual(0, await (s.Query<Person>().CountAsync(cancellationToken)), "Entities found in database.");
565+
ForceEscalationToDistributedTx.Escalate();
566+
await (s.SaveAsync(new Person()));
567+
568+
await (s.FlushAsync());
569+
tx.Complete();
531570
}
571+
Assert.That(interceptor.Exception, Is.Null);
532572
}
533573

534574
private void DodgeTransactionCompletionDelayIfRequired()

0 commit comments

Comments
 (0)