Skip to content

Commit ada8f2e

Browse files
NH-4034 - flushing sessions sharing transaction on commit.
1 parent 02c9f07 commit ada8f2e

File tree

12 files changed

+310
-56
lines changed

12 files changed

+310
-56
lines changed

src/NHibernate.Test/DebugSessionFactory.cs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,29 +53,41 @@ public bool CheckSessionsWereClosed()
5353
var allClosed = true;
5454
foreach (var session in _openedSessions)
5555
{
56-
if (session.IsOpen)
56+
// Do not inverse, we want to close all of them.
57+
allClosed = CheckSessionWasClosed(session) && allClosed;
58+
// Catches only session opened from another one while sharing the connection. Those
59+
// opened without sharing the connection stay un-monitored.
60+
foreach (var sharingSession in session.ConnectionManager.SessionsSharingManager)
5761
{
58-
if (session.TransactionContext?.ShouldCloseSessionOnDistributedTransactionCompleted ?? false)
59-
{
60-
// Delayed transactions not having completed and closed their sessions? Give them a chance to complete.
61-
Thread.Sleep(100);
62-
if (!session.IsOpen)
63-
{
64-
_log.Warn($"Test case had a delayed close of session {session.SessionId}.");
65-
continue;
66-
}
67-
}
68-
69-
_log.Error($"Test case didn't close session {session.SessionId}, closing");
70-
allClosed = false;
71-
(session as ISession)?.Close();
72-
(session as IStatelessSession)?.Close();
62+
allClosed = CheckSessionWasClosed(sharingSession) && allClosed;
7363
}
7464
}
7565

7666
return allClosed;
7767
}
7868

69+
private bool CheckSessionWasClosed(ISessionImplementor session)
70+
{
71+
if (!session.IsOpen)
72+
return true;
73+
74+
if (session.TransactionContext?.ShouldCloseSessionOnDistributedTransactionCompleted ?? false)
75+
{
76+
// Delayed transactions not having completed and closed their sessions? Give them a chance to complete.
77+
Thread.Sleep(100);
78+
if (!session.IsOpen)
79+
{
80+
_log.Warn($"Test case had a delayed close of session {session.SessionId}.");
81+
return true;
82+
}
83+
}
84+
85+
_log.Error($"Test case didn't close session {session.SessionId}, closing");
86+
(session as ISession)?.Close();
87+
(session as IStatelessSession)?.Close();
88+
return false;
89+
}
90+
7991
ISessionBuilder ISessionFactory.WithOptions()
8092
{
8193
return new SessionBuilder(ActualFactory.WithOptions(), this);

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,8 @@
14541454
<Compile Include="TestDialects\PostgreSQL83TestDialect.cs" />
14551455
<Compile Include="TestDialects\SQLiteTestDialect.cs" />
14561456
<Compile Include="Tools\hbm2ddl\SchemaExportTests\ExportToFileFixture.cs" />
1457+
<Compile Include="TransactionTest\Person.cs" />
1458+
<Compile Include="TransactionTest\TransactionFixtureBase.cs" />
14571459
<Compile Include="TransformTests\ImplementationOfEqualityTests.cs" />
14581460
<Compile Include="TypesTest\CharClass.cs" />
14591461
<Compile Include="TypesTest\CharClassFixture.cs" />
@@ -3282,6 +3284,7 @@
32823284
<EmbeddedResource Include="NHSpecificTest\NH1291AnonExample\Mappings.hbm.xml" />
32833285
</ItemGroup>
32843286
<ItemGroup>
3287+
<EmbeddedResource Include="TransactionTest\Person.hbm.xml" />
32853288
<EmbeddedResource Include="SessionBuilder\Mappings.hbm.xml" />
32863289
<EmbeddedResource Include="IdTest\IdentityClass.hbm.xml" />
32873290
<EmbeddedResource Include="NHSpecificTest\NH1904\StructMappings.hbm.xml" />
Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,22 @@
1-
using System.Collections;
1+
using System.Linq;
22
using System.Transactions;
3-
using NHibernate.DomainModel;
3+
using NHibernate.Linq;
4+
using NHibernate.Test.TransactionTest;
45
using NUnit.Framework;
56

67
namespace NHibernate.Test.SystemTransactions
78
{
89
[TestFixture]
9-
public class TransactionFixture : TestCase
10+
public class TransactionFixture : TransactionFixtureBase
1011
{
11-
protected override IList Mappings
12-
{
13-
get { return new string[] { "WZ.hbm.xml" }; }
14-
}
15-
1612
[Test]
1713
public void CanUseSystemTransactionsToCommit()
1814
{
19-
object identifier;
15+
int identifier;
2016
using(ISession session = Sfi.OpenSession())
2117
using(TransactionScope tx = new TransactionScope())
2218
{
23-
W s = new W();
19+
var s = new Person();
2420
session.Save(s);
2521
identifier = s.Id;
2622
tx.Complete();
@@ -29,11 +25,74 @@ public void CanUseSystemTransactionsToCommit()
2925
using (ISession session = Sfi.OpenSession())
3026
using (TransactionScope tx = new TransactionScope())
3127
{
32-
W w = session.Get<W>(identifier);
28+
var w = session.Get<Person>(identifier);
3329
Assert.IsNotNull(w);
3430
session.Delete(w);
3531
tx.Complete();
3632
}
3733
}
34+
35+
[Test]
36+
public void FlushFromTransactionAppliesToDisposedSharingSession()
37+
{
38+
using (var s = OpenSession())
39+
{
40+
var builder = s.SessionWithOptions().Connection();
41+
42+
using (var t = new TransactionScope())
43+
{
44+
var p1 = new Person();
45+
// The relationship is there for failing in case the flush ordering is not the expected one.
46+
var p2 = new Person { Related = p1 };
47+
var p3 = new Person { Related = p2 };
48+
49+
using (var s1 = builder.OpenSession())
50+
s1.Save(p1);
51+
using (var s2 = builder.OpenSession())
52+
s2.Save(p2);
53+
s.Save(p3);
54+
t.Complete();
55+
}
56+
}
57+
58+
using (var s = OpenSession())
59+
using (var t = s.BeginTransaction())
60+
{
61+
Assert.That(s.Query<Person>().Count(), Is.EqualTo(3));
62+
Assert.That(s.Query<Person>().Count(p => p.Related != null), Is.EqualTo(2));
63+
t.Commit();
64+
}
65+
}
66+
67+
[Test]
68+
public void FlushFromTransactionAppliesToSharingSession()
69+
{
70+
using (var s = OpenSession())
71+
{
72+
var builder = s.SessionWithOptions().Connection();
73+
74+
using (var s1 = builder.OpenSession())
75+
using (var s2 = builder.OpenSession())
76+
using (var t = new TransactionScope())
77+
{
78+
var p1 = new Person();
79+
// The relationship is there for failing in case the flush ordering is not the expected one.
80+
var p2 = new Person { Related = p1 };
81+
var p3 = new Person { Related = p2 };
82+
s1.Save(p1);
83+
s2.Save(p2);
84+
s.Save(p3);
85+
t.Complete();
86+
}
87+
}
88+
89+
using (var s = OpenSession())
90+
using (var t = s.BeginTransaction())
91+
{
92+
Assert.That(s.Query<Person>().Count(), Is.EqualTo(3));
93+
Assert.That(s.Query<Person>().Count(p => p.Related != null), Is.EqualTo(2));
94+
t.Commit();
95+
}
96+
}
3897
}
3998
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace NHibernate.Test.TransactionTest
4+
{
5+
public class Person
6+
{
7+
public virtual int Id { get; set; }
8+
9+
public virtual DateTime CreatedAt { get; set; } = DateTime.Now;
10+
11+
public virtual string NotNullData { get; set; } = "not-null";
12+
13+
public virtual Person Related { get; set; }
14+
}
15+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3+
namespace="NHibernate.Test.TransactionTest"
4+
assembly="NHibernate.Test">
5+
6+
<class name="Person">
7+
<id name="Id">
8+
<generator class="hilo"/>
9+
</id>
10+
<property name="CreatedAt"/>
11+
<property name="NotNullData"/> <!-- NOT NULL will be set on created schema directly to avoid NH checks. -->
12+
<many-to-one name="Related" class="Person" />
13+
</class>
14+
</hibernate-mapping>

src/NHibernate.Test/TransactionTest/TransactionFixture.cs

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
using System;
2-
using System.Collections;
3-
using System.Data.Common;
2+
using System.Linq;
3+
using NHibernate.Linq;
44
using NUnit.Framework;
55

66
namespace NHibernate.Test.TransactionTest
77
{
88
[TestFixture]
9-
public class TransactionFixture : TestCase
9+
public class TransactionFixture : TransactionFixtureBase
1010
{
11-
protected override IList Mappings
12-
{
13-
// The mapping is only actually needed in one test
14-
get { return new string[] {"Simple.hbm.xml"}; }
15-
}
16-
1711
[Test]
1812
public void SecondTransactionShouldntBeCommitted()
1913
{
@@ -74,25 +68,25 @@ public void CommandAfterTransactionShouldWork()
7468
{
7569
using (ISession s = OpenSession())
7670
{
77-
using (ITransaction t = s.BeginTransaction())
71+
using (s.BeginTransaction())
7872
{
7973
}
8074

81-
s.CreateQuery("from Simple").List();
75+
s.CreateQuery("from Person").List();
8276

8377
using (ITransaction t = s.BeginTransaction())
8478
{
8579
t.Commit();
8680
}
8781

88-
s.CreateQuery("from Simple").List();
82+
s.CreateQuery("from Person").List();
8983

9084
using (ITransaction t = s.BeginTransaction())
9185
{
9286
t.Rollback();
9387
}
9488

95-
s.CreateQuery("from Simple").List();
89+
s.CreateQuery("from Person").List();
9690
}
9791
}
9892

@@ -141,5 +135,36 @@ public void WasCommittedOrRolledBack()
141135
}
142136
}
143137
}
138+
139+
[Test]
140+
public void FlushFromTransactionAppliesToSharingSession()
141+
{
142+
using (var s = OpenSession())
143+
{
144+
var builder = s.SessionWithOptions().Connection();
145+
146+
using (var s1 = builder.OpenSession())
147+
using (var s2 = builder.OpenSession())
148+
using (var t = s.BeginTransaction())
149+
{
150+
var p1 = new Person();
151+
// The relationship is there for failing in case the flush ordering is not the expected one.
152+
var p2 = new Person { Related = p1 };
153+
var p3 = new Person { Related = p2 };
154+
s1.Save(p1);
155+
s2.Save(p2);
156+
s.Save(p3);
157+
t.Commit();
158+
}
159+
}
160+
161+
using (var s = OpenSession())
162+
using (var t = s.BeginTransaction())
163+
{
164+
Assert.That(s.Query<Person>().Count(), Is.EqualTo(3));
165+
Assert.That(s.Query<Person>().Count(p => p.Related != null), Is.EqualTo(2));
166+
t.Commit();
167+
}
168+
}
144169
}
145170
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Collections;
2+
using System.Linq;
3+
using NHibernate.Linq;
4+
5+
namespace NHibernate.Test.TransactionTest
6+
{
7+
public abstract class TransactionFixtureBase : TestCase
8+
{
9+
protected override IList Mappings => new[] { "TransactionTest.Person.hbm.xml" };
10+
11+
protected override string MappingsAssembly => "NHibernate.Test";
12+
13+
protected override void OnTearDown()
14+
{
15+
using (var s = OpenSession())
16+
using (var t = s.BeginTransaction())
17+
{
18+
// MySql and maybe some other db fails deleting the whole table in "right order"
19+
foreach (var p in s.Query<Person>().Where(p => p.Related.Related != null))
20+
{
21+
s.Delete(p);
22+
}
23+
s.Flush();
24+
s.CreateQuery("delete from Person p where p.Related != null").ExecuteUpdate();
25+
s.CreateQuery("delete from System.Object").ExecuteUpdate();
26+
t.Commit();
27+
}
28+
}
29+
}
30+
}

src/NHibernate/AdoNet/ConnectionManager.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Data;
34
using System.Data.Common;
45
using System.Runtime.Serialization;
@@ -41,6 +42,10 @@ public interface Callback
4142
private readonly ISessionImplementor session;
4243
private readonly ConnectionReleaseMode connectionReleaseMode;
4344
private readonly IInterceptor interceptor;
45+
private readonly List<ISessionImplementor> _sessionsSharingManager = new List<ISessionImplementor>();
46+
47+
public ISessionImplementor Session => session;
48+
public IReadOnlyCollection<ISessionImplementor> SessionsSharingManager => _sessionsSharingManager;
4449

4550
[NonSerialized]
4651
private bool _releasesEnabled = true;
@@ -63,6 +68,16 @@ public ConnectionManager(
6368
ownConnection = suppliedConnection == null;
6469
}
6570

71+
public void AddSessionSharingManager(ISessionImplementor session)
72+
{
73+
_sessionsSharingManager.Add(session);
74+
}
75+
76+
public void RemoveSessionSharingManager(ISessionImplementor session)
77+
{
78+
_sessionsSharingManager.Remove(session);
79+
}
80+
6681
public bool IsInActiveTransaction
6782
{
6883
get

src/NHibernate/ISharedSessionBuilder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ public interface ISharedSessionBuilder : ISessionBuilder<ISharedSessionBuilder>
99
{
1010
/// <summary>
1111
/// Signifies that the connection from the original session should be used to create the new session.
12+
/// The original session remains responsible for it and its closing will cause sharing sessions to be no
13+
/// more usable.
1214
/// </summary>
1315
/// <returns><see langword="this" />, for method chaining.</returns>
1416
ISharedSessionBuilder Connection();

0 commit comments

Comments
 (0)