Skip to content

Commit af8a46e

Browse files
authored
Merge pull request #342 from DataObjects-NET/6.0-stale-session-tokens-bug
Fix rare bug when entities appear Actual (EntityState.IsActual) even outside session
2 parents d8fb229 + 55e1461 commit af8a46e

File tree

5 files changed

+373
-3
lines changed

5 files changed

+373
-3
lines changed

ChangeLog/6.0.12_dev.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
[main] Addressed DataTypeCollection.Add method issue of wrong adding storage-specifid types to the collection
2+
[main] Addressed rare issue of entities expire management when SessionOption.NonTransactionalReads is on
23
[sqlserver] Sql error messages for British English are correctly parsed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
// Copyright (C) 2023 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 NUnit.Framework;
6+
using Xtensive.Orm.Configuration;
7+
using Xtensive.Orm.Tests.Issues.IssueGithub0066_TransactionalStatesRemainsActualWhenNonTransactionalReadsModel;
8+
9+
namespace Xtensive.Orm.Tests.Issues.IssueGithub0066_TransactionalStatesRemainsActualWhenNonTransactionalReadsModel
10+
{
11+
[HierarchyRoot]
12+
public class TestEntity : Entity
13+
{
14+
[Field, Key]
15+
public int Id { get; private set; }
16+
17+
[Field]
18+
public string Text { get; set; }
19+
20+
public TestEntity(Session session)
21+
: base(session)
22+
{
23+
}
24+
}
25+
}
26+
27+
namespace Xtensive.Orm.Tests.Issues
28+
{
29+
[TestFixture]
30+
public sealed class IssueGithub0066_TransactionalStatesRemainsActualWhenNonTransactionalReads : AutoBuildTest
31+
{
32+
private readonly SessionConfiguration clientProfileConfiguration = new SessionConfiguration(SessionOptions.ClientProfile);
33+
private readonly SessionConfiguration serverProfileConfiguration = new SessionConfiguration(SessionOptions.ServerProfile);
34+
35+
protected override DomainConfiguration BuildConfiguration()
36+
{
37+
var configuration = base.BuildConfiguration();
38+
configuration.Types.Register(typeof(TestEntity));
39+
configuration.UpgradeMode = DomainUpgradeMode.Recreate;
40+
return configuration;
41+
}
42+
43+
[Test]
44+
public void ClientProfileCase1()
45+
{
46+
using (var session = Domain.OpenSession(clientProfileConfiguration)) {
47+
var sessionToken = session.GetLifetimeToken();
48+
var sessionEntity = new TestEntity(session);
49+
Assert.That(sessionEntity.State.LifetimeToken, Is.EqualTo(sessionToken));
50+
Assert.That(sessionEntity.State.IsActual, Is.True);
51+
Assert.That(sessionEntity.State.LifetimeToken.IsActive, Is.True);
52+
53+
TestEntity outOfScopeEntity;
54+
StateLifetimeToken outOfScopeTxToken;
55+
using(var tx = session.OpenTransaction()) {
56+
var txToken = session.GetLifetimeToken();
57+
Assert.That(txToken.IsActive, Is.True);
58+
Assert.That(sessionToken.IsActive, Is.True);
59+
Assert.That(sessionToken, Is.Not.EqualTo(txToken));
60+
61+
var txEntity = new TestEntity(session);
62+
Assert.That(txEntity.State.LifetimeToken, Is.EqualTo(txToken));
63+
Assert.That(txEntity.State.IsActual, Is.True);
64+
Assert.That(txEntity.State.LifetimeToken.IsActive, Is.True);
65+
66+
outOfScopeEntity = txEntity;
67+
outOfScopeTxToken = txToken;
68+
tx.Complete();
69+
}
70+
71+
Assert.That(outOfScopeTxToken.IsActive, Is.True);
72+
73+
Assert.That(sessionEntity.State.IsActual, Is.True);
74+
Assert.That(sessionEntity.State.LifetimeToken.IsActive, Is.True);
75+
76+
Assert.That(outOfScopeEntity.State.IsActual, Is.True);
77+
Assert.That(outOfScopeEntity.State.LifetimeToken.IsActive, Is.True);
78+
}
79+
}
80+
81+
[Test]
82+
public void ClientProfileCase2()
83+
{
84+
using (var session = Domain.OpenSession(clientProfileConfiguration)) {
85+
var sessionToken = session.GetLifetimeToken();
86+
var sessionEntity = new TestEntity(session);
87+
Assert.That(sessionEntity.State.LifetimeToken, Is.EqualTo(sessionToken));
88+
Assert.That(sessionEntity.State.IsActual, Is.True);
89+
Assert.That(sessionEntity.State.LifetimeToken.IsActive, Is.True);
90+
91+
TestEntity outOfScopeEntity;
92+
StateLifetimeToken outOfScopeTxToken;
93+
using (var tx = session.OpenTransaction()) {
94+
var txToken = session.GetLifetimeToken();
95+
Assert.That(txToken.IsActive, Is.True);
96+
Assert.That(sessionToken.IsActive, Is.True);
97+
Assert.That(sessionToken, Is.Not.EqualTo(txToken));
98+
99+
var txEntity = new TestEntity(session);
100+
Assert.That(txEntity.State.LifetimeToken, Is.EqualTo(txToken));
101+
Assert.That(txEntity.State.LifetimeToken.IsActive, Is.True);
102+
Assert.That(txEntity.State.IsActual, Is.True);
103+
104+
outOfScopeEntity = txEntity;
105+
outOfScopeTxToken = txToken;
106+
}
107+
108+
Assert.That(outOfScopeTxToken.IsActive, Is.False);
109+
110+
Assert.That(sessionEntity.State.IsActual, Is.True);
111+
Assert.That(sessionEntity.State.LifetimeToken.IsActive, Is.True);
112+
113+
Assert.That(outOfScopeEntity.State.IsActual, Is.False);
114+
Assert.That(outOfScopeEntity.State.LifetimeToken.IsActive, Is.False);
115+
}
116+
}
117+
118+
[Test]
119+
public void ClientProfileCase3()
120+
{
121+
TestEntity outOfSessionRef;
122+
TestEntity outOfSessionAndTxRef;
123+
using (var session = Domain.OpenSession(clientProfileConfiguration)) {
124+
var sessionToken = session.GetLifetimeToken();
125+
var sessionEntity = new TestEntity(session);
126+
Assert.That(sessionEntity.State.LifetimeToken, Is.EqualTo(sessionToken));
127+
Assert.That(sessionEntity.State.IsActual, Is.True);
128+
Assert.That(sessionEntity.State.LifetimeToken.IsActive, Is.True);
129+
130+
TestEntity outOfScopeEntity;
131+
StateLifetimeToken outOfScopeTxToken;
132+
using (var tx = session.OpenTransaction()) {
133+
var txToken = session.GetLifetimeToken();
134+
Assert.That(txToken.IsActive, Is.True);
135+
Assert.That(sessionToken.IsActive, Is.True);
136+
Assert.That(sessionToken, Is.Not.EqualTo(txToken));
137+
138+
var txEntity = new TestEntity(session);
139+
Assert.That(txEntity.State.LifetimeToken, Is.EqualTo(txToken));
140+
Assert.That(txEntity.State.IsActual, Is.True);
141+
Assert.That(txEntity.State.LifetimeToken.IsActive, Is.True);
142+
143+
outOfScopeEntity = txEntity;
144+
outOfScopeTxToken = txToken;
145+
tx.Complete();
146+
}
147+
148+
Assert.That(outOfScopeTxToken.IsActive, Is.True);
149+
150+
Assert.That(sessionEntity.State.IsActual, Is.True);
151+
Assert.That(sessionEntity.State.LifetimeToken.IsActive, Is.True);
152+
153+
Assert.That(outOfScopeEntity.State.IsActual, Is.True);
154+
Assert.That(outOfScopeEntity.State.LifetimeToken.IsActive, Is.True);
155+
156+
outOfSessionRef = sessionEntity;
157+
outOfSessionAndTxRef = outOfScopeEntity;
158+
}
159+
160+
Assert.That(outOfSessionRef.State.IsActual, Is.False);
161+
Assert.That(outOfSessionRef.State.LifetimeToken.IsActive, Is.False);
162+
163+
Assert.That(outOfSessionAndTxRef.State.IsActual, Is.False);
164+
Assert.That(outOfSessionAndTxRef.State.LifetimeToken.IsActive, Is.False);
165+
}
166+
167+
[Test]
168+
public void ClientProfileCase4()
169+
{
170+
TestEntity outOfSessionRef;
171+
TestEntity outOfSessionAndTxRef;
172+
using (var session = Domain.OpenSession(clientProfileConfiguration)) {
173+
var sessionToken = session.GetLifetimeToken();
174+
var sessionEntity = new TestEntity(session);
175+
Assert.That(sessionEntity.State.LifetimeToken, Is.EqualTo(sessionToken));
176+
Assert.That(sessionEntity.State.IsActual, Is.True);
177+
Assert.That(sessionEntity.State.LifetimeToken.IsActive, Is.True);
178+
179+
TestEntity outOfScopeEntity;
180+
StateLifetimeToken outOfScopeTxToken;
181+
using (var tx = session.OpenTransaction()) {
182+
var txToken = session.GetLifetimeToken();
183+
Assert.That(txToken.IsActive, Is.True);
184+
Assert.That(sessionToken.IsActive, Is.True);
185+
Assert.That(sessionToken, Is.Not.EqualTo(txToken));
186+
187+
var txEntity = new TestEntity(session);
188+
Assert.That(txEntity.State.LifetimeToken, Is.EqualTo(txToken));
189+
Assert.That(txEntity.State.IsActual, Is.True);
190+
Assert.That(txEntity.State.LifetimeToken.IsActive, Is.True);
191+
192+
outOfScopeEntity = txEntity;
193+
outOfScopeTxToken = txToken;
194+
}
195+
196+
Assert.That(outOfScopeTxToken.IsActive, Is.False);
197+
198+
Assert.That(sessionEntity.State.IsActual, Is.True);
199+
Assert.That(sessionEntity.State.LifetimeToken.IsActive, Is.True);
200+
201+
Assert.That(outOfScopeEntity.State.IsActual, Is.False);
202+
Assert.That(outOfScopeEntity.State.LifetimeToken.IsActive, Is.False);
203+
204+
outOfSessionRef = sessionEntity;
205+
outOfSessionAndTxRef = outOfScopeEntity;
206+
}
207+
208+
Assert.That(outOfSessionRef.State.IsActual, Is.False);
209+
Assert.That(outOfSessionRef.State.LifetimeToken.IsActive, Is.False);
210+
211+
Assert.That(outOfSessionAndTxRef.State.IsActual, Is.False);
212+
Assert.That(outOfSessionAndTxRef.State.LifetimeToken.IsActive, Is.False);
213+
}
214+
215+
216+
[Test]
217+
public void ServerProfileCase1()
218+
{
219+
using (var session = Domain.OpenSession(serverProfileConfiguration)) {
220+
221+
TestEntity outOfScopeEntity;
222+
StateLifetimeToken outOfScopeTxToken;
223+
using (var tx = session.OpenTransaction()) {
224+
var txToken = session.GetLifetimeToken();
225+
Assert.That(txToken.IsActive, Is.True);
226+
227+
var txEntity = new TestEntity(session);
228+
Assert.That(txEntity.State.LifetimeToken, Is.EqualTo(txToken));
229+
Assert.That(txEntity.State.IsActual, Is.True);
230+
Assert.That(txEntity.State.LifetimeToken.IsActive, Is.True);
231+
232+
outOfScopeEntity = txEntity;
233+
outOfScopeTxToken = txToken;
234+
tx.Complete();
235+
}
236+
237+
Assert.That(outOfScopeTxToken.IsActive, Is.False);
238+
239+
Assert.That(outOfScopeEntity.State.IsActual, Is.False);
240+
Assert.That(outOfScopeEntity.State.LifetimeToken.IsActive, Is.False);
241+
}
242+
}
243+
244+
[Test]
245+
public void ServerProfileCase2()
246+
{
247+
using (var session = Domain.OpenSession(serverProfileConfiguration)) {
248+
249+
TestEntity outOfScopeEntity;
250+
StateLifetimeToken outOfScopeTxToken;
251+
using (var tx = session.OpenTransaction()) {
252+
var txToken = session.GetLifetimeToken();
253+
Assert.That(txToken.IsActive, Is.True);
254+
255+
var txEntity = new TestEntity(session);
256+
Assert.That(txEntity.State.LifetimeToken, Is.EqualTo(txToken));
257+
Assert.That(txEntity.State.LifetimeToken.IsActive, Is.True);
258+
Assert.That(txEntity.State.IsActual, Is.True);
259+
260+
outOfScopeEntity = txEntity;
261+
outOfScopeTxToken = txToken;
262+
}
263+
264+
Assert.That(outOfScopeTxToken.IsActive, Is.False);
265+
266+
Assert.That(outOfScopeEntity.State.IsActual, Is.False);
267+
Assert.That(outOfScopeEntity.State.LifetimeToken.IsActive, Is.False);
268+
}
269+
}
270+
271+
[Test]
272+
public void ServerProfileCase3()
273+
{
274+
TestEntity outOfSessionAndTxRef;
275+
using (var session = Domain.OpenSession(serverProfileConfiguration)) {
276+
277+
TestEntity outOfScopeEntity;
278+
StateLifetimeToken outOfScopeTxToken;
279+
using (var tx = session.OpenTransaction()) {
280+
var txToken = session.GetLifetimeToken();
281+
Assert.That(txToken.IsActive, Is.True);
282+
283+
var txEntity = new TestEntity(session);
284+
Assert.That(txEntity.State.LifetimeToken, Is.EqualTo(txToken));
285+
Assert.That(txEntity.State.IsActual, Is.True);
286+
Assert.That(txEntity.State.LifetimeToken.IsActive, Is.True);
287+
288+
outOfScopeEntity = new TestEntity(session);
289+
outOfScopeTxToken = txToken;
290+
tx.Complete();
291+
}
292+
293+
Assert.That(outOfScopeTxToken.IsActive, Is.False);
294+
295+
Assert.That(outOfScopeEntity.State.IsActual, Is.False);
296+
Assert.That(outOfScopeEntity.State.LifetimeToken.IsActive, Is.False);
297+
298+
outOfSessionAndTxRef = outOfScopeEntity;
299+
}
300+
301+
Assert.That(outOfSessionAndTxRef.State.IsActual, Is.False);
302+
Assert.That(outOfSessionAndTxRef.State.LifetimeToken.IsActive, Is.False);
303+
}
304+
305+
[Test]
306+
public void ServerProfileCase4()
307+
{
308+
TestEntity outOfSessionAndTxRef;
309+
using (var session = Domain.OpenSession(serverProfileConfiguration)) {
310+
311+
TestEntity outOfScopeEntity;
312+
StateLifetimeToken outOfScopeTxToken;
313+
using (var tx = session.OpenTransaction()) {
314+
var txToken = session.GetLifetimeToken();
315+
Assert.That(txToken.IsActive, Is.True);
316+
317+
var txEntity = new TestEntity(session);
318+
Assert.That(txEntity.State.LifetimeToken, Is.EqualTo(txToken));
319+
Assert.That(txEntity.State.IsActual, Is.True);
320+
Assert.That(txEntity.State.LifetimeToken.IsActive, Is.True);
321+
322+
outOfScopeEntity = new TestEntity(session);
323+
outOfScopeTxToken = txToken;
324+
}
325+
326+
Assert.That(outOfScopeTxToken.IsActive, Is.False);
327+
328+
Assert.That(outOfScopeEntity.State.IsActual, Is.False);
329+
Assert.That(outOfScopeEntity.State.LifetimeToken.IsActive, Is.False);
330+
331+
outOfSessionAndTxRef = outOfScopeEntity;
332+
}
333+
334+
Assert.That(outOfSessionAndTxRef.State.IsActual, Is.False);
335+
Assert.That(outOfSessionAndTxRef.State.LifetimeToken.IsActive, Is.False);
336+
}
337+
}
338+
}

Orm/Xtensive.Orm/Orm/Session.Transactions.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Created: 2008.11.07
66

77
using System;
8+
using System.Collections.Generic;
89
using System.Transactions;
910
using Xtensive.Orm.Configuration;
1011
using Xtensive.Orm.Internals;
@@ -18,7 +19,8 @@ public partial class Session
1819
{
1920
private const string SavepointNameFormat = "s{0}";
2021

21-
private readonly StateLifetimeToken sessionLifetimeToken = new StateLifetimeToken();
22+
private readonly StateLifetimeToken sessionLifetimeToken;
23+
private readonly List<StateLifetimeToken> promotedLifetimeTokens;
2224
private int nextSavepoint;
2325

2426
/// <summary>
@@ -338,5 +340,14 @@ internal StateLifetimeToken GetLifetimeToken()
338340
return sessionLifetimeToken;
339341
throw new InvalidOperationException(Strings.ExActiveTransactionIsRequiredForThisOperationUseSessionOpenTransactionToOpenIt);
340342
}
343+
344+
internal bool TryPromoteTokens(IEnumerable<StateLifetimeToken> tokens)
345+
{
346+
if (promotedLifetimeTokens is null) {
347+
return false;
348+
}
349+
promotedLifetimeTokens.AddRange(tokens);
350+
return true;
351+
}
341352
}
342353
}

0 commit comments

Comments
 (0)