Skip to content

Commit

Permalink
Don't dispose timers if we're in our UnhandledException handler. (#10…
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenMolloy committed Jul 16, 2024
1 parent b23dd72 commit 294f2a3
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,19 @@ public void Dispose()
GCHandleRef<Timer> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down

0 comments on commit 294f2a3

Please sign in to comment.