Settings corrupted on Shutdown
Opened this issue · 2 comments
I occasionally get error in telemetry reports that settings file is corrupted if user shuts down computer while program is running. I already have in place shutdown blocking - program blocks shutdown with ShutdownBlockReasonCreate
, runs Properties.Settings.Default.Save();
and then removes the shutdown block with ShutdownBlockReasonDestroy
. I know that the user didn't press "shut down anyway" because the Serilog logs a message that the ShutdownBlockReasonDestroy
was reached and completed, and that log is saved to disk and properly flushed, but for some reason the settings are not consistently saved.
Is there a way to ensure that settings are saved?
I am using PortableJsonSettingsProvider
0.2.1 from Nuget
[DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string pwszReason);
[DllImport("user32.dll")]
public extern static bool ShutdownBlockReasonDestroy(IntPtr hWnd);
This issue might occur due to file caching happening on the OS side (see documentation here).
As described in the linked article, file caching can be disabled for specific files if necessary. In .NET, this option is realized by passing FileOptions.WriteThrough
when opening a file stream.
Since the file stream persisting settings PortableJsonSettingsProvider
is not publicly exposed currently, you can copy the implementation of PortableJsonSettingsProvider
from the repository and modify the SetPropertyValues()
method, e.g. as follows:
public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
{
JObject jObject = GetJObject();
foreach (SettingsPropertyValue value in collection)
setSettingsValue(jObject, (string) context["GroupName"], value);
try
{
using (var fs = new FileStream(ApplicationSettingsFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 4096, FileOptions.WriteThrough))
using (var sw = new StreamWriter(fs))
{
sw.WriteLine(JsonConvert.SerializeObject(JsonUtility.SortPropertiesAlphabetically(jObject), Formatting.Indented));
}
}
catch { }
}
Please let me know if this resolves the issue you describe. If that's the case, I can consider adding a built-in option to enable this behavior.
Saving settings like this I have noticed that the resulting file would sometimes have some duplication
"UseSmoothScrolling": "False"
}
}
}
}
g": "False"
}
}
}
}
}
}
I would change settings and just call once
Properties.Settings.Default.Save();
It is not json serialization issue because saving two files at the same time will still produce the correct file with WriteAllText
using (var fs = new FileStream(ApplicationSettingsFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 40960, FileOptions.WriteThrough))
using (var sw = new StreamWriter(fs))
{ sw.WriteLine(JsonConvert.SerializeObject(JsonUtility.SortPropertiesAlphabetically(jObject), Formatting.Indented));
}
File.WriteAllText(ApplicationSettingsFile+"OLD", JsonConvert.SerializeObject(JsonUtility.SortPropertiesAlphabetically(jObject),
Formatting.Indented));
The fix seems to be using FileMode.Create
instead of FileMode.OpenOrCreate
I also suggest using Write
instead of WriteLine
, since the original
WriteAllText
uses that (and doesn't produce an extra newline at the end)
https://referencesource.microsoft.com/#mscorlib/system/io/file.cs,f73b33d2cc64e5db,references
But according to this
https://itecnote.com/tecnote/c-file-writealltext-not-flushing-data-to-disk/
FileOptions.WriteThrough can replace Flush but you still need the temp file if your data can exceed a single cluster in size.
so that might be the only way to guarantee that corruption won't happen at some point anyway.
I will release a version with these changes and see if I get any additional corruption issues in telemetry.