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) {