-
Notifications
You must be signed in to change notification settings - Fork 726
Always check refCount after acquiring lock #1986
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I never noticed that we had two things with nearly the same implementation
There was a problem hiding this comment.
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 race conditions in reference-counted cache implementations by adding checks for deleted entries and adjusting lock timing. The changes ensure that operations handle cases where entries are marked for deletion (refCount <= 0) between the time they're looked up and when their locks are acquired.
Key Changes
- Added resurrection logic in
Ref()methods to handle entries deleted while acquiring locks - Added recursive retry logic in
loadOrStoreNewLockedEntry()for deleted entries - Moved
mu.Unlock()inDeref()methods to afterDelete()operations to prevent race conditions
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| internal/project/parsecache.go | Adds race condition handling for deleted parse cache entries and fixes unlock ordering in Deref |
| internal/project/extendedconfigcache.go | Adds race condition handling for deleted config cache entries and fixes unlock ordering in Deref |
Comments suppressed due to low confidence (1)
internal/project/extendedconfigcache.go:1
- This recursive retry could loop indefinitely under sustained contention. Consider adding a maximum retry count to prevent potential stack overflow or infinite loops in edge cases where entries are repeatedly deleted.
package project
internal/project/parsecache.go
Outdated
| if existing.refCount <= 0 { | ||
| // Existing entry was deleted while we were acquiring the lock | ||
| existing.mu.Unlock() | ||
| return c.loadOrStoreNewLockedEntry(key) | ||
| } |
Copilot
AI
Oct 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This recursive retry pattern could potentially loop indefinitely if there's sustained contention where entries are constantly being deleted. Consider adding a retry limit or exponential backoff to prevent unbounded recursion in pathological cases.
|
Oh, it's blowing up because we never delete entries in our test suites since they're likely to come back |
|
Doesn't that imply we aren't testing this new code? |
|
Yes. AFAIK it's not possible to deterministically trigger the race, because there's no callback into test code that could occur between the sync map load and the mutex lock. |
| if entry, ok := c.entries.Load(path); ok { | ||
| entry.mu.Lock() | ||
| if entry.refCount <= 0 { | ||
| // Entry was deleted while we were acquiring the lock |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't possible that the entry got deleted before the cal to c.entries.Load(path) as well?
Fixes a race noticed by @sheetalkamat
Probably fixes #1983