I have an app where I save its state as often as possible to a Json file. When multiple events occur at the same time, they may overwrite information over each other. The state is held by a singleton, and my current solution is to add small delays before saving to the file, and it works for the time being but I feel like its waiting to go wrong at some point.
Whilst keeping the ‘state’ of your application in a singleton does work, the singleton pattern is primarily used for when the possibility of a new object is created, it will only and always return the initially created object. IE: A second object cannot be created.
Your application, having the ability to possibly create more than one ‘state’ object doesn’t sound like the correct architecture.
Instead, you can create the applications ‘state’ object as either a global object (not recommended) or as an object wrapped in a namespace file. This second option would be the preferred.
With 'state' functionality being in its own file, this allows you have a single source of truth for the 'state' object. All functions would be contained in this module and then required
by other files when the need arises to get()
the 'state' object. An alternative would be to use the Node.js event
system (only in the Electron main process) to call for the 'state' object, similar to Electrons IPC system.
An important question you need to ask is: Are the update / save requests coming in faster than the system can save to disk?
If application 'state' saves are only required when the user perform particular UI actions then you only need to perform a save operation when any of these particular events occur. As the user won't be performing UI actions milliseconds apart, the module can just perform the request (update the ‘state’ object in RAM and then save the ‘state’ object to disk) without issue.
If the application 'state' can be changed in quick succession by the application itself (and not via the UI), then we need to take a different approach.
In this instance, I would use a stack to track the requests. FIFO (first in, first out). If you want to reference a design pattern, I guess the 'active object' design pattern is close to what you want.
Implementation of such a function would be to have a name spaced file that contains the 'state' object, a stack counter, a 'ready' (Boolean) flag and a promise based (asynchronous) function to save the 'state' object to file. Other ancillary functions would be in there such as a stack increment and decrement, toggling the 'ready' flag and the publicly available 'update()' and get()' functions.
The file would initially read the 'state' object into RAM from disk upon application start.
When the 'state' object changes (via calling the 'update()' function), you would set the 'ready' flag to false
, push the object onto a stack (array) and increment the stack counter. Then, update the 'state' object in RAM and save the 'state' object to file. When the disk ‘save’ promise is fulfilled, remove the object from the stack, decrement the stack counter and set the 'ready' flag back to true
.
Whilst the above was going on, if another request came in to update the 'state' object, the same would occur but the new 'state' object would be pushed onto the stack and the stack counter incremented. This would continue to go on until the stack counter came back down to zero, at which point we know that all queue requests have now been processed.
The point of the 'ready' flag is to inform an incoming get()
request of any possible discrepancy between what is in memory and what is on disk. Delaying the get()
function from returning a possibly invalid ‘state’ object until the ‘ready’ flag is true
would be prudent coding. Perhaps a promise based mechanism could be used to ensure the function requesting the ‘state’ object does not receive the latest update until the ‘ready’ flag is true
.
As you can see, to code a robust module of this functionality does require some work, but building it right the first time will ensure it will hold up to anything you throw at it in the future.