Skip to content

Commit 9e3461f

Browse files
committed
Fix pre-existing static constructor race
1 parent b20d117 commit 9e3461f

1 file changed

Lines changed: 30 additions & 10 deletions

File tree

src/Npgsql/TypeMapping/GlobalTypeMapper.cs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,31 @@ internal IEnumerable<PgTypeInfoResolverFactory> GetPluginResolverFactories()
4848

4949
internal void AddGlobalTypeMappingResolvers(PgTypeInfoResolverFactory[] factories, Func<PgTypeInfoResolverChainBuilder>? builderFactory = null, bool overwrite = false)
5050
{
51-
// Good enough logic to prevent SlimBuilder overriding the normal Builder.
52-
if (overwrite || factories.Length > _typeMappingResolvers.Length)
51+
_lock.EnterWriteLock();
52+
try
5353
{
54-
_builderFactory = builderFactory;
55-
_typeMappingResolvers = factories;
56-
ResetTypeMappingCache();
54+
// Prevent SlimBuilder from overriding the normal Builder.
55+
if (overwrite || factories.Length > _typeMappingResolvers.Length)
56+
{
57+
_builderFactory = builderFactory;
58+
_typeMappingResolvers = factories;
59+
ResetTypeMappingCache();
60+
}
61+
}
62+
finally
63+
{
64+
_lock.ExitWriteLock();
5765
}
5866
}
5967

60-
void ResetTypeMappingCache() => _typeMappingOptions = null;
68+
void ResetTypeMappingCache()
69+
{
70+
Interlocked.Increment(ref _typeMappingVersion);
71+
_typeMappingOptions = null;
72+
}
6173

74+
int _typeMappingVersion;
75+
int _typeMappingCachedVersion = -1;
6276
PgSerializerOptions? _typeMappingOptions;
6377
Func<PgTypeInfoResolverChainBuilder>? _builderFactory;
6478
JsonSerializerOptions? _jsonSerializerOptions;
@@ -67,19 +81,25 @@ PgSerializerOptions TypeMappingOptions
6781
{
6882
get
6983
{
70-
if (_typeMappingOptions is not null)
71-
return _typeMappingOptions;
84+
var version = Volatile.Read(ref _typeMappingVersion);
85+
if (_typeMappingOptions is { } options && _typeMappingCachedVersion == version)
86+
return options;
7287

73-
_lock.EnterReadLock();
88+
_lock.EnterWriteLock();
7489
try
7590
{
91+
version = _typeMappingVersion;
92+
if (_typeMappingOptions is not null && _typeMappingCachedVersion == version)
93+
return _typeMappingOptions;
94+
7695
var builder = _builderFactory?.Invoke() ?? new();
7796
builder.AppendResolverFactory(_userTypeMapper);
7897
foreach (var factory in _pluginResolverFactories)
7998
builder.AppendResolverFactory(factory);
8099
foreach (var factory in _typeMappingResolvers)
81100
builder.AppendResolverFactory(factory);
82101
var chain = builder.Build();
102+
_typeMappingCachedVersion = version;
83103
return _typeMappingOptions = new(PostgresMinimalDatabaseInfo.DefaultTypeCatalog, chain)
84104
{
85105
// This means we don't ever have a missing oid for a datatypename as our canonical format is datatypenames.
@@ -90,7 +110,7 @@ PgSerializerOptions TypeMappingOptions
90110
}
91111
finally
92112
{
93-
_lock.ExitReadLock();
113+
_lock.ExitWriteLock();
94114
}
95115
}
96116
}

0 commit comments

Comments
 (0)