Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 7484ca7

Browse files
committed
Fix threading issue in ModelDefinition
1 parent 88a46f1 commit 7484ca7

File tree

4 files changed

+106
-43
lines changed

4 files changed

+106
-43
lines changed

src/ServiceStack.OrmLite/ModelDefinition.cs

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -62,76 +62,56 @@ public string ModelName
6262

6363
public FieldDefinition PrimaryKey
6464
{
65-
get
66-
{
67-
return this.FieldDefinitions.First(x => x.IsPrimaryKey);
68-
}
65+
get { return this.FieldDefinitions.First(x => x.IsPrimaryKey); }
6966
}
7067

7168
public List<FieldDefinition> FieldDefinitions { get; set; }
7269

7370
private FieldDefinition[] fieldDefinitionsArray;
7471
public FieldDefinition[] FieldDefinitionsArray
7572
{
76-
get
77-
{
78-
if (fieldDefinitionsArray == null)
79-
{
80-
fieldDefinitionsArray = FieldDefinitions.ToArray();
81-
}
82-
return fieldDefinitionsArray;
83-
}
73+
get { return fieldDefinitionsArray; }
8474
}
8575

8676
public List<FieldDefinition> IgnoredFieldDefinitions { get; set; }
8777

8878
private FieldDefinition[] ignoredFieldDefinitionsArray;
8979
public FieldDefinition[] IgnoredFieldDefinitionsArray
9080
{
91-
get
92-
{
93-
if (ignoredFieldDefinitionsArray == null)
94-
{
95-
ignoredFieldDefinitionsArray = IgnoredFieldDefinitions.ToArray();
96-
}
97-
return ignoredFieldDefinitionsArray;
98-
}
81+
get { return ignoredFieldDefinitionsArray; }
9982
}
10083

10184
private FieldDefinition[] allFieldDefinitionsArray;
10285
public FieldDefinition[] AllFieldDefinitionsArray
10386
{
104-
get
105-
{
106-
if (allFieldDefinitionsArray == null)
107-
{
108-
List<FieldDefinition> allItems = new List<FieldDefinition>(FieldDefinitions);
109-
allItems.AddRange(IgnoredFieldDefinitions);
110-
allFieldDefinitionsArray = allItems.ToArray();
111-
}
112-
return allFieldDefinitionsArray;
113-
}
87+
get { return allFieldDefinitionsArray; }
11488
}
11589

90+
private readonly object fieldDefLock = new object();
11691
private Dictionary<string, FieldDefinition> fieldDefinitionMap;
11792
private Func<string, string> fieldNameSanitizer;
11893
public Dictionary<string, FieldDefinition> GetFieldDefinitionMap(Func<string, string> sanitizeFieldName)
11994
{
12095
if (fieldDefinitionMap == null || fieldNameSanitizer != sanitizeFieldName)
12196
{
122-
fieldNameSanitizer = sanitizeFieldName;
123-
fieldDefinitionMap = new Dictionary<string, FieldDefinition>();
124-
foreach (var fieldDef in FieldDefinitionsArray)
97+
lock (fieldDefLock)
12598
{
126-
fieldDefinitionMap[sanitizeFieldName(fieldDef.FieldName)] = fieldDef;
99+
if (fieldDefinitionMap == null || fieldNameSanitizer != sanitizeFieldName)
100+
{
101+
fieldDefinitionMap = new Dictionary<string, FieldDefinition>();
102+
fieldNameSanitizer = sanitizeFieldName;
103+
foreach (var fieldDef in FieldDefinitionsArray)
104+
{
105+
fieldDefinitionMap[sanitizeFieldName(fieldDef.FieldName)] = fieldDef;
106+
}
107+
}
127108
}
128109
}
129110
return fieldDefinitionMap;
130111
}
131112

132113
public List<CompositeIndexAttribute> CompositeIndexes { get; set; }
133114

134-
135115
public FieldDefinition GetFieldDefinition<T>(Expression<Func<T, object>> field)
136116
{
137117
var fn = GetFieldName(field);
@@ -147,13 +127,24 @@ string GetFieldName<T>(Expression<Func<T, object>> field)
147127
var me = lambda.Body as MemberExpression;
148128
return me.Member.Name;
149129
}
150-
else
151-
{
152-
var operand = (lambda.Body as UnaryExpression).Operand;
153-
return (operand as MemberExpression).Member.Name;
154-
}
130+
131+
var operand = (lambda.Body as UnaryExpression).Operand;
132+
return (operand as MemberExpression).Member.Name;
155133
}
156134

135+
public void AfterInit()
136+
{
137+
fieldDefinitionsArray = FieldDefinitions.ToArray();
138+
ignoredFieldDefinitionsArray = IgnoredFieldDefinitions.ToArray();
139+
140+
var allItems = new List<FieldDefinition>(FieldDefinitions);
141+
allItems.AddRange(IgnoredFieldDefinitions);
142+
allFieldDefinitionsArray = allItems.ToArray();
143+
144+
SqlSelectAllFromTable = "SELECT {0} FROM {1} "
145+
.Fmt(OrmLiteConfig.DialectProvider.GetColumnNames(this),
146+
OrmLiteConfig.DialectProvider.GetQuotedTableName(this));
147+
}
157148
}
158149

159150

src/ServiceStack.OrmLite/OrmLiteConfigExtensions.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,7 @@ internal static ModelDefinition GetModelDefinition(this Type modelType)
189189
modelDef.RowVersion = fieldDefinition;
190190
}
191191

192-
modelDef.SqlSelectAllFromTable = "SELECT {0} FROM {1} "
193-
.Fmt(OrmLiteConfig.DialectProvider.GetColumnNames(modelDef),
194-
OrmLiteConfig.DialectProvider.GetQuotedTableName(modelDef));
192+
modelDef.AfterInit();
195193

196194
Dictionary<Type, ModelDefinition> snapshot, newCache;
197195
do
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading;
4+
using NUnit.Framework;
5+
using ServiceStack.Text;
6+
7+
namespace ServiceStack.OrmLite.Tests.Issues
8+
{
9+
[Explicit]
10+
[TestFixture]
11+
public class MultithreadingIssueTests
12+
: OrmLiteTestBase
13+
{
14+
[SetUp]
15+
public void SetUp()
16+
{
17+
using (var db = OpenDbConnection())
18+
{
19+
db.DropAndCreateTable<ModelWithDifferentNumTypes>();
20+
}
21+
}
22+
23+
[Test]
24+
public void Can_SaveAll_in_multiple_threads()
25+
{
26+
var errors = new List<string>();
27+
int threads = 0;
28+
var mainLock = new object();
29+
30+
10.Times(() => {
31+
ThreadPool.QueueUserWorkItem(_ => {
32+
33+
Interlocked.Increment(ref threads);
34+
var threadId = Thread.CurrentThread.ManagedThreadId;
35+
"Thread {0} started...".Print(threadId);
36+
37+
var rows = 10.Times(i => ModelWithDifferentNumTypes.Create(i));
38+
using (var db = OpenDbConnection())
39+
{
40+
100.Times(i =>
41+
{
42+
try
43+
{
44+
db.SaveAll(rows);
45+
}
46+
catch (Exception ex)
47+
{
48+
lock (errors)
49+
errors.Add(ex.Message + "\nStackTrace:\n" + ex.StackTrace);
50+
}
51+
});
52+
}
53+
54+
"Thread {0} finished".Print(threadId);
55+
if (Interlocked.Decrement(ref threads) == 0)
56+
{
57+
"All Threads Finished".Print();
58+
lock (mainLock)
59+
Monitor.Pulse(mainLock);
60+
}
61+
});
62+
});
63+
64+
lock (mainLock)
65+
Monitor.Wait(mainLock);
66+
67+
"Stopping...".Print();
68+
errors.PrintDump();
69+
70+
Assert.That(errors.Count, Is.EqualTo(0));
71+
}
72+
}
73+
}

tests/ServiceStack.OrmLite.Tests/ServiceStack.OrmLite.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
<Compile Include="Expression\SqlExpressionTests.cs" />
125125
<Compile Include="Expression\SelectExpressionTests.cs" />
126126
<Compile Include="Issues\MismatchSchemaTests.cs" />
127+
<Compile Include="Issues\MultithreadingIssueTests.cs" />
127128
<Compile Include="LicenseUsageTests.cs" />
128129
<Compile Include="LoadReferencesJoinTests.cs" />
129130
<Compile Include="LoadReferencesTests.cs" />

0 commit comments

Comments
 (0)