Skip to content

Commit c3f8cc4

Browse files
authored
Merge pull request #108 from DataObjects-NET/master-zombie-transaction-rollback-commit
Zombie check before transaction commit or rollback
2 parents cc27172 + 6048b1f commit c3f8cc4

File tree

2 files changed

+199
-26
lines changed

2 files changed

+199
-26
lines changed

Orm/Xtensive.Orm.SqlServer/Sql.Drivers.SqlServer/Connection.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,74 @@ public override void BeginTransaction(IsolationLevel isolationLevel)
9797
activeTransaction = underlyingConnection.BeginTransaction(isolationLevel);
9898
}
9999

100+
/// <inheritdoc/>
101+
public override void Commit()
102+
{
103+
EnsureIsNotDisposed();
104+
EnsureTransactionIsActive();
105+
106+
try {
107+
if (!IsTransactionZombied()) {
108+
ActiveTransaction.Commit();
109+
}
110+
}
111+
finally {
112+
ActiveTransaction.Dispose();
113+
ClearActiveTransaction();
114+
}
115+
}
116+
117+
/// <inheritdoc/>
118+
public override async Task CommitAsync(CancellationToken token = default)
119+
{
120+
EnsureIsNotDisposed();
121+
EnsureTransactionIsActive();
122+
123+
try {
124+
if (!IsTransactionZombied()) {
125+
await ActiveTransaction.CommitAsync(token).ConfigureAwait(false);
126+
}
127+
}
128+
finally {
129+
await ActiveTransaction.DisposeAsync().ConfigureAwait(false);
130+
ClearActiveTransaction();
131+
}
132+
}
133+
134+
/// <inheritdoc/>
135+
public override void Rollback()
136+
{
137+
EnsureIsNotDisposed();
138+
EnsureTransactionIsActive();
139+
140+
try {
141+
if (!IsTransactionZombied()) {
142+
ActiveTransaction.Rollback();
143+
}
144+
}
145+
finally {
146+
ActiveTransaction.Dispose();
147+
ClearActiveTransaction();
148+
}
149+
}
150+
151+
/// <inheritdoc/>
152+
public override async Task RollbackAsync(CancellationToken token = default)
153+
{
154+
EnsureIsNotDisposed();
155+
EnsureTransactionIsActive();
156+
157+
try {
158+
if (!IsTransactionZombied()) {
159+
await ActiveTransaction.RollbackAsync(token).ConfigureAwait(false);
160+
}
161+
}
162+
finally {
163+
await ActiveTransaction.DisposeAsync().ConfigureAwait(false);
164+
ClearActiveTransaction();
165+
}
166+
}
167+
100168
/// <inheritdoc/>
101169
public override void MakeSavepoint(string name)
102170
{
@@ -200,6 +268,11 @@ private async Task OpenWithCheckAsync(string checkQueryString, CancellationToken
200268
}
201269
}
202270

271+
private bool IsTransactionZombied()
272+
{
273+
return ActiveTransaction != null && ActiveTransaction.Connection == null;
274+
}
275+
203276
// Constructors
204277

205278
public Connection(SqlDriver driver, bool checkConnection)

Orm/Xtensive.Orm.Tests/Storage/NestedTransactionsTest.cs

Lines changed: 126 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// Created by: Denis Krjuchkov
55
// Created: 2009.11.26
66

7+
using System.Collections.Generic;
8+
using System.Linq;
79
using NUnit.Framework;
810
using Xtensive.Core;
911
using Xtensive.Orm.Providers;
@@ -13,25 +15,28 @@ namespace Xtensive.Orm.Tests.Storage
1315
{
1416
public class NestedTransactionsTest : TransactionsTestBase
1517
{
18+
private StorageProviderInfo storageProviderInfo;
19+
private Session globalSession;
20+
1621
public override void TestFixtureSetUp()
1722
{
1823
base.TestFixtureSetUp();
19-
Domain.OpenSession();
24+
globalSession = Domain.OpenSession();
25+
storageProviderInfo = StorageProviderInfo.Instance;
2026
}
2127

22-
public override void TestFixtureTearDown()
23-
{
24-
Session.Current.DisposeSafely();
25-
}
28+
protected override void CheckRequirements()
29+
=> Require.AllFeaturesSupported(ProviderFeatures.Savepoints);
30+
31+
public override void TestFixtureTearDown() => globalSession.DisposeSafely();
2632

2733
[Test]
2834
public void UnmodifiedStateIsValidInInnerTransactionTest()
2935
{
30-
Require.AllFeaturesSupported(ProviderFeatures.Savepoints);
31-
using (var outerScope = Session.Demand().OpenTransaction()) {
36+
using (var outerScope = globalSession.OpenTransaction()) {
3237
var outerTransaction = Transaction.Current;
3338
var theHexagon = new Hexagon();
34-
using (var innerScope = Session.Demand().OpenTransaction(TransactionOpenMode.New)) {
39+
using (var innerScope = globalSession.OpenTransaction(TransactionOpenMode.New)) {
3540
AssertStateIsValid(theHexagon);
3641
Assert.AreEqual(theHexagon.Kwanza, 0);
3742
AssertStateIsValid(theHexagon);
@@ -43,11 +48,10 @@ public void UnmodifiedStateIsValidInInnerTransactionTest()
4348
[Test]
4449
public void ModifiedStateIsValidInInnerTransactionTest()
4550
{
46-
Require.AllFeaturesSupported(ProviderFeatures.Savepoints);
47-
using (var outerScope = Session.Demand().OpenTransaction()) {
51+
using (var outerScope = globalSession.OpenTransaction()) {
4852
var outerTransaction = Transaction.Current;
4953
var theHexagon = new Hexagon();
50-
using (var innerScope = Session.Demand().OpenTransaction(TransactionOpenMode.New)) {
54+
using (var innerScope = globalSession.OpenTransaction(TransactionOpenMode.New)) {
5155
theHexagon.IncreaseKwanza();
5256
AssertStateIsValid(theHexagon);
5357
Assert.AreEqual(theHexagon.Kwanza, 1);
@@ -59,11 +63,10 @@ public void ModifiedStateIsValidInInnerTransactionTest()
5963
[Test]
6064
public void UnmodifiedStateIsValidInOuterTransactionAfterInnerTransactionRolledBackTest()
6165
{
62-
Require.AllFeaturesSupported(ProviderFeatures.Savepoints);
63-
using (var outerScope = Session.Demand().OpenTransaction()) {
66+
using (var outerScope = globalSession.OpenTransaction()) {
6467
var outerTransaction = Transaction.Current;
6568
var theHexagon = new Hexagon();
66-
using (var innerScope = Session.Demand().OpenTransaction(TransactionOpenMode.New)) {
69+
using (var innerScope = globalSession.OpenTransaction(TransactionOpenMode.New)) {
6770
// rollback
6871
}
6972
AssertStateIsValid(theHexagon);
@@ -74,11 +77,10 @@ public void UnmodifiedStateIsValidInOuterTransactionAfterInnerTransactionRolledB
7477
[Test]
7578
public void ModifiedStateIsInvalidInOuterTransactionAfterInnerTransactionRolledBackTest()
7679
{
77-
Require.AllFeaturesSupported(ProviderFeatures.Savepoints);
78-
using (var outerScope = Session.Demand().OpenTransaction()) {
80+
using (var outerScope = globalSession.OpenTransaction()) {
7981
var outerTransaction = Transaction.Current;
8082
var theHexagon = new Hexagon();
81-
using (var innerScope = Session.Demand().OpenTransaction(TransactionOpenMode.New)) {
83+
using (var innerScope = globalSession.OpenTransaction(TransactionOpenMode.New)) {
8284
theHexagon.IncreaseKwanza();
8385
// rollback
8486
}
@@ -90,11 +92,10 @@ public void ModifiedStateIsInvalidInOuterTransactionAfterInnerTransactionRolledB
9092
[Test]
9193
public void UnmodifiedStateIsValidInOuterTransactionAfterInnerTransactionCommitedTest()
9294
{
93-
Require.AllFeaturesSupported(ProviderFeatures.Savepoints);
94-
using (var outerScope = Session.Demand().OpenTransaction()) {
95+
using (var outerScope = globalSession.OpenTransaction()) {
9596
var outerTransaction = Transaction.Current;
9697
var theHexagon = new Hexagon();
97-
using (var innerScope = Session.Demand().OpenTransaction(TransactionOpenMode.New)) {
98+
using (var innerScope = globalSession.OpenTransaction(TransactionOpenMode.New)) {
9899
innerScope.Complete();
99100
}
100101
AssertStateIsValid(theHexagon);
@@ -105,11 +106,10 @@ public void UnmodifiedStateIsValidInOuterTransactionAfterInnerTransactionCommite
105106
[Test]
106107
public void ModifiedStateIsValidInOuterTransactionAfterInnerTransactionCommitedTest()
107108
{
108-
Require.AllFeaturesSupported(ProviderFeatures.Savepoints);
109-
using (var outerScope = Session.Demand().OpenTransaction()) {
109+
using (var outerScope = globalSession.OpenTransaction()) {
110110
var outerTransaction = Transaction.Current;
111111
var theHexagon = new Hexagon();
112-
using (var innerScope = Session.Demand().OpenTransaction(TransactionOpenMode.New)) {
112+
using (var innerScope = globalSession.OpenTransaction(TransactionOpenMode.New)) {
113113
theHexagon.IncreaseKwanza();
114114
innerScope.Complete();
115115
}
@@ -121,14 +121,114 @@ public void ModifiedStateIsValidInOuterTransactionAfterInnerTransactionCommitedT
121121
[Test]
122122
public void WrongNestedTransactionUsageTest()
123123
{
124-
Require.AllFeaturesSupported(ProviderFeatures.Savepoints);
125-
using (var outerScope = Session.Demand().OpenTransaction())
126-
using (var innerScope = Session.Demand().OpenTransaction(TransactionOpenMode.New)) {
124+
using (var outerScope = globalSession.OpenTransaction())
125+
using (var innerScope = globalSession.OpenTransaction(TransactionOpenMode.New)) {
127126
outerScope.Complete();
128127
AssertEx.ThrowsInvalidOperationException(outerScope.Dispose);
129128
}
130129
Assert.IsNull(Session.Current.Transaction);
131130
Assert.IsNull(StorageTestHelper.GetNativeTransaction());
132131
}
132+
133+
[Test]
134+
public void RollbackNestedTransactionWithActiveEnumeratorTest()
135+
{
136+
var session = Session.Demand();
137+
using (var outerTx = session.OpenTransaction()) {
138+
_ = new Hexagon();
139+
_ = new Hexagon();
140+
_ = new Hexagon();
141+
142+
IEnumerator<int> enumerator = null;
143+
var innerTx = session.OpenTransaction(TransactionOpenMode.New);
144+
145+
enumerator = session.Query.All<Hexagon>()
146+
.Select(item => item.Id).AsEnumerable().GetEnumerator();
147+
_ = enumerator.MoveNext();
148+
149+
if (storageProviderInfo.CheckProviderIs(StorageProvider.SqlServer)) {
150+
_ = Assert.Throws<StorageException>(() => innerTx.Dispose());
151+
}
152+
else {
153+
Assert.DoesNotThrow(() => innerTx.Dispose());
154+
}
155+
}
156+
}
157+
158+
[Test]
159+
public void RollbackNestedTransactionWithActiveEnumeratorAndThenCompleteOutermostTest()
160+
{
161+
var session = Session.Demand();
162+
using (var outerTx = session.OpenTransaction()) {
163+
_ = new Hexagon();
164+
_ = new Hexagon();
165+
_ = new Hexagon();
166+
167+
IEnumerator<int> enumerator = null;
168+
var innerTx = session.OpenTransaction(TransactionOpenMode.New);
169+
170+
enumerator = session.Query.All<Hexagon>()
171+
.Select(item => item.Id).AsEnumerable().GetEnumerator();
172+
_ = enumerator.MoveNext();
173+
174+
if (storageProviderInfo.CheckProviderIs(StorageProvider.SqlServer)) {
175+
_ = Assert.Throws<StorageException>(() => innerTx.Dispose());
176+
}
177+
else {
178+
Assert.DoesNotThrow(() => innerTx.Dispose());
179+
}
180+
outerTx.Complete();
181+
}
182+
}
183+
184+
[Test]
185+
public void CommitNestedTransactionWithActiveEnumeratorAndRollbackOutermostTest()
186+
{
187+
var session = Session.Demand();
188+
using (var outerTx = session.OpenTransaction()) {
189+
_ = new Hexagon();
190+
_ = new Hexagon();
191+
_ = new Hexagon();
192+
193+
IEnumerator<int> enumerator = null;
194+
var innerTx = session.OpenTransaction(TransactionOpenMode.New);
195+
196+
enumerator = session.Query.All<Hexagon>()
197+
.Select(item => item.Id).AsEnumerable().GetEnumerator();
198+
_ = enumerator.MoveNext();
199+
200+
innerTx.Complete();
201+
innerTx.Dispose();
202+
}
203+
}
204+
205+
[Test]
206+
public void CommitNestedTransactionWithActiveEnumeratorAndCommitOutermostTest()
207+
{
208+
var session = Session.Demand();
209+
var outerTx = session.OpenTransaction();
210+
_ = new Hexagon();
211+
_ = new Hexagon();
212+
_ = new Hexagon();
213+
214+
IEnumerator<int> enumerator = null;
215+
var innerTx = session.OpenTransaction(TransactionOpenMode.New);
216+
217+
enumerator = session.Query.All<Hexagon>()
218+
.Select(item => item.Id).AsEnumerable().GetEnumerator();
219+
_ = enumerator.MoveNext();
220+
221+
innerTx.Complete();
222+
innerTx.Dispose();
223+
224+
outerTx.Complete();
225+
if (storageProviderInfo.CheckProviderIs(StorageProvider.SqlServer)) {
226+
var exception = Assert.Throws<StorageException>(() => outerTx.Dispose());
227+
Assert.That(exception.InnerException, Is.InstanceOf<System.Data.SqlClient.SqlException>());
228+
}
229+
else {
230+
Assert.DoesNotThrow(() => outerTx.Dispose());
231+
}
232+
}
133233
}
134234
}

0 commit comments

Comments
 (0)