Skip to content

Commit 1d4e37c

Browse files
authored
Merge pull request #111 from DataObjects-NET/command-allocation-during-corrupted-tx
Resolves command allocation during corrupted transaction
2 parents 7f5d17b + 9cb3468 commit 1d4e37c

File tree

2 files changed

+186
-2
lines changed

2 files changed

+186
-2
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright (C) 2020 Xtensive LLC.
2+
// This code is distributed under MIT license terms.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using System.Transactions;
12+
using NUnit.Framework;
13+
using Xtensive.Core;
14+
using Xtensive.Orm.Configuration;
15+
using Xtensive.Orm.Model;
16+
using Xtensive.Orm.Services;
17+
using Xtensive.Orm.Tests.Issues.IssueGitHub0110_SimpleCommandProcessorOverridesOriginalExceptionModel;
18+
19+
namespace Xtensive.Orm.Tests.Issues.IssueGitHub0110_SimpleCommandProcessorOverridesOriginalExceptionModel
20+
{
21+
[HierarchyRoot(InheritanceSchema.ConcreteTable)]
22+
public class Bin : Entity
23+
{
24+
[Field, Key]
25+
public long Id { get; private set; }
26+
27+
public Bin(Session session)
28+
: base(session)
29+
{
30+
}
31+
}
32+
33+
[HierarchyRoot(InheritanceSchema.ConcreteTable)]
34+
[Index("Name", Unique = true)]
35+
[Index(nameof(Active), nameof(N), nameof(M))]
36+
public class InventoryBalance : Entity
37+
{
38+
[Field, Key]
39+
public long Id { get; private set; }
40+
41+
[Field(Nullable = false)]
42+
public Bin Bin { get; private set; }
43+
44+
[Field]
45+
public bool Active { get; set; }
46+
47+
[Field]
48+
public string Name { get; set; }
49+
50+
[Field]
51+
public decimal N { get; set; }
52+
53+
[Field]
54+
public decimal M { get; set; }
55+
56+
[Field(DefaultSqlExpression = "GETUTCDATE()")]
57+
public DateTime ModifiedOn { get; set; }
58+
59+
public InventoryBalance(Session session, string name, Bin bin)
60+
: base(session)
61+
{
62+
Name = name;
63+
Active = true;
64+
Bin = bin;
65+
}
66+
}
67+
68+
public class OperationState
69+
{
70+
public Exception CatchendException { get; set; }
71+
72+
public bool Ended { get; set; }
73+
74+
public Domain Domain { get; }
75+
76+
public OperationState(Domain domain)
77+
{
78+
Domain = domain;
79+
}
80+
}
81+
}
82+
83+
namespace Xtensive.Orm.Tests.Issues
84+
{
85+
public class IssueGitHub0110_SimpleCommandProcessorOverridesOriginalException : AutoBuildTest
86+
{
87+
private static ManualResetEvent theStarter;
88+
89+
protected override void CheckRequirements() => Require.ProviderIs(StorageProvider.SqlServer);
90+
91+
protected override DomainConfiguration BuildConfiguration()
92+
{
93+
var configuration = base.BuildConfiguration();
94+
configuration.Types.Register(typeof(InventoryBalance).Assembly, typeof(InventoryBalance).Namespace);
95+
configuration.UpgradeMode = DomainUpgradeMode.Recreate;
96+
97+
var defaultSessionConfig = configuration.Sessions.Default;
98+
if (defaultSessionConfig == null) {
99+
defaultSessionConfig = new SessionConfiguration(WellKnown.Sessions.Default, SessionOptions.ServerProfile);
100+
configuration.Sessions.Add(defaultSessionConfig);
101+
}
102+
103+
defaultSessionConfig.DefaultIsolationLevel = IsolationLevel.Snapshot;
104+
defaultSessionConfig.DefaultCommandTimeout = 60 * 60; // 60 min.
105+
106+
defaultSessionConfig.BatchSize = 1; //force to use SimpleCommandProcessor
107+
108+
return configuration;
109+
}
110+
111+
protected override void PopulateData()
112+
{
113+
using (var session = Domain.OpenSession())
114+
using (var tx = session.OpenTransaction()) {
115+
_ = new InventoryBalance(session, "aaa", new Bin(session));
116+
tx.Complete();
117+
}
118+
}
119+
120+
[SetUp]
121+
public void InitStarter() => theStarter = new ManualResetEvent(false);
122+
123+
[TearDown]
124+
public void DisposeStarter()
125+
{
126+
theStarter.DisposeSafely();
127+
theStarter = null;
128+
}
129+
130+
[Test]
131+
public void MainTest()
132+
{
133+
var task1State = new OperationState(Domain);
134+
var task2State = new OperationState(Domain);
135+
136+
var task1 = new System.Threading.Tasks.Task(Outer, task1State);
137+
var task2 = new System.Threading.Tasks.Task(Outer, task2State);
138+
task1.Start();
139+
task2.Start();
140+
141+
Thread.Sleep(500);
142+
143+
_ = theStarter.Set();
144+
145+
while (!task1State.Ended && !task1State.Ended) {
146+
Thread.Sleep(100);
147+
}
148+
149+
var exception = task1State.CatchendException ?? task2State.CatchendException;
150+
Assert.That(exception, Is.Not.Null);
151+
Assert.That(exception, Is.InstanceOf<TransactionSerializationFailureException>());
152+
}
153+
154+
private static void Outer(object state)
155+
{
156+
var operationState = (OperationState) state;
157+
158+
_ = theStarter.WaitOne();
159+
try {
160+
using (var session = operationState.Domain.OpenSession(SessionType.User))
161+
using (var tx = session.OpenTransaction()) {
162+
var binId = session.Query.All<Bin>().First().Id;
163+
var bin = session.Query.SingleOrDefault<Bin>(binId);
164+
Inner(session, bin);
165+
tx.Complete();
166+
}
167+
operationState.Ended = true;
168+
}
169+
catch (Exception ex) {
170+
operationState.CatchendException = ex;
171+
operationState.Ended = true;
172+
}
173+
}
174+
175+
private static void Inner(Session session, Bin bin)
176+
{
177+
using (var tx = session.OpenTransaction()) {
178+
var doc = session.Query.All<InventoryBalance>().SingleOrDefault(o => o.Active == true && o.Bin == bin);
179+
doc.N += 1;
180+
tx.Complete();
181+
}
182+
}
183+
}
184+
}

Orm/Xtensive.Orm/Orm/Providers/CommandProcessing/SimpleCommandProcessor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@ void ISqlTaskProcessor.ProcessTask(SqlPersistTask task, CommandProcessorContext
3232
ValidateCommandParameters(part);
3333
context.ActiveCommand.AddPart(part);
3434
var affectedRowsCount = context.ActiveCommand.ExecuteNonQuery();
35-
if (task.ValidateRowCount && affectedRowsCount==0) {
35+
if (task.ValidateRowCount && affectedRowsCount == 0) {
3636
throw new VersionConflictException(string.Format(
3737
Strings.ExVersionOfEntityWithKeyXDiffersFromTheExpectedOne, task.EntityKey));
3838
}
3939
}
4040
finally {
4141
context.ActiveCommand.DisposeSafely();
4242
ReleaseCommand(context);
43-
AllocateCommand(context);
4443
}
44+
AllocateCommand(context);
4545
}
4646
}
4747

0 commit comments

Comments
 (0)