diff --git a/src/libraries/System.Runtime.Caching/src/System.Runtime.Caching.csproj b/src/libraries/System.Runtime.Caching/src/System.Runtime.Caching.csproj
index b3d85127f02129..241cbbffb21367 100644
--- a/src/libraries/System.Runtime.Caching/src/System.Runtime.Caching.csproj
+++ b/src/libraries/System.Runtime.Caching/src/System.Runtime.Caching.csproj
@@ -3,6 +3,8 @@
$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);$(NetCoreAppPrevious)-windows;$(NetCoreAppPrevious);$(NetCoreAppMinimum)-windows;$(NetCoreAppMinimum);netstandard2.0
true
true
+ true
+ 1
true
true
true
diff --git a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCache.cs b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCache.cs
index e740eed69ed514..e2c99f5a92e050 100644
--- a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCache.cs
+++ b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCache.cs
@@ -40,6 +40,7 @@ public class MemoryCache : ObjectCache, IEnumerable, IDisposable
private bool _throwOnDisposed;
private EventHandler _onAppDomainUnload;
private UnhandledExceptionEventHandler _onUnhandledException;
+ private int _inUnhandledExceptionHandler;
#if NETCOREAPP
[UnsupportedOSPlatformGuard("browser")]
private static bool _countersSupported => !OperatingSystem.IsBrowser();
@@ -238,14 +239,19 @@ private void OnAppDomainUnload(object unusedObject, EventArgs unusedEventArgs)
Dispose();
}
+ internal bool InUnhandledExceptionHandler => _inUnhandledExceptionHandler > 0;
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs eventArgs)
{
+ Interlocked.Increment(ref _inUnhandledExceptionHandler);
+
// if the CLR is terminating, dispose the cache.
// This will dispose the perf counters
if (eventArgs.IsTerminating)
{
Dispose();
}
+
+ Interlocked.Decrement(ref _inUnhandledExceptionHandler);
}
private static void ValidatePolicy(CacheItemPolicy policy)
diff --git a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCacheStatistics.cs b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCacheStatistics.cs
index 96cfc3bda655bd..0599e8febc593c 100644
--- a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCacheStatistics.cs
+++ b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCacheStatistics.cs
@@ -340,8 +340,19 @@ public void Dispose()
GCHandleRef timerHandleRef = _timerHandleRef;
if (timerHandleRef != null && Interlocked.CompareExchange(ref _timerHandleRef, null, timerHandleRef) == timerHandleRef)
{
- timerHandleRef.Dispose();
- Dbg.Trace("MemoryCacheStats", "Stopped CacheMemoryTimers");
+ // If inside an unhandled exception handler, Timers may be succeptible to deadlocks. Use a safer approach.
+ if (_memoryCache.InUnhandledExceptionHandler)
+ {
+ // This does not stop/dispose the timer. But the callback on the timer is protected by _disposed, which we have already
+ // set above.
+ timerHandleRef.FreeHandle();
+ Dbg.Trace("MemoryCacheStats", "Freed CacheMemoryTimers");
+ }
+ else
+ {
+ timerHandleRef.Dispose();
+ Dbg.Trace("MemoryCacheStats", "Stopped CacheMemoryTimers");
+ }
}
}
while (_inCacheManagerThread != 0)
diff --git a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/SRef.cs b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/SRef.cs
index 9a09ba1d9781ae..412f66971a7fea 100644
--- a/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/SRef.cs
+++ b/src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/SRef.cs
@@ -59,6 +59,11 @@ public T Target
public void Dispose()
{
Target.Dispose();
+ FreeHandle();
+ }
+
+ internal void FreeHandle()
+ {
// Safe to call Dispose more than once but not thread-safe
if (_handle.IsAllocated)
{