fix(config): guard the shared config maps, restore lost fields in Tab.Clone
TabsListLock never covered ChatColours, PrivacyPersistChannels or RetentionPerChannelDays, yet the settings UI mutates them from the draw thread while the retention thread can be serializing the same config. Adding a new key to a dictionary or a new element to a set invalidates a running enumeration, so this could throw from inside JsonConvert.SerializeObject. Not a corner case: the colour picker lists 66 channels but only 25 ship with a default, so the first edit of any of the remaining ones inserts a new key -- and the reset button removes a key, which makes the next edit a fresh insert again. The readers matter as much as the writers. IsAllowedForStorage runs per message on the worker thread and asks PrivacyPersistChannels whether a channel may be stored; a Contains racing an Add that resizes buckets can answer wrong, and that answer decides whether a message is written to disk. The retention sweep enumerates RetentionPerChannelDays on the framework thread while the wizard can clear it -- Clear does not throw there, it just cuts the enumeration short, so the sweep would run on half a policy. New ConfigMapsLock covers all of it. It sits inside TabsListLock (that edge is real, AutoTellTabsService calls SaveConfig while holding the tabs lock), never the other way round -- so every call site closes the lock before saving. Tab.Clone silently dropped Icon and ChatCodes, both serialized. A reflection test now walks the serialized fields so a future one cannot slip past. Also: CurrentTab read Count and [0] as two separate accesses.
This commit is contained in:
@@ -84,13 +84,18 @@ internal sealed class ChatTab
|
||||
var present = Plugin.Config.PrivacyPersistChannels.Contains(ct);
|
||||
if (ImGui.Checkbox($"{label}##persist-{label}", ref present))
|
||||
{
|
||||
if (present)
|
||||
// Lock closes before SaveConfig: taking ConfigMapsLock across a save
|
||||
// would invert the lock order (SaveConfig can reach TabsListLock).
|
||||
lock (_plugin.ConfigMapsLock)
|
||||
{
|
||||
Plugin.Config.PrivacyPersistChannels.Add(ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
Plugin.Config.PrivacyPersistChannels.Remove(ct);
|
||||
if (present)
|
||||
{
|
||||
Plugin.Config.PrivacyPersistChannels.Add(ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
Plugin.Config.PrivacyPersistChannels.Remove(ct);
|
||||
}
|
||||
}
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user