-
Notifications
You must be signed in to change notification settings - Fork 0
Fix RepositoryPaths.BuildVariables temp file deletion issue #151
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
Co-authored-by: BenjaminMichaelis <[email protected]>
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 refactors BuildVariables
to defer loading until first access and adds error handling for missing temp files or directories, preventing the static initializer crash. It also introduces a unit test to confirm graceful degradation when the temp file is deleted.
- Switch
BuildVariables
from eager static initialization to a lazily-loaded getter withLoadBuildVariables()
. - Catch
FileNotFoundException
andDirectoryNotFoundException
, returning an empty dictionary. - Add
BuildVariables_HandlesFileNotFound_Gracefully
test to verify behavior when the temp file is absent.
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
File | Description |
---|---|
IntelliTect.Multitool/RepositoryPaths.cs | Replaced eager initialization with lazy loading and error handling. |
IntelliTect.Multitool.Tests/RepositoryPaths.Tests.cs | Added test for missing temp file scenario and reset cache via reflection. |
Comments suppressed due to low confidence (1)
IntelliTect.Multitool.Tests/RepositoryPaths.Tests.cs:47
- [nitpick] Consider adding a test for the DirectoryNotFoundException case to cover the branch when the temp directory itself is missing.
[Fact]
public static ReadOnlyDictionary<string, string?> BuildVariables | ||
{ | ||
get | ||
{ | ||
if (_buildVariables == null) | ||
{ | ||
_buildVariables = LoadBuildVariables(); | ||
} | ||
return _buildVariables; | ||
} | ||
} | ||
|
||
private static ReadOnlyDictionary<string, string?>? _buildVariables; |
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.
The lazy initialization of _buildVariables isn’t thread-safe. Consider using Lazy or a lock to prevent race conditions if BuildVariables is accessed concurrently.
public static ReadOnlyDictionary<string, string?> BuildVariables | |
{ | |
get | |
{ | |
if (_buildVariables == null) | |
{ | |
_buildVariables = LoadBuildVariables(); | |
} | |
return _buildVariables; | |
} | |
} | |
private static ReadOnlyDictionary<string, string?>? _buildVariables; | |
public static ReadOnlyDictionary<string, string?> BuildVariables => _buildVariables.Value; | |
private static readonly Lazy<ReadOnlyDictionary<string, string?>> _buildVariables = | |
new Lazy<ReadOnlyDictionary<string, string?>>(LoadBuildVariables); |
Copilot uses AI. Check for mistakes.
string filePath = Path.Combine(Path.GetTempPath(), BuildVariableFileName); | ||
var lines = File.ReadAllLines(filePath); | ||
var dictionary = lines | ||
.Select(line => line.Split("::")) |
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.
Using Split("::") splits on individual ':' characters rather than the literal "::" token. If the intent is to split on "::", use Split(new[] { "::" }, StringSplitOptions.None) for correct behavior.
.Select(line => line.Split("::")) | |
.Select(line => line.Split(new[] { "::" }, StringSplitOptions.None)) |
Copilot uses AI. Check for mistakes.
return new ReadOnlyDictionary<string, string?>(new Dictionary<string, string?>()); | ||
} | ||
catch (DirectoryNotFoundException) | ||
{ | ||
// Return empty dictionary if the temp directory doesn't exist | ||
return new ReadOnlyDictionary<string, string?>(new Dictionary<string, string?>()); |
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.
[nitpick] Allocating a new empty dictionary on each catch can be wasteful. Consider defining a static readonly empty ReadOnlyDictionary to reuse and avoid repeated allocations.
return new ReadOnlyDictionary<string, string?>(new Dictionary<string, string?>()); | |
} | |
catch (DirectoryNotFoundException) | |
{ | |
// Return empty dictionary if the temp directory doesn't exist | |
return new ReadOnlyDictionary<string, string?>(new Dictionary<string, string?>()); | |
return _emptyReadOnlyDictionary; | |
} | |
catch (DirectoryNotFoundException) | |
{ | |
// Return empty dictionary if the temp directory doesn't exist | |
return _emptyReadOnlyDictionary; |
Copilot uses AI. Check for mistakes.
try | ||
{ | ||
// Reset the static field to force re-initialization | ||
var field = typeof(RepositoryPaths).GetField("_buildVariables", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); |
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.
[nitpick] Using reflection to reset a private static field makes the test brittle. Consider adding an internal reset method or using a public test hook to clear the cache more robustly.
Copilot uses AI. Check for mistakes.
The
RepositoryPaths.BuildVariables
property was failing with aTypeInitializationException
when the temporary build variables file gets deleted by Windows cleanup processes, making the entireRepositoryPaths
class unusable.Problem
This occurred when:
Root Cause
The
BuildVariables
property was initialized eagerly as a static readonly property that immediately tried to read from a temp file without any error handling:Solution
Changed
BuildVariables
to use lazy initialization with proper error handling:FileNotFoundException
andDirectoryNotFoundException
TryGetValue()
which handles missing keys appropriatelyChanges Made
BuildVariables_HandlesFileNotFound_Gracefully
to verify the fixVerification
TypeInitializationException
when temp file is missingBuildVariables
returns empty dictionary gracefully when file doesn't existGetDefaultRepoRoot()
handles missing BuildVariables appropriatelyThe fix is minimal and surgical, maintaining full backward compatibility while resolving the crash scenario.
Fixes #137.
💡 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.