Skip to content

fix/autosave timer - #8917

Merged
max-nextcloud merged 15 commits into
mainfrom
fix/autosave-timer
Jul 30, 2026
Merged

fix/autosave timer#8917
max-nextcloud merged 15 commits into
mainfrom
fix/autosave-timer

Conversation

@max-nextcloud

Copy link
Copy Markdown
Collaborator
  • fix(autosave): every time the typing stops
  • chore(refactor): document tracking into provideSyncService
  • fix(autosave): only save when server is ready
  • fix(autosave): better dirty tracking with versions
  • chore(refactor): Save service with its own bus
  • chore(refactor): separate SaveService from SyncService and ydoc
  • fix(autosave): exponential delay for retries on error
  • fix(autosave): retry if server throttled save request
  • fix(autosave): handle out of sync clocks

@max-nextcloud
max-nextcloud marked this pull request as ready for review July 28, 2026 08:36
@max-nextcloud max-nextcloud moved this to 🏗️ In progress in 📝 Productivity team Jul 28, 2026
@max-nextcloud max-nextcloud moved this from 🏗️ In progress to 👀 In review in 📝 Productivity team Jul 28, 2026
@max-nextcloud max-nextcloud self-assigned this Jul 28, 2026

@mejo- mejo- left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have quite some comments, but most of them are probably indicators that I'm uncertain about the impact of the code changes. To be honest I would prefer pairing review in a call 😆

Comment thread src/services/SaveService.ts
Comment thread src/services/SaveService.ts Outdated
if (now < lastSave + SERVER_AUTOSAVE_INTERVAL) {
logger.debug('Not autosaving as last save is recent', { lastSave, now })
const nextSave = lastSave + SERVER_AUTOSAVE_INTERVAL + Math.random() * MAX_RANDOM_AUTOSAVE_DELAY
setTimeout(() => this.autosave(), (nextSave - now) * 1000)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took me some time to understand the logic here. If I got it right, it means that the next save will happen sometime between 0 and 13 seconds from now but not before lastSave + 10s.

What's the purpose of randomising it in a range of 3 seconds? To make autosaves more useful because other clients save at a different point in time? Or to lower the load on the server?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the random range all clients would attempt to save at the same time. That seemed to be asking for trouble. I had race conditions in mind - but also what you are saying.

I later removed both the attempt at calculating the time the server is available again and the randomness for the sake of simplicity and deterministic behavior of the client for easier testing.

Comment thread src/composables/useSyncService.ts Outdated
Comment thread src/services/SaveService.ts Outdated
*/
function updateDocument(event: { document: Document }) {
document.value = event.document
// Limit lastSavedVersionTime to now. No saving from the future.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this change I now wonder whether it's clever to calculate delays by comparing server and client timestamps at all. It's probably pretty common that the clocks differ by a few seconds, no?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did remove some of the computations later on. But maybe we should fully rely on the clients clock.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did so now.

Comment thread src/services/SaveService.ts Outdated
@max-nextcloud
max-nextcloud force-pushed the fix/autosave-timer branch 3 times, most recently from 3c68cca to b7d8abd Compare July 29, 2026 07:26
@max-nextcloud
max-nextcloud requested a review from mejo- July 29, 2026 07:29
Debounce will delay the execution of the function every time it is called.
So autosave was waiting until no updates occured for 30 seconds.

That's a long time and does not feel responsive.
Try to autosave every time the user stops typing for at least one second.

Signed-off-by: Max <max@nextcloud.com>
Also removed the document attribute from the `sync` event load.
It is already included in the `change` event load.

Signed-off-by: Max <max@nextcloud.com>
The server will only accept autosaves every 10 seconds.
`document` contains the last saved timestamp.
Compute the time to autosave next.
Add a small random delay (up to 3 seconds)
to avoid all connected clients from saving at the same time.

Signed-off-by: Max <max@nextcloud.com>
Keep track of the version that has our changes
and compare it to the last saved version on the server.

This allows fixing two scenarios:
* Server response happily to save but does not actually save.
  The server will only save new versions every 10 seconds.
  If the latest save just happened it will still respond with 200
  but list the outdated version in the response.
  Comparing the versions shows that our changes have not been saved yet.
* Other user already saved the file.
  our changes.
  So far dirty would stay true until WE save our changes.
  Comparing the versions also shows the file was saved
  when it was saved by someone else.

Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
* `getSaveData` now prepares the data to send to the server.
* `provideSaveService` now handles all the sync service events
  calling functions on `saveService` where needed.

Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
The server will only perform one actual save every 10 seconds.

Trigger another autosave if the save attempt was throttled.
`document` is udpated by throttled save attempts.
Rely on its `lastSavedVersionTime` for the retry.

Signed-off-by: Max <max@nextcloud.com>
* If the server claims to have saved the doc in the future
  use the current timestamp instead.
  Autosave happens at least ten seconds after `lastSavedVersionTime`.
  So if that was far into the future it would never happen.

* If the server is living in the past
  delay the autosave retries
  without relying on `lastSavedVersionTime`.

Signed-off-by: Max <max@nextcloud.com>
Adding a random offset makes determenistic testing harder.

Now we also do not depend on in sync clocks.

Signed-off-by: Max <max@nextcloud.com>
Autosave is triggered by the push of the steps.
Saving before that delays the autosave because of server throttling.

Signed-off-by: Max <max@nextcloud.com>
Some of our runners are slow. Give them more time to finish the runs.

Also separate local and CI config in the config file.

Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
Only consider the local clock.
Avoid problems if clocks are out of sync
or if the server is unresponsive and `lastSavedVersionTime` does not get updated

Signed-off-by: Max <max@nextcloud.com>
Signed-off-by: Max <max@nextcloud.com>
@max-nextcloud

Copy link
Copy Markdown
Collaborator Author

/backport to stable34

@max-nextcloud
max-nextcloud merged commit 4ad091c into main Jul 30, 2026
61 checks passed
@max-nextcloud
max-nextcloud deleted the fix/autosave-timer branch July 30, 2026 07:28
@github-project-automation github-project-automation Bot moved this from 👀 In review to ☑️ Done in 📝 Productivity team Jul 30, 2026
@backportbot backportbot Bot mentioned this pull request Jul 30, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: ☑️ Done

Development

Successfully merging this pull request may close these issues.

2 participants