|
@ -10,6 +10,10 @@ using IPA.Utilities.Async; |
|
|
using System.IO; |
|
|
using System.IO; |
|
|
using System.Runtime.CompilerServices; |
|
|
using System.Runtime.CompilerServices; |
|
|
using IPA.Logging; |
|
|
using IPA.Logging; |
|
|
|
|
|
#if NET4
|
|
|
|
|
|
using Task = System.Threading.Tasks.Task; |
|
|
|
|
|
using TaskEx = System.Threading.Tasks.Task; |
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
namespace IPA.Config |
|
|
namespace IPA.Config |
|
|
{ |
|
|
{ |
|
@ -49,6 +53,24 @@ namespace IPA.Config |
|
|
saveThread = new Thread(SaveThread); |
|
|
saveThread = new Thread(SaveThread); |
|
|
saveThread.Start(); |
|
|
saveThread.Start(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
AppDomain.CurrentDomain.ProcessExit -= ShutdownRuntime; |
|
|
|
|
|
AppDomain.CurrentDomain.ProcessExit += ShutdownRuntime; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static void ShutdownRuntime(object sender, EventArgs e) |
|
|
|
|
|
{ |
|
|
|
|
|
watcherTrackConfigs.Clear(); |
|
|
|
|
|
var watchList = watchers.ToArray(); |
|
|
|
|
|
watchers.Clear(); |
|
|
|
|
|
|
|
|
|
|
|
foreach (var pair in watchList) |
|
|
|
|
|
pair.Value.EnableRaisingEvents = false; |
|
|
|
|
|
|
|
|
|
|
|
loadScheduler.Join(); // we can wait for the loads to finish
|
|
|
|
|
|
saveThread.Abort(); // eww, but i don't like any of the other potential solutions
|
|
|
|
|
|
|
|
|
|
|
|
SaveAll(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public static void RegisterConfig(Config cfg) |
|
|
public static void RegisterConfig(Config cfg) |
|
@ -107,25 +129,31 @@ namespace IPA.Config |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static void EnsureWritesSane(Config config) |
|
|
|
|
|
{ |
|
|
|
|
|
// compare exchange loop to be sane
|
|
|
|
|
|
var writes = config.Writes; |
|
|
|
|
|
while (writes < 0) |
|
|
|
|
|
writes = Interlocked.CompareExchange(ref config.Writes, 0, writes); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private static void FileChangedEvent(object sender, FileSystemEventArgs e) |
|
|
private static void FileChangedEvent(object sender, FileSystemEventArgs e) |
|
|
{ |
|
|
{ |
|
|
var watcher = sender as FileSystemWatcher; |
|
|
var watcher = sender as FileSystemWatcher; |
|
|
if (!watcherTrackConfigs.TryGetValue(watcher, out var bag)) return; |
|
|
if (!watcherTrackConfigs.TryGetValue(watcher, out var bag)) return; |
|
|
|
|
|
|
|
|
var config = bag.FirstOrDefault(c => c.File.FullName == e.FullPath); |
|
|
var config = bag.FirstOrDefault(c => c.File.FullName == e.FullPath); |
|
|
if (config != null) |
|
|
|
|
|
|
|
|
if (config != null && Interlocked.Decrement(ref config.Writes) + 1 > 0) |
|
|
|
|
|
{ |
|
|
|
|
|
EnsureWritesSane(config); |
|
|
TriggerFileLoad(config); |
|
|
TriggerFileLoad(config); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public static Task TriggerFileLoad(Config config) => loadFactory.StartNew(() => LoadTask(config)); |
|
|
public static Task TriggerFileLoad(Config config) => loadFactory.StartNew(() => LoadTask(config)); |
|
|
|
|
|
|
|
|
public static Task TriggerLoadAll() => |
|
|
public static Task TriggerLoadAll() => |
|
|
#if NET3
|
|
|
|
|
|
TaskEx |
|
|
|
|
|
#else
|
|
|
|
|
|
Task |
|
|
|
|
|
#endif
|
|
|
|
|
|
.WhenAll(configs.Select(TriggerFileLoad)); |
|
|
|
|
|
|
|
|
TaskEx.WhenAll(configs.Select(TriggerFileLoad)); |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// this is synchronous, unlike <see cref="TriggerFileLoad(Config)"/>
|
|
|
/// this is synchronous, unlike <see cref="TriggerFileLoad(Config)"/>
|
|
@ -141,9 +169,15 @@ namespace IPA.Config |
|
|
lock (config.Provider) |
|
|
lock (config.Provider) |
|
|
{ |
|
|
{ |
|
|
config.Provider.File = config.File; |
|
|
config.Provider.File = config.File; |
|
|
|
|
|
EnsureWritesSane(config); |
|
|
|
|
|
Interlocked.Increment(ref config.Writes); |
|
|
store.WriteTo(config.Provider); |
|
|
store.WriteTo(config.Provider); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
catch (ThreadAbortException) |
|
|
|
|
|
{ |
|
|
|
|
|
throw; |
|
|
|
|
|
} |
|
|
catch (Exception e) |
|
|
catch (Exception e) |
|
|
{ |
|
|
{ |
|
|
Logger.config.Error($"{nameof(IConfigStore)} for {config.File} errored while writing to disk"); |
|
|
Logger.config.Error($"{nameof(IConfigStore)} for {config.File} errored while writing to disk"); |
|
@ -181,31 +215,42 @@ namespace IPA.Config |
|
|
|
|
|
|
|
|
private static void SaveThread() |
|
|
private static void SaveThread() |
|
|
{ |
|
|
{ |
|
|
while (true) |
|
|
|
|
|
|
|
|
try |
|
|
{ |
|
|
{ |
|
|
var configArr = configs.Where(c => c.Store != null).ToArray(); |
|
|
|
|
|
int index = -1; |
|
|
|
|
|
try |
|
|
|
|
|
{ |
|
|
|
|
|
var waitHandles = configArr.Select(c => c.Store.SyncObject) |
|
|
|
|
|
.Prepend(configsChangedWatcher) |
|
|
|
|
|
.ToArray(); |
|
|
|
|
|
index = WaitHandle.WaitAny(waitHandles); |
|
|
|
|
|
} |
|
|
|
|
|
catch (Exception e) |
|
|
|
|
|
|
|
|
while (true) |
|
|
{ |
|
|
{ |
|
|
Logger.config.Error($"Error waiting for in-memory updates"); |
|
|
|
|
|
Logger.config.Error(e); |
|
|
|
|
|
Thread.Sleep(TimeSpan.FromSeconds(1)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var configArr = configs.Where(c => c.Store != null).ToArray(); |
|
|
|
|
|
int index = -1; |
|
|
|
|
|
try |
|
|
|
|
|
{ |
|
|
|
|
|
var waitHandles = configArr.Select(c => c.Store.SyncObject) |
|
|
|
|
|
.Prepend(configsChangedWatcher) |
|
|
|
|
|
.ToArray(); |
|
|
|
|
|
index = WaitHandle.WaitAny(waitHandles); |
|
|
|
|
|
} |
|
|
|
|
|
catch (ThreadAbortException) |
|
|
|
|
|
{ |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
catch (Exception e) |
|
|
|
|
|
{ |
|
|
|
|
|
Logger.config.Error($"Error waiting for in-memory updates"); |
|
|
|
|
|
Logger.config.Error(e); |
|
|
|
|
|
Thread.Sleep(TimeSpan.FromSeconds(1)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (index <= 0) |
|
|
|
|
|
{ // we got a signal that the configs collection changed, loop around, or errored
|
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if (index <= 0) |
|
|
|
|
|
{ // we got a signal that the configs collection changed, loop around, or errored
|
|
|
|
|
|
continue; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// otherwise, we have a thing that changed in a store
|
|
|
|
|
|
Save(configArr[index - 1]); |
|
|
|
|
|
|
|
|
// otherwise, we have a thing that changed in a store
|
|
|
|
|
|
Save(configArr[index - 1]); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
catch (ThreadAbortException) |
|
|
|
|
|
{ |
|
|
|
|
|
// we got aborted :(
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|