Skip to content

Commit 91f6c68

Browse files
authored
Merge pull request #2569 from JKamsker/bugs/concurrent-access#2
Fix potential concurrency issue in GetEntityMapper caused by #2566
2 parents ed10cc3 + 2cec865 commit 91f6c68

File tree

3 files changed

+76
-5
lines changed

3 files changed

+76
-5
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.IO;
2+
using Xunit;
3+
4+
namespace LiteDB.Tests.Database;
5+
6+
public class Writing_While_Reading_Test
7+
{
8+
[Fact]
9+
public void Test()
10+
{
11+
using var f = new TempFile();
12+
using (var db = new LiteDatabase(f.Filename))
13+
{
14+
var col = db.GetCollection<MyClass>("col");
15+
col.Insert(new MyClass { Name = "John", Description = "Doe" });
16+
col.Insert(new MyClass { Name = "Joana", Description = "Doe" });
17+
col.Insert(new MyClass { Name = "Doe", Description = "Doe" });
18+
}
19+
20+
21+
using (var db = new LiteDatabase(f.Filename))
22+
{
23+
var col = db.GetCollection<MyClass>("col");
24+
foreach (var item in col.FindAll())
25+
{
26+
item.Description += " Changed";
27+
col.Update(item);
28+
}
29+
30+
db.Commit();
31+
}
32+
33+
34+
using (var db = new LiteDatabase(f.Filename))
35+
{
36+
var col = db.GetCollection<MyClass>("col");
37+
foreach (var item in col.FindAll())
38+
{
39+
Assert.EndsWith("Changed", item.Description);
40+
}
41+
}
42+
}
43+
44+
class MyClass
45+
{
46+
public int Id { get; set; }
47+
48+
public string Name { get; set; }
49+
50+
public string Description { get; set; }
51+
}
52+
}

LiteDB/Client/Mapper/BsonMapper.GetEntityMapper.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,29 @@ internal EntityMapper GetEntityMapper(Type type)
2525
}
2626

2727
using var cts = new CancellationTokenSource();
28-
mapper = new EntityMapper(type, cts.Token);
29-
if (_entities.TryAdd(type, mapper))
28+
try
3029
{
31-
this.BuildEntityMapper(mapper);
30+
// We need to add the empty shell, because ``BuildEntityMapper`` may use this method recursively
31+
mapper = new EntityMapper(type, cts.Token);
32+
EntityMapper addedMapper = _entities.GetOrAdd(type, mapper);
33+
if (ReferenceEquals(addedMapper, mapper))
34+
{
35+
try
36+
{
37+
this.BuildEntityMapper(mapper);
38+
}
39+
catch (Exception ex)
40+
{
41+
_entities.TryRemove(type, out _);
42+
throw new LiteException(LiteException.MAPPING_ERROR, $"Error in '{type.Name}' mapping: {ex.Message}", ex);
43+
}
44+
}
45+
}
46+
finally
47+
{
48+
// Allow the Mapper to be used for de-/serialization
49+
cts.Cancel();
3250
}
33-
cts.Cancel();
34-
cts.Dispose();
3551

3652
return mapper;
3753
}

LiteDB/Utils/LiteException.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public class LiteException : Exception
5858
public const int INVALID_PASSWORD = 217;
5959
public const int ILLEGAL_DESERIALIZATION_TYPE = 218;
6060
public const int ENTITY_INITIALIZATION_FAILED = 219;
61+
public const int MAPPER_NOT_FOUND = 220;
62+
public const int MAPPING_ERROR = 221;
63+
6164

6265
public const int INVALID_DATAFILE_STATE = 999;
6366

0 commit comments

Comments
 (0)