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
Protect AppSettings saving using global mutex #10193
Conversation
GitCommands/Settings/AppSettings.cs
Outdated
@@ -1603,7 +1606,16 @@ public static void SaveSettings() | |||
{ | |||
SettingsContainer.LockedAction(() => | |||
{ | |||
SettingsContainer.Save(); | |||
_globalMutex ??= new Mutex(initiallyOwned: false, name: SettingsFilePath.ToPosixPath()); |
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.
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.
Yes, that's the trick. GE instances must write the settings one after the other. (The last one wins. For this I leave #8006 open.)
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.
Why not use OpenExisting in this PR?
(This PR is probably an improvement anyway.)
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 use of OpenExisting
needs a lot of additional code for little benefit (protection by access rights).
From https://docs.microsoft.com/en-us/dotnet/api/system.threading.mutex.-ctor?view=net-6.0#system-threading-mutex-ctor(system-boolean-system-string):
If a name is provided and a synchronization object of the requested type already exists in the namespace, the existing synchronization object is used.
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.
A named mutex is always shared between processes - at least in the same session.
If a synchronization object of a different type already exists in the namespace, a WaitHandleCannotBeOpenedException is thrown.
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 name may be prefixed with Global\ or Local\ to specify a namespace. When the Global namespace is specified, the synchronization object may be shared with any processes on the system. When the Local namespace is specified, which is also the default when no namespace is specified, the synchronization object may be shared with processes in the same session. On Windows, a session is a login session, and services typically run in a different non-interactive session. On Unix-like operating systems, each shell has its own session.
So it seems to be working in Windows, append Global
to prepare for other platforms.
Have you tested multiple closing (by adding a delay or so).
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.
OK, I admit I stopped reading after "On Windows, a session is a login session".
Adding "Global\"
.
Have you tested multiple closing (by adding a delay or so).
Yes, it takes 30s to close three instances at the same each waiting 10s - both with and without "Global\"
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.
Reading the docs, this looks sensible 👍
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 would like a confirmation it actually is tested though...
caba998
to
7031810
Compare
From what I see of the code, this PR is more a fix for #8006 (error messages because multiple processes fails to acces the same file) than #10016 (the last process which write the settings overwrite some changes made in the settings in a process that exited earlier). And for #10016, IMHO, there is no perfect (or easy) solution. Maybe preventing the instances that has no setting change to write the settings when exiting... |
Igor proposed a solution in #8006 (comment). I think #10016 has the same root cause: multiple instances are trying to write the settings file. As the user stated "Then when I opened GitExtensions, at least one setting (probably Show artificial commits or not) was reset to default.", the setting were broken - not just the last GE instance won. So the compiled-in defaults were used on next start. |
The settings is a pandora box, isn't it? :) When I think about this area, I have the following problems in mind:
I don't think we must persist settings on the app exit, and this IMO is the ultimate fix for #10016 and other related issues. gitextensions/GitUI/CommandsDialogs/FormBrowse.cs Lines 1435 to 1438 in ad6671d
|
Writing the settings must be an atomic operation. That's why this PR is necessary and should be taken as is. |
This is not all settings, see FormSettings Save(), but what may be changed outside FormSettings it seems. This is an improvement and should be merged and included in 4.0 |
Yes, there are other settings. Though they are neither written on
This should be idempotent, i.e. not change on reopen FormSettings right after it was closed. |
Any concerns yet merging this? |
No, it should be limiting some issues. |
Fixes #10016
Proposed changes
AppSettings.SaveSettings
using a global mutex common to all GE instancesScreenshots
N/A
Test methodology
Test environment(s)
Merge strategy
I agree that the maintainer squash merge this PR (if the commit message is clear).
✒️ I contribute this code under The Developer Certificate of Origin.