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 3f8f859c4f45d9..ef2dd3aa7ed4ab 100644
--- a/src/libraries/System.Runtime.Caching/src/System.Runtime.Caching.csproj
+++ b/src/libraries/System.Runtime.Caching/src/System.Runtime.Caching.csproj
@@ -4,8 +4,8 @@
$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent);netcoreapp3.1-windows;netcoreapp3.1;netstandard2.0-windows;netstandard2.0
true
- false
- 0
+ 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 72d92119acd795..0b55c536adcfdc 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
@@ -39,6 +39,7 @@ public class MemoryCache : ObjectCache, IEnumerable, IDisposable
private bool _useMemoryCacheManager = true;
private EventHandler _onAppDomainUnload;
private UnhandledExceptionEventHandler _onUnhandledException;
+ private int _inUnhandledExceptionHandler;
#if NET5_0_OR_GREATER
[UnsupportedOSPlatformGuard("browser")]
private static bool _countersSupported => !OperatingSystem.IsBrowser();
@@ -240,14 +241,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 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 107c46f0158c9c..1ad5d6c2ff65a4 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
@@ -334,8 +334,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 b0027403163355..5d7eaa6a96fdc1 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
@@ -56,6 +56,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)
{