Maybe fix the race condition and add more logging.

This commit is contained in:
Ottermandias 2023-05-29 18:48:46 +02:00
parent f8d1fcf4e2
commit 02fe5a4fb3
6 changed files with 96 additions and 53 deletions

View file

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
@ -30,7 +31,10 @@ public class CollectionCache : IDisposable
public readonly MetaCache Meta; public readonly MetaCache Meta;
public readonly Dictionary<IMod, SingleArray<ModConflicts>> _conflicts = new(); public readonly Dictionary<IMod, SingleArray<ModConflicts>> _conflicts = new();
public bool Calculating; public int Calculating = -1;
public string AnonymizedName
=> _collection.AnonymizedName;
public IEnumerable<SingleArray<ModConflicts>> AllConflicts public IEnumerable<SingleArray<ModConflicts>> AllConflicts
=> _conflicts.Values; => _conflicts.Values;
@ -138,7 +142,7 @@ public class CollectionCache : IDisposable
=> _manager.AddChange(ChangeData.ModRemoval(this, mod, addMetaChanges)); => _manager.AddChange(ChangeData.ModRemoval(this, mod, addMetaChanges));
/// <summary> Force a file to be resolved to a specific path regardless of conflicts. </summary> /// <summary> Force a file to be resolved to a specific path regardless of conflicts. </summary>
private void ForceFileSync(Utf8GamePath path, FullPath fullPath) internal void ForceFileSync(Utf8GamePath path, FullPath fullPath)
{ {
if (!CheckFullPath(path, fullPath)) if (!CheckFullPath(path, fullPath))
return; return;
@ -149,7 +153,7 @@ public class CollectionCache : IDisposable
ResolvedFiles.Add(path, new ModPath(Mod.ForcedFiles, fullPath)); ResolvedFiles.Add(path, new ModPath(Mod.ForcedFiles, fullPath));
} }
internal void ReloadModSync(IMod mod, bool addMetaChanges) private void ReloadModSync(IMod mod, bool addMetaChanges)
{ {
RemoveModSync(mod, addMetaChanges); RemoveModSync(mod, addMetaChanges);
AddModSync(mod, addMetaChanges); AddModSync(mod, addMetaChanges);
@ -238,7 +242,7 @@ public class CollectionCache : IDisposable
{ {
++_collection.ChangeCounter; ++_collection.ChangeCounter;
if (mod.TotalManipulations > 0) if (mod.TotalManipulations > 0)
AddMetaFiles(); AddMetaFiles(false);
_manager.MetaFileManager.ApplyDefaultFiles(_collection); _manager.MetaFileManager.ApplyDefaultFiles(_collection);
} }
@ -263,6 +267,8 @@ public class CollectionCache : IDisposable
if (!CheckFullPath(path, file)) if (!CheckFullPath(path, file))
return; return;
try
{
if (ResolvedFiles.TryAdd(path, new ModPath(mod, file))) if (ResolvedFiles.TryAdd(path, new ModPath(mod, file)))
{ {
ModData.AddPath(mod, path); ModData.AddPath(mod, path);
@ -281,6 +287,11 @@ public class CollectionCache : IDisposable
ModData.AddPath(mod, path); ModData.AddPath(mod, path);
} }
} }
catch (Exception ex)
{
Penumbra.Log.Error($"[{Thread.CurrentThread.ManagedThreadId}] Error adding redirection {file} -> {path} for mod {mod.Name} to collection cache {AnonymizedName}:\n{ex}");
}
}
// Remove all empty conflict sets for a given mod with the given conflicts. // Remove all empty conflict sets for a given mod with the given conflicts.
@ -374,8 +385,8 @@ public class CollectionCache : IDisposable
// Add all necessary meta file redirects. // Add all necessary meta file redirects.
public void AddMetaFiles() public void AddMetaFiles(bool fromFullCompute)
=> Meta.SetImcFiles(); => Meta.SetImcFiles(fromFullCompute);
// Identify and record all manipulated objects for this entire collection. // Identify and record all manipulated objects for this entire collection.

View file

@ -81,17 +81,31 @@ public class CollectionCacheManager : IDisposable
} }
public void AddChange(CollectionCache.ChangeData data) public void AddChange(CollectionCache.ChangeData data)
{
if (data.Cache.Calculating == -1)
{ {
if (_framework.Framework.IsInFrameworkUpdateThread) if (_framework.Framework.IsInFrameworkUpdateThread)
data.Apply(); data.Apply();
else else
_changeQueue.Enqueue(data); _changeQueue.Enqueue(data);
} }
else if (data.Cache.Calculating == Environment.CurrentManagedThreadId)
{
data.Apply();
}
else
{
_changeQueue.Enqueue(data);
}
}
/// <summary> Only creates a new cache, does not update an existing one. </summary> /// <summary> Only creates a new cache, does not update an existing one. </summary>
public bool CreateCache(ModCollection collection) public bool CreateCache(ModCollection collection)
{ {
if (collection.HasCache || collection.Index == ModCollection.Empty.Index) if (collection.Index == ModCollection.Empty.Index)
return false;
if (collection._cache != null)
return false; return false;
collection._cache = new CollectionCache(this, collection); collection._cache = new CollectionCache(this, collection);
@ -115,27 +129,33 @@ public class CollectionCacheManager : IDisposable
if (collection.Index == 0) if (collection.Index == 0)
return; return;
Penumbra.Log.Debug($"[{Thread.CurrentThread.ManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName}"); Penumbra.Log.Debug($"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName}");
if (!collection.HasCache) if (!collection.HasCache)
{ {
Penumbra.Log.Error( Penumbra.Log.Error(
$"[{Thread.CurrentThread.ManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, no cache exists."); $"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, no cache exists.");
return;
} }
else if (collection._cache!.Calculating != -1)
{
Penumbra.Log.Error(
$"[{Environment.CurrentManagedThreadId}] Recalculating effective file list for {collection.AnonymizedName} failed, already in calculation on [{collection._cache!.Calculating}].");
}
else
{
FullRecalculation(collection); FullRecalculation(collection);
Penumbra.Log.Debug( Penumbra.Log.Debug(
$"[{Thread.CurrentThread.ManagedThreadId}] Recalculation of effective file list for {collection.AnonymizedName} finished."); $"[{Environment.CurrentManagedThreadId}] Recalculation of effective file list for {collection.AnonymizedName} finished.");
}
} }
private void FullRecalculation(ModCollection collection) private void FullRecalculation(ModCollection collection)
{ {
var cache = collection._cache; var cache = collection._cache;
if (cache == null || cache.Calculating) if (cache is not { Calculating: -1 })
return; return;
cache.Calculating = true; cache.Calculating = Environment.CurrentManagedThreadId;
try try
{ {
cache.ResolvedFiles.Clear(); cache.ResolvedFiles.Clear();
@ -152,7 +172,7 @@ public class CollectionCacheManager : IDisposable
foreach (var mod in _modStorage) foreach (var mod in _modStorage)
cache.AddModSync(mod, false); cache.AddModSync(mod, false);
cache.AddMetaFiles(); cache.AddMetaFiles(true);
++collection.ChangeCounter; ++collection.ChangeCounter;
@ -160,7 +180,7 @@ public class CollectionCacheManager : IDisposable
} }
finally finally
{ {
cache.Calculating = false; cache.Calculating = -1;
} }
} }
@ -329,12 +349,19 @@ public class CollectionCacheManager : IDisposable
/// </summary> /// </summary>
public void CreateNecessaryCaches() public void CreateNecessaryCaches()
{ {
Parallel.ForEach(_active.SpecialAssignments.Select(p => p.Value) ModCollection[] caches;
// Lock to make sure no race conditions during CreateCache happen.
lock (this)
{
caches = _active.SpecialAssignments.Select(p => p.Value)
.Concat(_active.Individuals.Select(p => p.Collection)) .Concat(_active.Individuals.Select(p => p.Collection))
.Prepend(_active.Current) .Prepend(_active.Current)
.Prepend(_active.Default) .Prepend(_active.Default)
.Prepend(_active.Interface) .Prepend(_active.Interface)
.Where(CreateCache), CalculateEffectiveFileListInternal); .Where(CreateCache).ToArray();
}
Parallel.ForEach(caches, CalculateEffectiveFileListInternal);
} }
private void OnModDiscoveryStarted() private void OnModDiscoveryStarted()

View file

@ -17,11 +17,19 @@ public readonly struct ImcCache : IDisposable
public ImcCache() public ImcCache()
{ } { }
public void SetFiles(ModCollection collection) public void SetFiles(ModCollection collection, bool fromFullCompute)
{
if (fromFullCompute)
{
foreach (var path in _imcFiles.Keys)
collection._cache!.ForceFileSync(path, CreateImcPath(collection, path));
}
else
{ {
foreach (var path in _imcFiles.Keys) foreach (var path in _imcFiles.Keys)
collection._cache!.ForceFile(path, CreateImcPath(collection, path)); collection._cache!.ForceFile(path, CreateImcPath(collection, path));
} }
}
public void Reset(ModCollection collection) public void Reset(ModCollection collection)
{ {

View file

@ -59,7 +59,7 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
_estCache.SetFiles(_manager); _estCache.SetFiles(_manager);
_gmpCache.SetFiles(_manager); _gmpCache.SetFiles(_manager);
_cmpCache.SetFiles(_manager); _cmpCache.SetFiles(_manager);
_imcCache.SetFiles(_collection); _imcCache.SetFiles(_collection, false);
} }
public void Reset() public void Reset()
@ -167,8 +167,8 @@ public class MetaCache : IDisposable, IEnumerable<KeyValuePair<MetaManipulation,
} }
/// <summary> Set the currently relevant IMC files for the collection cache. </summary> /// <summary> Set the currently relevant IMC files for the collection cache. </summary>
public void SetImcFiles() public void SetImcFiles(bool fromFullCompute)
=> _imcCache.SetFiles(_collection); => _imcCache.SetFiles(_collection, fromFullCompute);
public MetaList.MetaReverter TemporarilySetEqpFile() public MetaList.MetaReverter TemporarilySetEqpFile()
=> _eqpCache.TemporarilySetFiles(_manager); => _eqpCache.TemporarilySetFiles(_manager);

View file

@ -31,15 +31,12 @@ public partial class IndividualCollections
if (_actorService.Valid) if (_actorService.Valid)
return ReadJObjectInternal(obj, storage); return ReadJObjectInternal(obj, storage);
void Func() void Func()
{
saver.DalamudFramework.RunOnFrameworkThread(() =>
{ {
if (ReadJObjectInternal(obj, storage)) if (ReadJObjectInternal(obj, storage))
saver.ImmediateSave(parent); saver.ImmediateSave(parent);
IsLoaded = true; IsLoaded = true;
Loaded.Invoke(); Loaded.Invoke();
_actorService.FinishedCreation -= Func; _actorService.FinishedCreation -= Func;
});
} }
_actorService.FinishedCreation += Func; _actorService.FinishedCreation += Func;
return false; return false;

View file

@ -77,7 +77,7 @@ public sealed class ModManager : ModStorage, IDisposable
ScanMods(); ScanMods();
_communicator.ModDiscoveryFinished.Invoke(); _communicator.ModDiscoveryFinished.Invoke();
Penumbra.Log.Information("Rediscovered mods."); Penumbra.Log.Information($"Rediscovered {Mods.Count} mods.");
if (ModBackup.MigrateModBackups) if (ModBackup.MigrateModBackups)
ModBackup.MigrateZipToPmp(this); ModBackup.MigrateZipToPmp(this);