Workspace config stops being saved (due to a timing issue in debounce())

Steps to reproduce

  1. Starting in the sandbox, open two popout windows using “Open in new window” on files in the Obsidian explorer pane
  2. Focus one of the windows. Wait a few seconds, then make a note of the modification time of .obsidian/workspace.json. It should be roughly the current time. (Or more precisely, about 1 second after you focused the window.)
  3. While the same window is still focused, click the “X” on the tab in the other popout window, causing it to close. (Don’t switch to the window first - you must go from "focused in window 1 having done nothing for a few seconds, to just clicking the tab X in the other window.)
  4. Wait a few seconds, then check the timestamp of workspace.json – it will not have changed.
  5. Do anything else you like that should result in the saving of the workspace file, and check the timestamp again: still no change.

Depending on your timing, you may have to do this once or twice to get the repro. You will know you have repro at step 4 if the file timestamp does not change. (Note that you will need to check the timestamp using file properties or stat or something else that gives you seconds resolution, or else you will need to wait for the current clock minute to change at step 3 before clicking, in order to see that the timestamp is not updated.)

The required action to get the repro is that you be 1) focused in a popout with the workspace state saved (i.e. you do not switch tabs for a few seconds), and 2) you close a different popout, by clicking a tab’s X (so that the second window gains focus and then is closed within <1 second). The reproduction requires that a save request be triggered and the active window closed within 1 second of the save request, i.e. before the save can actually happen, but at a moment where the activeWindow is the window that will be closed.

Did you follow the troubleshooting guide? [Y/N]

Y

Expected result

The workspace.json file should be updated with the workspace state: i.e. changes of active window or tab, opening new files, etc.

Actual result

Obsidian will not save the workspace file ever again unless you close the vault entirely and restart, or else run app.workspace.requestSaveLayout.cancel() in the developer console.

This took forever to figure out because I’ve been having the problem of lost workspace state happen to me for quite some time, but only recently figured out why. I would just close Obsidian (or have it crash) and then have tabs missing or be the wrong ones when I started again, with a version of my layout/windows that was days older than what I expected it to be.

Environment

SYSTEM INFO:
Obsidian version: v1.7.7
Installer version: v1.5.8
Operating system: Windows 10 Pro 10.0.19045
Login status: logged in
Language: en
Catalyst license: supporter
Insider build toggle: off
Live preview: on
Base theme: light
Community theme: none
Snippets enabled: 0
Restricted mode: on

RECOMMENDATIONS:
none


Additional information

The problem happens because Obsidian’s debounce() API is not robust in the instance where a setTimeout callback is dropped, either due to Chromium timer throttling or the closing of the window where the timeout was set. Its logic assumes that if a timer was set, then eventually it will be called and all will be well, rather than tracking a target time and running when the target time is reached. This is why, once it breaks, it stays broken until the .cancel() operation is performed.

Once I figured this out, I was then able to reverse engineer a way to reproduce the problem – one that also seems likely to be the event sequence that’s been causing my problem, since it’s not uncommon for me to open a file in a new window while already in a popout, then close that window a few seconds later while still being focused in a different popout. It’s not something I do often, but given that it usually takes a few days of Obsidian being open for the problem to occur, it seems reasonably likely to be the thing tripping the issue. (I’ve gotten paranoid about checking my workspace.json timestamp, trying to catch the issue early.)

Note, however, that the repro steps I have given are only one way to produce the problem, since anything that prevents a setTimeout will cause any debounce()-wrapped function in any part of Obsidian (core or plugins) to have the same issue. (If for example Chromium timeout throttling can also do this, getting a repro would not be anywhere near as straightforward as this one.)

One simple fix would be to have debounce() track the target time (as it already does for certain debounce modes) and check if that time is already past when a request happens, so that it can go ahead and clear the skipped timer in that case. While the scheduled activation will still be missed, the debounced function won’t enter a permanently-broken state where it can never be run again without an explicit cancel.

(So in the workspace-specific case, saving would resume as activity continued in the app, even though the save request triggered by the active window change would still not run.)

Thanks for the detailed report

Have you tried 1.8.2? We have made some changes to the workspace saving.

Thanks for the investigation. I was able to repro and fix. In my logging this also seemed to affect the word count plugin.

Will be included in 1.8.3.

1 Like