Skip to content

Commit 95ad1c4

Browse files
committed
Don't dispose timers if we're in our UnhandledException handler. (dotnet#103937)
1 parent 0960bd6 commit 95ad1c4

File tree

3 files changed

+24
-2
lines changed

3 files changed

+24
-2
lines changed

src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCache.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class MemoryCache : ObjectCache, IEnumerable, IDisposable
3939
private bool _useMemoryCacheManager = true;
4040
private EventHandler _onAppDomainUnload;
4141
private UnhandledExceptionEventHandler _onUnhandledException;
42+
private int _inUnhandledExceptionHandler;
4243
#if NET5_0_OR_GREATER
4344
[UnsupportedOSPlatformGuard("browser")]
4445
private static bool _countersSupported => !OperatingSystem.IsBrowser();
@@ -240,14 +241,19 @@ private void OnAppDomainUnload(object unusedObject, EventArgs unusedEventArgs)
240241
Dispose();
241242
}
242243

244+
internal bool InUnhandledExceptionHandler => _inUnhandledExceptionHandler > 0;
243245
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs eventArgs)
244246
{
247+
Interlocked.Increment(ref _inUnhandledExceptionHandler);
248+
245249
// if the CLR is terminating, dispose the cache.
246250
// This will dispose the perf counters
247251
if (eventArgs.IsTerminating)
248252
{
249253
Dispose();
250254
}
255+
256+
Interlocked.Decrement(ref _inUnhandledExceptionHandler);
251257
}
252258

253259
private void ValidatePolicy(CacheItemPolicy policy)

src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/MemoryCacheStatistics.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,19 @@ public void Dispose()
334334
GCHandleRef<Timer> timerHandleRef = _timerHandleRef;
335335
if (timerHandleRef != null && Interlocked.CompareExchange(ref _timerHandleRef, null, timerHandleRef) == timerHandleRef)
336336
{
337-
timerHandleRef.Dispose();
338-
Dbg.Trace("MemoryCacheStats", "Stopped CacheMemoryTimers");
337+
// If inside an unhandled exception handler, Timers may be succeptible to deadlocks. Use a safer approach.
338+
if (_memoryCache.InUnhandledExceptionHandler)
339+
{
340+
// This does not stop/dispose the timer. But the callback on the timer is protected by _disposed, which we have already
341+
// set above.
342+
timerHandleRef.FreeHandle();
343+
Dbg.Trace("MemoryCacheStats", "Freed CacheMemoryTimers");
344+
}
345+
else
346+
{
347+
timerHandleRef.Dispose();
348+
Dbg.Trace("MemoryCacheStats", "Stopped CacheMemoryTimers");
349+
}
339350
}
340351
}
341352
while (_inCacheManagerThread != 0)

src/libraries/System.Runtime.Caching/src/System/Runtime/Caching/SRef.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public T Target
5656
public void Dispose()
5757
{
5858
Target.Dispose();
59+
FreeHandle();
60+
}
61+
62+
internal void FreeHandle()
63+
{
5964
// Safe to call Dispose more than once but not thread-safe
6065
if (_handle.IsAllocated)
6166
{

0 commit comments

Comments
 (0)