9
9
10
10
11
11
using System ;
12
- using System . Collections ;
13
12
using System . Linq ;
14
- using System . Reflection ;
15
13
using System . Threading ;
16
14
using System . Transactions ;
17
15
using log4net ;
18
16
using log4net . Repository . Hierarchy ;
19
- using NHibernate . Cfg ;
20
- using NHibernate . Cfg . MappingSchema ;
21
17
using NHibernate . Linq ;
22
- using NHibernate . Tool . hbm2ddl ;
18
+ using NHibernate . Test . TransactionTest ;
23
19
using NUnit . Framework ;
24
20
25
- namespace NHibernate . Test . NHSpecificTest . DtcFailures
21
+ namespace NHibernate . Test . SystemTransactions
26
22
{
27
23
using System . Threading . Tasks ;
28
24
[ TestFixture ]
29
- public class DtcFailuresFixtureAsync : TestCase
25
+ public class DistributedSystemTransactionFixtureAsync : SystemTransactionFixtureBase
30
26
{
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 ) ) ;
38
28
39
29
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 ) ;
60
31
61
32
protected override void OnTearDown ( )
62
33
{
63
34
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 ( ) ;
78
36
}
79
37
80
38
[ Test ]
@@ -91,7 +49,7 @@ public async Task SupportsEnlistingInDistributedAsync()
91
49
92
50
using ( var s = OpenSession ( ) )
93
51
{
94
- await ( s . SaveAsync ( new Person { CreatedAt = DateTime . Now } ) ) ;
52
+ await ( s . SaveAsync ( new Person ( ) ) ) ;
95
53
// Ensure the connection is acquired (thus enlisted)
96
54
Assert . DoesNotThrowAsync ( ( ) => s . FlushAsync ( ) , "Failure enlisting a connection in a distributed transaction." ) ;
97
55
}
@@ -105,7 +63,7 @@ public async Task SupportsPromotingToDistributedAsync()
105
63
{
106
64
using ( var s = OpenSession ( ) )
107
65
{
108
- await ( s . SaveAsync ( new Person { CreatedAt = DateTime . Now } ) ) ;
66
+ await ( s . SaveAsync ( new Person ( ) ) ) ;
109
67
// Ensure the connection is acquired (thus enlisted)
110
68
await ( s . FlushAsync ( ) ) ;
111
69
}
@@ -119,7 +77,7 @@ public async Task SupportsPromotingToDistributedAsync()
119
77
}
120
78
121
79
[ Test ]
122
- public async Task WillNotCrashOnDtcPrepareFailureAsync ( )
80
+ public async Task WillNotCrashOnPrepareFailureAsync ( )
123
81
{
124
82
var tx = new TransactionScope ( TransactionScopeAsyncFlowOption . Enabled ) ;
125
83
var disposeCalled = false ;
@@ -163,7 +121,7 @@ public async Task CanRollbackTransactionAsync([Values(false, true)] bool explici
163
121
using ( var s = OpenSession ( ) )
164
122
{
165
123
ForceEscalationToDistributedTx . Escalate ( true ) ; //will rollback tx
166
- await ( s . SaveAsync ( new Person { CreatedAt = DateTime . Today } ) ) ;
124
+ await ( s . SaveAsync ( new Person ( ) ) ) ;
167
125
168
126
if ( explicitFlush )
169
127
await ( s . FlushAsync ( ) ) ;
@@ -189,7 +147,7 @@ public async Task CanRollbackTransactionAsync([Values(false, true)] bool explici
189
147
}
190
148
}
191
149
192
- await ( AssertNoPersonsAsync ( ) ) ;
150
+ AssertNoPersons ( ) ;
193
151
}
194
152
195
153
[ Test ]
@@ -199,14 +157,14 @@ public async Task CanRollbackTransactionFromScopeAsync([Values(false, true)] boo
199
157
using ( var s = OpenSession ( ) )
200
158
{
201
159
ForceEscalationToDistributedTx . Escalate ( ) ;
202
- await ( s . SaveAsync ( new Person { CreatedAt = DateTime . Today } ) ) ;
160
+ await ( s . SaveAsync ( new Person ( ) ) ) ;
203
161
204
162
if ( explicitFlush )
205
163
await ( s . FlushAsync ( ) ) ;
206
164
// No Complete call for triggering rollback.
207
165
}
208
166
209
- await ( AssertNoPersonsAsync ( ) ) ;
167
+ AssertNoPersons ( ) ;
210
168
}
211
169
212
170
[ Test ]
@@ -219,7 +177,7 @@ public async Task RollbackOutsideNhAsync([Values(false, true)] bool explicitFlus
219
177
{
220
178
using ( var s = OpenSession ( ) )
221
179
{
222
- var person = new Person { CreatedAt = DateTime . Now } ;
180
+ var person = new Person ( ) ;
223
181
await ( s . SaveAsync ( person ) ) ;
224
182
225
183
if ( explicitFlush )
@@ -237,7 +195,7 @@ public async Task RollbackOutsideNhAsync([Values(false, true)] bool explicitFlus
237
195
_log . Debug ( "Transaction aborted." ) ;
238
196
}
239
197
240
- await ( AssertNoPersonsAsync ( ) ) ;
198
+ AssertNoPersons ( ) ;
241
199
}
242
200
243
201
[ Test ]
@@ -248,17 +206,16 @@ public async Task TransactionInsertWithRollBackFromScopeAsync([Values(false, tru
248
206
{
249
207
using ( var s = OpenSession ( ) )
250
208
{
251
- var person = new Person { CreatedAt = DateTime . Now } ;
209
+ var person = new Person ( ) ;
252
210
await ( s . SaveAsync ( person ) ) ;
253
211
ForceEscalationToDistributedTx . Escalate ( ) ;
254
- person . CreatedAt = DateTime . Now ;
255
212
256
213
if ( explicitFlush )
257
214
await ( s . FlushAsync ( ) ) ;
258
215
}
259
216
// No Complete call for triggering rollback.
260
217
}
261
- await ( AssertNoPersonsAsync ( ) ) ;
218
+ AssertNoPersons ( ) ;
262
219
}
263
220
264
221
[ Test ]
@@ -271,10 +228,9 @@ public async Task TransactionInsertWithRollBackTaskAsync([Values(false, true)] b
271
228
{
272
229
using ( var s = OpenSession ( ) )
273
230
{
274
- var person = new Person { CreatedAt = DateTime . Now } ;
231
+ var person = new Person ( ) ;
275
232
await ( s . SaveAsync ( person ) ) ;
276
233
ForceEscalationToDistributedTx . Escalate ( true ) ; //will rollback tx
277
- person . CreatedAt = DateTime . Now ;
278
234
279
235
if ( explicitFlush )
280
236
await ( s . FlushAsync ( ) ) ;
@@ -289,7 +245,7 @@ public async Task TransactionInsertWithRollBackTaskAsync([Values(false, true)] b
289
245
_log . Debug ( "Transaction aborted." ) ;
290
246
}
291
247
292
- await ( AssertNoPersonsAsync ( ) ) ;
248
+ AssertNoPersons ( ) ;
293
249
}
294
250
295
251
[ Test ]
@@ -396,7 +352,7 @@ public async Task CanDeleteItemInDtcAsync([Values(false, true)] bool explicitFlu
396
352
{
397
353
using ( var s = OpenSession ( ) )
398
354
{
399
- id = await ( s . SaveAsync ( new Person { CreatedAt = DateTime . Today } ) ) ;
355
+ id = await ( s . SaveAsync ( new Person ( ) ) ) ;
400
356
401
357
ForceEscalationToDistributedTx . Escalate ( ) ;
402
358
@@ -432,7 +388,7 @@ public async Task CanDeleteItemInDtcAsync([Values(false, true)] bool explicitFlu
432
388
433
389
DodgeTransactionCompletionDelayIfRequired ( ) ;
434
390
435
- await ( AssertNoPersonsAsync ( ) ) ;
391
+ AssertNoPersons ( ) ;
436
392
}
437
393
438
394
[ Test ]
@@ -455,14 +411,82 @@ public async Task NH1744Async()
455
411
}
456
412
}
457
413
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
+
458
482
[ Test ]
459
483
public async Task CanUseSessionOutsideOfScopeAfterScopeAsync ( [ Values ( false , true ) ] bool explicitFlush )
460
484
{
461
485
using ( var s = Sfi . WithOptions ( ) . ConnectionReleaseMode ( ConnectionReleaseMode . OnClose ) . OpenSession ( ) )
462
486
{
463
487
using ( var tx = new TransactionScope ( TransactionScopeAsyncFlowOption . Enabled ) )
464
488
{
465
- await ( s . SaveAsync ( new Person { CreatedAt = DateTime . Today } ) ) ;
489
+ await ( s . SaveAsync ( new Person ( ) ) ) ;
466
490
467
491
ForceEscalationToDistributedTx . Escalate ( ) ;
468
492
@@ -498,7 +522,7 @@ public async Task DelayedTransactionCompletionAsync([Values(false, true)] bool e
498
522
{
499
523
using ( var s = OpenSession ( ) )
500
524
{
501
- await ( s . SaveAsync ( new Person { CreatedAt = DateTime . Today } ) ) ;
525
+ await ( s . SaveAsync ( new Person ( ) ) ) ;
502
526
503
527
ForceEscalationToDistributedTx . Escalate ( ) ;
504
528
@@ -511,6 +535,15 @@ public async Task DelayedTransactionCompletionAsync([Values(false, true)] bool e
511
535
var count = await ( controlSession . Query < Person > ( ) . CountAsync ( ) ) ;
512
536
if ( count != i )
513
537
{
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.
514
547
Thread . Sleep ( 100 ) ;
515
548
var countSecondTry = await ( controlSession . Query < Person > ( ) . CountAsync ( ) ) ;
516
549
Assert . Warn ( $ "Unexpected entity count: { count } instead of { i } . " +
@@ -522,13 +555,20 @@ public async Task DelayedTransactionCompletionAsync([Values(false, true)] bool e
522
555
}
523
556
}
524
557
525
- private async Task AssertNoPersonsAsync ( CancellationToken cancellationToken = default ( CancellationToken ) )
558
+ [ Test ]
559
+ public async Task DoNotDeadlockOnAfterTransactionWaitAsync ( )
526
560
{
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 ) )
529
564
{
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 ( ) ;
531
570
}
571
+ Assert . That ( interceptor . Exception , Is . Null ) ;
532
572
}
533
573
534
574
private void DodgeTransactionCompletionDelayIfRequired ( )
0 commit comments