Skip to content

PROD-5987: fix race condition in HzCacheMemoryLocker causing disposed semaphore errors#26

Merged
JoelNygren-Norce merged 2 commits into
mainfrom
PROD-5987-fix-memory-locker-race-condition
Jun 10, 2026
Merged

PROD-5987: fix race condition in HzCacheMemoryLocker causing disposed semaphore errors#26
JoelNygren-Norce merged 2 commits into
mainfrom
PROD-5987-fix-memory-locker-race-condition

Conversation

@JoelNygren-Norce

@JoelNygren-Norce JoelNygren-Norce commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary\n\nHzCacheMemoryLocker stores SemaphoreSlim instances in a MemoryCache with 5-minute sliding expiration. The post-eviction callback disposes the semaphore, but there's a race between GetSemaphore() returning a reference and WaitAsync()/Wait() using it — the MemoryCache can evict+dispose the semaphore in that window, causing ObjectDisposedException to propagate as a fatal "Could not acquire lock" error.\n\nFix: catch ObjectDisposedException in both AcquireLockAsync and AcquireLock, remove the stale lockCache entry, and retry with a fresh semaphore:\n\ndiff\n+try\n+{\n acquired = await semaphore.WaitAsync(timeout, token);\n+}\n+catch (ObjectDisposedException)\n+{\n+ lockCache.Remove(key);\n+ semaphore = GetSemaphore(cacheName, cacheInstanceId, key, logger);\n+ acquired = await semaphore.WaitAsync(timeout, token);\n+}\n\n\nObserved in production Admin on 2026-06-10 (09:07–09:52 UTC, ~2,375 errors). The race was amplified by background serialization failures (StartUpdateChecksumAndNotify) causing contention and GC pressure.\n\nJira: PROD-5987">

Link to Devin session: https://app.devin.ai/sessions/3e7c5915fd8640e69f53c7ad184e302d
Requested by: @JoelNygren-Norce

…evicted

Catch ObjectDisposedException in both AcquireLockAsync and AcquireLock.
When MemoryCache evicts and disposes a SemaphoreSlim between GetSemaphore
and WaitAsync, remove the stale entry and retry with a fresh semaphore
instead of propagating the exception as a fatal error.

PROD-5987

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Comment thread HzMemoryCache/HzCacheMemoryLocker.cs Outdated
Replace single retry with a for-loop bounded by maxLockRetries (default 3).
The catch filter (when attempt < maxLockRetries) lets the final attempt's
ObjectDisposedException propagate if retries are exhausted.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@johan-ketels
johan-ketels requested a review from Copilot June 10, 2026 10:55
@johan-ketels
johan-ketels marked this pull request as ready for review June 10, 2026 10:57
@johan-ketels
johan-ketels requested a review from a team as a code owner June 10, 2026 10:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a race in HzCacheMemoryLocker where SemaphoreSlim instances stored in MemoryCache can be evicted/disposed between GetSemaphore() and Wait()/WaitAsync(), causing ObjectDisposedException to escape and be treated as a fatal “Could not acquire lock” error.

Changes:

  • Added maxLockRetries to HzCacheMemoryLockerOptions to control retry behavior on disposed semaphores.
  • Wrapped SemaphoreSlim.WaitAsync() in a retry loop that removes the stale cache entry and re-creates the semaphore when ObjectDisposedException occurs.
  • Applied the same retry pattern to the synchronous SemaphoreSlim.Wait() path.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread HzMemoryCache/HzCacheMemoryLocker.cs
Comment thread HzMemoryCache/HzCacheMemoryLocker.cs
Comment thread HzMemoryCache/HzCacheMemoryLocker.cs
@JoelNygren-Norce
JoelNygren-Norce merged commit 7865206 into main Jun 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants