Add thread-safe double-checked locking to MotelyPaths.Initialize#19
Conversation
Co-authored-by: joirunner <6373131+joirunner@users.noreply.github.com>
Co-authored-by: joirunner <6373131+joirunner@users.noreply.github.com>
…hrows Co-authored-by: joirunner <6373131+joirunner@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR implements thread-safe initialization for the MotelyPaths static class using a double-checked locking pattern to address concurrency concerns raised in code review feedback.
Changes:
- Added thread-safe initialization using double-checked locking pattern with a dedicated lock object
- Updated documentation to clarify thread-safety guarantees and behavior
- Ensured atomic initialization where directory creation failures allow for retry
Comments suppressed due to low confidence (1)
Motely.API/MotelyPaths.cs:18
- The volatile keyword on the string fields (_contentRoot, _jamlFiltersOverride, etc.) is unnecessary and potentially misleading. In the double-checked locking pattern, only the _isInitialized flag needs to be volatile to ensure proper memory ordering. The lock in the Initialize method already provides the necessary memory barriers to ensure that all field writes inside the lock are visible to threads that read _isInitialized as true. These volatile keywords can be removed from the string fields while keeping it only on _isInitialized.
private static volatile string _contentRoot = Directory.GetCurrentDirectory();
private static volatile string? _jamlFiltersOverride;
private static volatile string? _seedSourcesOverride;
private static volatile string? _searchResultsOverride;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Kept inside the lock to ensure atomic initialization: if directory creation fails, | ||
| // _isInitialized remains false, allowing retry on next Initialize call. |
There was a problem hiding this comment.
The comment states "Kept inside the lock to ensure atomic initialization" but the actual reason is more nuanced. Directory creation is a side effect that can fail, and keeping it inside the lock ensures that if it fails and throws an exception, _isInitialized remains false, allowing retry. However, the operations themselves are not truly "atomic" - if directory creation partially succeeds then fails, some directories may exist while others don't. Consider clarifying this comment to explain the retry-on-failure behavior more explicitly.
| // Kept inside the lock to ensure atomic initialization: if directory creation fails, | |
| // _isInitialized remains false, allowing retry on next Initialize call. | |
| // Kept inside the lock so that if any directory creation fails and throws, | |
| // _isInitialized remains false and a future Initialize call can safely retry. |
The
volatilekeyword alone is insufficient for thread-safe initialization. Multiple threads callingInitializeconcurrently could race, creating directories multiple times or setting_isInitializedbefore initialization completes.Changes
_initLockwith double-checked locking pattern_isInitializedbefore acquiring lock to minimize contention post-initialization_isInitializedfrom being set, allowing retryThe volatile fields remain necessary for memory ordering guarantees on the initialization flag.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.