Skip to content

Commit 4efb3a9

Browse files
Daniel Hegenerdnickless
authored andcommitted
Reduced the lock contention in BsonSerializer.LookupActualType which is on the hot path during deserialization by switching to the (unfortunately not generic) Hashtable type instead of a HashSet<T>. Hashtable is safe for concurrent reads (as per https://docs.microsoft.com/en-us/dotnet/api/system.collections.hashtable?redirectedfrom=MSDN&view=netframework-4.7.2#thread-safety) and the amount of casting introduced is very small. The HashSet<T> instances that are tracked inside the Hashtable get replaced instead of updated upon a write which allows consumers to safely iterate over them.
1 parent add2c25 commit 4efb3a9

File tree

1 file changed

+66
-67
lines changed

1 file changed

+66
-67
lines changed

src/MongoDB.Bson/Serialization/BsonSerializer.cs

Lines changed: 66 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
*/
1515

1616
using System;
17+
using System.Collections;
1718
using System.Collections.Generic;
1819
using System.IO;
19-
using System.Linq;
2020
using System.Reflection;
2121
using System.Threading;
2222

@@ -25,7 +25,6 @@
2525
using MongoDB.Bson.Serialization.Attributes;
2626
using MongoDB.Bson.Serialization.Conventions;
2727
using MongoDB.Bson.Serialization.IdGenerators;
28-
using MongoDB.Bson.Serialization.Serializers;
2928

3029
namespace MongoDB.Bson.Serialization
3130
{
@@ -38,11 +37,11 @@ public static class BsonSerializer
3837
private static ReaderWriterLockSlim __configLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
3938
private static Dictionary<Type, IIdGenerator> __idGenerators = new Dictionary<Type, IIdGenerator>();
4039
private static Dictionary<Type, IDiscriminatorConvention> __discriminatorConventions = new Dictionary<Type, IDiscriminatorConvention>();
41-
private static Dictionary<BsonValue, HashSet<Type>> __discriminators = new Dictionary<BsonValue, HashSet<Type>>();
42-
private static HashSet<Type> __discriminatedTypes = new HashSet<Type>();
40+
private static Hashtable __discriminators = new Hashtable(EqualityComparer<BsonValue>.Default); // effectively a Dictionary<BsonValue, HashSet<Type>>
41+
private static Hashtable __discriminatedTypes = new Hashtable();
4342
private static BsonSerializerRegistry __serializerRegistry;
4443
private static TypeMappingSerializationProvider __typeMappingSerializationProvider;
45-
private static HashSet<Type> __typesWithRegisteredKnownTypes = new HashSet<Type>();
44+
private static Hashtable __typesWithRegisteredKnownTypes = new Hashtable(EqualityComparer<Type>.Default);
4645

4746
private static bool __useNullIdChecker = false;
4847
private static bool __useZeroIdChecker = false;
@@ -276,7 +275,7 @@ public static object Deserialize(TextReader textReader, Type nominalType, Action
276275
public static bool IsTypeDiscriminated(Type type)
277276
{
278277
var typeInfo = type.GetTypeInfo();
279-
return typeInfo.IsInterface || __discriminatedTypes.Contains(type);
278+
return typeInfo.IsInterface || __discriminatedTypes.ContainsKey(type);
280279
}
281280

282281
/// <summary>
@@ -295,56 +294,49 @@ public static Type LookupActualType(Type nominalType, BsonValue discriminator)
295294
// note: EnsureKnownTypesAreRegistered handles its own locking so call from outside any lock
296295
EnsureKnownTypesAreRegistered(nominalType);
297296

298-
__configLock.EnterReadLock();
299-
try
300-
{
301-
Type actualType = null;
297+
Type actualType = null;
302298

303-
HashSet<Type> hashSet;
304-
if (__discriminators.TryGetValue(discriminator, out hashSet))
299+
object hashSetAsObject = __discriminators[discriminator];
300+
if (hashSetAsObject != null)
301+
{
302+
HashSet<Type> hashSet = (HashSet<Type>)__discriminators[discriminator];
303+
foreach (var type in hashSet)
305304
{
306-
foreach (var type in hashSet)
305+
if (nominalType.GetTypeInfo().IsAssignableFrom(type))
307306
{
308-
if (nominalType.GetTypeInfo().IsAssignableFrom(type))
307+
if (actualType == null)
309308
{
310-
if (actualType == null)
311-
{
312-
actualType = type;
313-
}
314-
else
315-
{
316-
string message = string.Format("Ambiguous discriminator '{0}'.", discriminator);
317-
throw new BsonSerializationException(message);
318-
}
309+
actualType = type;
310+
}
311+
else
312+
{
313+
string message = string.Format("Ambiguous discriminator '{0}'.", discriminator);
314+
throw new BsonSerializationException(message);
319315
}
320316
}
321317
}
318+
}
322319

323-
if (actualType == null && discriminator.IsString)
324-
{
325-
actualType = TypeNameDiscriminator.GetActualType(discriminator.AsString); // see if it's a Type name
326-
}
327-
328-
if (actualType == null)
329-
{
330-
string message = string.Format("Unknown discriminator value '{0}'.", discriminator);
331-
throw new BsonSerializationException(message);
332-
}
333-
334-
if (!nominalType.GetTypeInfo().IsAssignableFrom(actualType))
335-
{
336-
string message = string.Format(
337-
"Actual type {0} is not assignable to expected type {1}.",
338-
actualType.FullName, nominalType.FullName);
339-
throw new BsonSerializationException(message);
340-
}
320+
if (actualType == null && discriminator.IsString)
321+
{
322+
actualType = TypeNameDiscriminator.GetActualType(discriminator.AsString); // see if it's a Type name
323+
}
341324

342-
return actualType;
325+
if (actualType == null)
326+
{
327+
string message = string.Format("Unknown discriminator value '{0}'.", discriminator);
328+
throw new BsonSerializationException(message);
343329
}
344-
finally
330+
331+
if (!nominalType.GetTypeInfo().IsAssignableFrom(actualType))
345332
{
346-
__configLock.ExitReadLock();
333+
string message = string.Format(
334+
"Actual type {0} is not assignable to expected type {1}.",
335+
actualType.FullName, nominalType.FullName);
336+
throw new BsonSerializationException(message);
347337
}
338+
339+
return actualType;
348340
}
349341

350342
/// <summary>
@@ -504,6 +496,17 @@ public static IBsonSerializer LookupSerializer(Type type)
504496
return __serializerRegistry.GetSerializer(type);
505497
}
506498

499+
private static void PopulateHashSet(HashSet<Type> hashSet, Type type)
500+
{
501+
hashSet.Add(type);
502+
503+
// mark all base types as discriminated (so we know that it's worth reading a discriminator)
504+
for (var baseType = type.GetTypeInfo().BaseType; baseType != null; baseType = baseType.GetTypeInfo().BaseType)
505+
{
506+
__discriminatedTypes[baseType] = null /* we don't care about the value */;
507+
}
508+
}
509+
507510
/// <summary>
508511
/// Registers the discriminator for a type.
509512
/// </summary>
@@ -519,23 +522,27 @@ public static void RegisterDiscriminator(Type type, BsonValue discriminator)
519522
}
520523

521524
__configLock.EnterWriteLock();
525+
522526
try
523527
{
524-
HashSet<Type> hashSet;
525-
if (!__discriminators.TryGetValue(discriminator, out hashSet))
528+
object hashSetAsObject = __discriminators[discriminator];
529+
if (hashSetAsObject == null)
526530
{
527-
hashSet = new HashSet<Type>();
528-
__discriminators.Add(discriminator, hashSet);
531+
// straight forward: discriminator was unknown so we just add a new hash set
532+
HashSet<Type> hashSet = new HashSet<Type>();
533+
PopulateHashSet(hashSet, type);
534+
__discriminators[discriminator] = hashSet;
529535
}
530-
531-
if (!hashSet.Contains(type))
536+
else
532537
{
533-
hashSet.Add(type);
534-
535-
// mark all base types as discriminated (so we know that it's worth reading a discriminator)
536-
for (var baseType = type.GetTypeInfo().BaseType; baseType != null; baseType = baseType.GetTypeInfo().BaseType)
538+
HashSet<Type> hashSet = (HashSet<Type>)hashSetAsObject;
539+
if (!hashSet.Contains(type))
537540
{
538-
__discriminatedTypes.Add(baseType);
541+
// an existing hash set was there so we duplicate it and replace the old one in the cache with a new instance
542+
// so that consumers can safely iterate over it because we don't modify the same instance
543+
hashSet = new HashSet<Type>(hashSet);
544+
PopulateHashSet(hashSet, type);
545+
__discriminators[discriminator] = hashSet;
539546
}
540547
}
541548
}
@@ -672,23 +679,15 @@ public static void Serialize(
672679
// internal static methods
673680
internal static void EnsureKnownTypesAreRegistered(Type nominalType)
674681
{
675-
__configLock.EnterReadLock();
676-
try
682+
if (__typesWithRegisteredKnownTypes.ContainsKey(nominalType))
677683
{
678-
if (__typesWithRegisteredKnownTypes.Contains(nominalType))
679-
{
680-
return;
681-
}
682-
}
683-
finally
684-
{
685-
__configLock.ExitReadLock();
684+
return;
686685
}
687686

688687
__configLock.EnterWriteLock();
689688
try
690689
{
691-
if (!__typesWithRegisteredKnownTypes.Contains(nominalType))
690+
if (!__typesWithRegisteredKnownTypes.ContainsKey(nominalType))
692691
{
693692
// only call LookupClassMap for classes with a BsonKnownTypesAttribute
694693
#if NET452
@@ -702,7 +701,7 @@ internal static void EnsureKnownTypesAreRegistered(Type nominalType)
702701
LookupSerializer(nominalType);
703702
}
704703

705-
__typesWithRegisteredKnownTypes.Add(nominalType);
704+
__typesWithRegisteredKnownTypes[nominalType] = null /* we don't care about the value */;
706705
}
707706
}
708707
finally

0 commit comments

Comments
 (0)