Hi Alexey,
Here a UnitTest to reproduce the null ref in GetFieldStateMap, actually it's not the bug I wanted to reproduce !! The bug I was having is transaction rollback not properly done but I cannot reproduce it because I have this null ref !
using System;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Xtensive.Orm;
using Xtensive.Orm.Configuration;
namespace UnitTests.Orm
{
[TestClass]
public class DataObjects2
{
private const int MaxEntities = 10;
private Domain domain;
[TestMethod, TestCategory("SessionOptions.NotTransactionRead")]
public void TestTransactionRollbackOnDeadLock()
{
domain = CreateDomain(SessionOptions.ValidateEntities | SessionOptions.AutoActivation | SessionOptions.AutoSaveChanges | SessionOptions.NonTransactionalReads);
CreateEntities();
Task task1 = Task.Factory.StartNew(() => UpdateEntities(1));
Task task2 = Task.Factory.StartNew(() => UpdateEntities(2));
Task.WaitAll(task1, task2);
domain.Dispose();
}
private void CreateEntities()
{
using (Session session = domain.OpenSession())
{
using (TransactionScope t = session.OpenTransaction())
{
for (int i = 0; i < MaxEntities; i++)
{
new TestEntity() {Index = i, Value=1};
}
t.Complete();
}
}
using (Session session = domain.OpenSession())
{
using (TransactionScope t = session.OpenTransaction())
{
Assert.IsTrue(Query.All<TestEntity>().Count() == MaxEntities);
}
}
}
private void UpdateEntities(int instanceId)
{
for (int i = 0; i < MaxEntities; i++)
{
using (Session session = domain.OpenSession())
{
int retryCount = 3;
for (int retry = 0;; retry++)
{
int initialValue = 0;
try
{
initialValue = GetEntityValue(session, i);
UpdateEntity(session, i);
break;
}
catch (Exception ex)
{
if (ex is DeadlockException ||
ex is TransactionSerializationFailureException ||
(ex is TargetInvocationException && (ex.InnerException is DeadlockException || ex.InnerException is TransactionSerializationFailureException)))
{
if (retry + 1 < retryCount)
{
Debug.WriteLine("Deadlock detected : retrying transactional method for UpdateEntities({0}, {1})", instanceId, i);
int currentValue = GetEntityValue(session, i);
if (currentValue != initialValue)
{
Debug.WriteLine("Deadlock detected : retrying transactional method for UpdateEntities({0}, {1})", instanceId, i);
}
continue;
}
else
{
Debug.WriteLine("Deadlock detected on last try : giving up on UpdateEntities2({0})", i);
throw;
}
}
else
{
throw;
}
}
}
}
}
}
private int GetEntityValue(Session session, int i)
{
int initialValue;
using (TransactionScope t = session.OpenTransaction())
{
TestEntity entity = Query.All<TestEntity>().Single(e => e.Index == i);
initialValue = entity.Value;
t.Complete();
}
return initialValue;
}
private void UpdateEntity(Session session, int i)
{
using (TransactionScope t = session.OpenTransaction())
{
TestEntity entity = Query.All<TestEntity>().Single(e => e.Index == i);
int initialValue = entity.Value;
entity.Value ++;
Debug.WriteLine("Updated entity {0} value from {1} to {2}", i, initialValue, entity.Value);
t.Complete(); // rollback
}
}
private Domain CreateDomain(SessionOptions sessionOptions)
{
string connectionString = Configuration.Instance.DataObjectsConnectionString;
DomainConfiguration config = new DomainConfiguration(connectionString);
config.UpgradeMode = DomainUpgradeMode.Recreate;
config.Types.Register(typeof(TestEntity));
SessionConfiguration defaultSessionConfig = config.Sessions.Default;
if (defaultSessionConfig == null)
{
defaultSessionConfig = new SessionConfiguration(WellKnown.Sessions.Default);
config.Sessions.Add(defaultSessionConfig);
}
defaultSessionConfig.Options = sessionOptions;
ConnectionInfo connectionInfo = new ConnectionInfo(connectionString);
string sqlConnectionString = new Xtensive.Sql.Drivers.SqlServer.DriverFactory().GetConnectionString(connectionInfo);
using (System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection(sqlConnectionString))
using (DbCommand command = connection.CreateCommand())
{
connection.Open();
command.CommandText = "Select * From sys.fulltext_catalogs";
bool ftCatalogExists = false;
using (DbDataReader reader = command.ExecuteReader())
{
ftCatalogExists = reader.HasRows;
}
if (!ftCatalogExists)
{
// full text catalog does not exists : creates it
command.CommandText = "CREATE FULLTEXT CATALOG [Default] " + "WITH ACCENT_SENSITIVITY = ON " + "AS DEFAULT " + "AUTHORIZATION [dbo]";
command.ExecuteNonQuery();
command.CommandText = "ALTER FULLTEXT CATALOG [Default] REBUILD ";
command.ExecuteNonQuery();
}
}
return Domain.Build(config);
}
[Serializable]
[HierarchyRoot]
public class TestEntity : Entity
{
[Field, Key]
public long Id { get; private set; }
[Field]
public int Index { get; set; }
[Field]
public int Value { get; set; }
}
}
}
answered
Sep 21 '16 at 09:05
Benoit Nesme
43●20●20●24
I have not been able to reproduce it with a sample yet. When I can say when debugging using DotPeek is that DifferentialTuple.Difference is null which leads to the null reference in GetFieldStateMap.