Skip to content

Commit d25d4ce

Browse files
Prevent concurrent BsonClassMap registrations (#812)
* Replace Mongo RegisterClassMap * Simplify RegisterOutboxClassMappings code * Use lock instead of reflection in RegisterSagaEntityClassMappings --------- Co-authored-by: Brandon Ording <bording@gmail.com>
1 parent 8aa9189 commit d25d4ce

File tree

2 files changed

+41
-41
lines changed

2 files changed

+41
-41
lines changed

src/NServiceBus.Storage.MongoDB/Outbox/OutboxStorage.cs

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -57,53 +57,44 @@ internal static bool RegisterOutboxClassMappings()
5757
// If any of the class maps are already registered, then we assume that the user has provided their own custom class maps
5858
// and treat the entire tree as custom.
5959
var usesDefaultClassMap = true;
60-
if (!BsonClassMap.IsClassMapRegistered(typeof(OutboxRecordId)))
61-
{
62-
BsonClassMap.RegisterClassMap<OutboxRecordId>(cm =>
63-
{
64-
cm.AutoMap();
65-
cm.MapMember(x => x.PartitionKey).SetElementName("pk");
66-
cm.MapMember(x => x.MessageId).SetElementName("mid");
67-
});
68-
}
69-
else
60+
61+
if (!TryRegisterOutboxRecordId())
7062
{
7163
usesDefaultClassMap = false;
7264
}
7365

74-
if (!BsonClassMap.IsClassMapRegistered(typeof(OutboxRecord)))
75-
{
76-
BsonClassMap.RegisterClassMap<OutboxRecord>(cm =>
77-
{
78-
cm.AutoMap();
79-
cm.MapIdMember(x => x.Id).SetSerializer(new OutboxRecordIdSerializer());
80-
});
81-
}
82-
else
66+
if (!TryRegisterOutboxRecord())
8367
{
8468
usesDefaultClassMap = false;
8569
}
8670

87-
if (!BsonClassMap.IsClassMapRegistered(typeof(StorageTransportOperation)))
88-
{
89-
BsonClassMap.RegisterClassMap<StorageTransportOperation>(cm =>
90-
{
91-
cm.AutoMap();
92-
cm.MapMember(c => c.Headers)
93-
.SetSerializer(
94-
new DictionaryInterfaceImplementerSerializer<Dictionary<string, string>>(
95-
DictionaryRepresentation.ArrayOfDocuments));
96-
cm.MapMember(c => c.Options)
97-
.SetSerializer(
98-
new DictionaryInterfaceImplementerSerializer<Dictionary<string, string>>(
99-
DictionaryRepresentation.ArrayOfDocuments));
100-
});
101-
}
102-
else
71+
if (!TryRegisterStorageTransportOperation())
10372
{
10473
usesDefaultClassMap = false;
10574
}
10675

10776
return usesDefaultClassMap;
10877
}
78+
79+
static bool TryRegisterOutboxRecordId() => BsonClassMap.TryRegisterClassMap<OutboxRecordId>(cm =>
80+
{
81+
cm.AutoMap();
82+
cm.MapMember(x => x.PartitionKey).SetElementName("pk");
83+
cm.MapMember(x => x.MessageId).SetElementName("mid");
84+
});
85+
86+
static bool TryRegisterOutboxRecord() => BsonClassMap.TryRegisterClassMap<OutboxRecord>(cm =>
87+
{
88+
cm.AutoMap();
89+
cm.MapIdMember(x => x.Id).SetSerializer(new OutboxRecordIdSerializer());
90+
});
91+
92+
static bool TryRegisterStorageTransportOperation() => BsonClassMap.TryRegisterClassMap<StorageTransportOperation>(cm =>
93+
{
94+
cm.AutoMap();
95+
cm.MapMember(c => c.Headers)
96+
.SetSerializer(new DictionaryInterfaceImplementerSerializer<Dictionary<string, string>>(DictionaryRepresentation.ArrayOfDocuments));
97+
cm.MapMember(c => c.Options)
98+
.SetSerializer(new DictionaryInterfaceImplementerSerializer<Dictionary<string, string>>(DictionaryRepresentation.ArrayOfDocuments));
99+
});
109100
}

src/NServiceBus.Storage.MongoDB/Sagas/SagaStorage.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace NServiceBus.Storage.MongoDB;
22

33
using System.Collections.Generic;
4+
using System.Threading;
45
using Features;
56
using global::MongoDB.Bson.Serialization;
67
using Microsoft.Extensions.DependencyInjection;
@@ -45,18 +46,23 @@ internal readonly struct MappingMetadata(string sagaEntity, bool usesDefaultClas
4546
internal static IReadOnlyCollection<MappingMetadata> RegisterSagaEntityClassMappings(SagaMetadataCollection sagaMetadataCollection, MemberMapCache memberMapCache)
4647
{
4748
var sagaEntityToClassMapDiagnostics = new List<MappingMetadata>();
49+
4850
foreach (var sagaMetadata in sagaMetadataCollection)
4951
{
5052
var usesDefaultClassMap = false;
51-
if (!BsonClassMap.IsClassMapRegistered(sagaMetadata.SagaEntityType))
53+
54+
lock (classMapLock)
5255
{
53-
var classMap = new BsonClassMap(sagaMetadata.SagaEntityType);
54-
classMap.AutoMap();
55-
classMap.SetIgnoreExtraElements(true);
56+
if (!BsonClassMap.IsClassMapRegistered(sagaMetadata.SagaEntityType))
57+
{
58+
var classMap = new BsonClassMap(sagaMetadata.SagaEntityType);
59+
classMap.AutoMap();
60+
classMap.SetIgnoreExtraElements(true);
5661

57-
BsonClassMap.RegisterClassMap(classMap);
62+
BsonClassMap.RegisterClassMap(classMap);
5863

59-
usesDefaultClassMap = true;
64+
usesDefaultClassMap = true;
65+
}
6066
}
6167

6268
sagaEntityToClassMapDiagnostics.Add(new(sagaMetadata.SagaEntityType.FullName!, usesDefaultClassMap));
@@ -66,6 +72,9 @@ internal static IReadOnlyCollection<MappingMetadata> RegisterSagaEntityClassMapp
6672
_ = memberMapCache.GetOrAdd(sagaMetadata.SagaEntityType, property);
6773
}
6874
}
75+
6976
return sagaEntityToClassMapDiagnostics;
7077
}
78+
79+
static readonly Lock classMapLock = new();
7180
}

0 commit comments

Comments
 (0)