bugfix: fixed a race condition potentially causing last write wins

This commit is contained in:
mayo 2025-11-01 16:45:33 -04:00
parent 723eaa0076
commit d4b6f611f8
No known key found for this signature in database
GPG key ID: 5B138E78344184A6

View file

@ -31,36 +31,38 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
public ModCollection Create(string name, int index, ModCollection? duplicate) public ModCollection Create(string name, int index, ModCollection? duplicate)
{ {
var newCollection = duplicate?.Duplicate(name, CurrentCollectionId, index) var localId = AllocateNextId();
?? ModCollection.CreateEmpty(name, CurrentCollectionId, index, _modStorage.Count); var newCollection = duplicate?.Duplicate(name, localId, index)
Add(newCollection); ?? ModCollection.CreateEmpty(name, localId, index, _modStorage.Count);
AddAtLocalId(newCollection, localId);
return newCollection; return newCollection;
} }
public ModCollection CreateFromData(Guid id, string name, int version, Dictionary<string, ModSettings.SavedSettings> allSettings, public ModCollection CreateFromData(Guid id, string name, int version, Dictionary<string, ModSettings.SavedSettings> allSettings,
IReadOnlyList<string> inheritances) IReadOnlyList<string> inheritances)
{ {
var localId = AllocateNextId();
var newCollection = ModCollection.CreateFromData(_saveService, _modStorage, var newCollection = ModCollection.CreateFromData(_saveService, _modStorage,
new ModCollectionIdentity(id, CurrentCollectionId, name, Count), version, allSettings, inheritances); new ModCollectionIdentity(id, localId, name, Count), version, allSettings, inheritances);
Add(newCollection); AddAtLocalId(newCollection, localId);
return newCollection; return newCollection;
} }
public ModCollection CreateTemporary(string name, int index, int globalChangeCounter) public ModCollection CreateTemporary(string name, int index, int globalChangeCounter)
{ {
var newCollection = ModCollection.CreateTemporary(name, CurrentCollectionId, index, globalChangeCounter); var localId = AllocateNextId();
Add(newCollection); var newCollection = ModCollection.CreateTemporary(name, localId, index, globalChangeCounter);
AddAtLocalId(newCollection, localId);
return newCollection; return newCollection;
} }
/// <remarks> Atomically add to _collectionLocal and increments _currentCollectionIdValue. </remarks> /// <remarks> Atomically add to _collectionLocal at the id given. </remarks>
private void Add(ModCollection newCollection) private void AddAtLocalId(ModCollection newCollection, LocalCollectionId id)
{ {
_collectionsByLocal.AddOrUpdate(CurrentCollectionId, _collectionsByLocal.AddOrUpdate(id,
static (_, newColl) => newColl, static (_, newColl) => newColl,
static (_, _, newColl) => newColl, static (_, _, newColl) => newColl,
newCollection); newCollection);
Interlocked.Increment(ref _currentCollectionIdValue);
} }
public void Delete(ModCollection collection) public void Delete(ModCollection collection)
@ -87,6 +89,9 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable, ISer
/// <remarks> Starts at 1 because the empty collection gets Zero. </remarks> /// <remarks> Starts at 1 because the empty collection gets Zero. </remarks>
public LocalCollectionId CurrentCollectionId => new(_currentCollectionIdValue); public LocalCollectionId CurrentCollectionId => new(_currentCollectionIdValue);
private LocalCollectionId AllocateNextId ()
=> new(Interlocked.Increment(ref _currentCollectionIdValue));
/// <summary> Default enumeration skips the empty collection. </summary> /// <summary> Default enumeration skips the empty collection. </summary>
public IEnumerator<ModCollection> GetEnumerator() public IEnumerator<ModCollection> GetEnumerator()
=> _collections.Skip(1).GetEnumerator(); => _collections.Skip(1).GetEnumerator();