Skip to content

Static MySettings.QueueSave()/SaveIfRequired() save the Get() singleton, not the LoadOrCreate() instance, so edits made through LoadOrCreate() are lost or overwritten with stale data #330

Description

@matt-edmondson

What's wrong

The static helpers AppData<T>.QueueSave() and SaveIfRequired() (AppDataStorage/AppData.cs:615,620) act on Get(), which is InternalState.Value, a Lazy<T> that runs its own LoadOrCreate (AppData.cs:410,415). The parameterless LoadOrCreate() (AppData.cs:~560-576) always deserializes a new object and never sets or returns InternalState.

So MySettings.LoadOrCreate() and MySettings.Get() are two independent in-memory copies of the same file:

  • Edits made on one are invisible to the other.
  • Whichever saves last overwrites the file.

The repo's CLAUDE.md usage example implies they are the same object:

var data = MyAppData.LoadOrCreate();
var same = MyAppData.Get(); // Singleton-like access

Failure scenarios

  1. Edits are silently lost.
    var s = MySettings.LoadOrCreate();
    s.Theme = "dark";
    MySettings.QueueSave();       // queues the Get() singleton, not s
    // ...3 s later...
    MySettings.SaveIfRequired();  // writes the singleton's "light"
    The file ends up with "Theme": "light". s never had a save queued, so its dispose-on-exit writes nothing. Verified with a scratch console app: same instance as Get(): False, and the disk shows "Theme": "light".
  2. Stale data overwrites newer data. Code saves s.Save() from a LoadOrCreate() instance. Anything that later queues a save on a Get() singleton loaded earlier writes the old values back over the file.

Suggested fix

Pick one:

  • Make the parameterless LoadOrCreate() return InternalState.Value. The default-location instance is then the singleton, and the subdirectory and file-name overloads remain separate instances.
  • Or let the first parameterless LoadOrCreate() seed InternalState, and have later calls return it.
  • At minimum, document on LoadOrCreate() and on the static QueueSave/SaveIfRequired that they target different instances, and fix the CLAUDE.md example.

Acceptance criteria

  • ReferenceEquals(MySettings.LoadOrCreate(), MySettings.Get()) holds, or the docs clearly state it does not and the CLAUDE.md sample is corrected.
  • A test shows that an edit made through the documented access path and saved via the static QueueSave/SaveIfRequired reaches disk.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions