Skip to content

Commit 9f4f783

Browse files
authored
Mark AddTypeHandlerImpl as obsolete and prevent lost updates via AddTypeHandler (#2129)
* - mark AddTypeHandlerImpl as obsolete - syncronize type-handler mutates, to prevent lost writes * CI: revert pgsql uddate * suppress Impl's use of clone: false * avoid additional local snapshot, now that we're synchronized * explicitly request postgresql96 * more pgsql poking * pg path fix
1 parent 00a3808 commit 9f4f783

File tree

2 files changed

+48
-28
lines changed

2 files changed

+48
-28
lines changed

Dapper/SqlMapper.cs

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,14 @@ static SqlMapper()
266266
[MemberNotNull(nameof(typeHandlers))]
267267
private static void ResetTypeHandlers(bool clone)
268268
{
269-
typeHandlers = [];
270-
AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone);
271-
AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone);
272-
AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone);
273-
AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone);
269+
lock (typeHandlersSyncLock)
270+
{
271+
typeHandlers = [];
272+
AddTypeHandlerCore(typeof(DataTable), new DataTableHandler(), clone);
273+
AddTypeHandlerCore(typeof(XmlDocument), new XmlDocumentHandler(), clone);
274+
AddTypeHandlerCore(typeof(XDocument), new XDocumentHandler(), clone);
275+
AddTypeHandlerCore(typeof(XElement), new XElementHandler(), clone);
276+
}
274277
}
275278

276279
/// <summary>
@@ -339,7 +342,7 @@ public static void RemoveTypeMap(Type type)
339342
/// </summary>
340343
/// <param name="type">The type to handle.</param>
341344
/// <param name="handler">The handler to process the <paramref name="type"/>.</param>
342-
public static void AddTypeHandler(Type type, ITypeHandler handler) => AddTypeHandlerImpl(type, handler, true);
345+
public static void AddTypeHandler(Type type, ITypeHandler handler) => AddTypeHandlerCore(type, handler, true);
343346
/// <summary>
344347
/// Determine if the specified type will be processed by a custom handler.
345348
/// </summary>
@@ -353,7 +356,16 @@ public static void RemoveTypeMap(Type type)
353356
/// <param name="type">The type to handle.</param>
354357
/// <param name="handler">The handler to process the <paramref name="type"/>.</param>
355358
/// <param name="clone">Whether to clone the current type handler map.</param>
359+
[Obsolete("Please use " + nameof(AddTypeHandler), error: true)]
360+
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
356361
public static void AddTypeHandlerImpl(Type type, ITypeHandler? handler, bool clone)
362+
{
363+
// this method was accidentally made public; we'll mark it as illegal, but
364+
// preserve existing usage in compiled code; sorry about this!
365+
AddTypeHandlerCore(type, handler, true); // do not allow suppress clone
366+
}
367+
368+
private static void AddTypeHandlerCore(Type type, ITypeHandler? handler, bool clone)
357369
{
358370
if (type is null) throw new ArgumentNullException(nameof(type));
359371

@@ -373,39 +385,45 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler? handler, bool clo
373385
}
374386
}
375387

376-
var snapshot = typeHandlers;
377-
if (snapshot.TryGetValue(type, out var oldValue) && handler == oldValue) return; // nothing to do
388+
// synchronize between callers mutating type-handlers; note that regular query
389+
// code may still be accessing the field, so we still use snapshot/mutate/swap;
390+
// the synchronize is just to prevent lost writes
391+
lock (typeHandlersSyncLock)
392+
{
393+
if (typeHandlers.TryGetValue(type, out var oldValue) && handler == oldValue) return; // nothing to do
378394

379-
var newCopy = clone ? new Dictionary<Type, ITypeHandler>(snapshot) : snapshot;
395+
var newCopy = clone ? new Dictionary<Type, ITypeHandler>(typeHandlers) : typeHandlers;
380396

381397
#pragma warning disable 618
382-
typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache<int>.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]);
383-
if (secondary is not null)
384-
{
385-
typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache<int>.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]);
386-
}
398+
typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache<int>.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]);
399+
if (secondary is not null)
400+
{
401+
typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache<int>.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]);
402+
}
387403
#pragma warning restore 618
388-
if (handler is null)
389-
{
390-
newCopy.Remove(type);
391-
if (secondary is not null) newCopy.Remove(secondary);
392-
}
393-
else
394-
{
395-
newCopy[type] = handler;
396-
if (secondary is not null) newCopy[secondary] = handler;
404+
if (handler is null)
405+
{
406+
newCopy.Remove(type);
407+
if (secondary is not null) newCopy.Remove(secondary);
408+
}
409+
else
410+
{
411+
newCopy[type] = handler;
412+
if (secondary is not null) newCopy[secondary] = handler;
413+
}
414+
typeHandlers = newCopy;
397415
}
398-
typeHandlers = newCopy;
399416
}
400417

401418
/// <summary>
402419
/// Configure the specified type to be processed by a custom handler.
403420
/// </summary>
404421
/// <typeparam name="T">The type to handle.</typeparam>
405422
/// <param name="handler">The handler for the type <typeparamref name="T"/>.</param>
406-
public static void AddTypeHandler<T>(TypeHandler<T> handler) => AddTypeHandlerImpl(typeof(T), handler, true);
423+
public static void AddTypeHandler<T>(TypeHandler<T> handler) => AddTypeHandlerCore(typeof(T), handler, true);
407424

408425
private static Dictionary<Type, ITypeHandler> typeHandlers;
426+
private static readonly object typeHandlersSyncLock = new();
409427

410428
internal const string LinqBinary = "System.Data.Linq.Binary";
411429

@@ -479,7 +497,7 @@ public static void SetDbType(IDataParameter parameter, object value)
479497
{
480498
handler = (ITypeHandler)Activator.CreateInstance(
481499
typeof(SqlDataRecordHandler<>).MakeGenericType(argTypes))!;
482-
AddTypeHandlerImpl(type, handler, true);
500+
AddTypeHandlerCore(type, handler, true);
483501
return DbType.Object;
484502
}
485503
catch

appveyor.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ skip_commits:
99
# install:
1010
# - choco install dotnet-sdk --version 8.0.100
1111

12+
stack: postgresql 15
13+
1214
environment:
1315
Appveyor: true
1416
# Postgres
15-
POSTGRES_PATH: C:\Program Files\PostgreSQL\13
17+
POSTGRES_PATH: C:\Program Files\PostgreSQL\15
1618
PGUSER: postgres
1719
PGPASSWORD: Password12!
1820
POSTGRES_ENV_POSTGRES_USER: postgres
@@ -32,7 +34,7 @@ environment:
3234

3335
services:
3436
# - mysql
35-
- postgresql13
37+
- postgresql15
3638

3739
init:
3840
- git config --global core.autocrlf input

0 commit comments

Comments
 (0)