Skip to content

sync: a game being played is held back from syncing until the session ends - #117

Open
ChakraFusion wants to merge 2 commits into
Liquid-co:dev-publicfrom
ChakraFusion:pr/play-hold
Open

ChakraFusion wants to merge 2 commits into
Liquid-co:dev-publicfrom
ChakraFusion:pr/play-hold

Conversation

@ChakraFusion

Copy link
Copy Markdown

The next standalone part of the play-sessions draft (#34), rebuilt on dev-public without save versions.

The problem. A game autosaves every few minutes, and each autosave was synced as it came. One afternoon, a device took eleven versions of one game in 26 minutes from the device it was being played on, each with its own "synced from another device" snapshot. The playing device snapshotted every autosave too. The gameplay guard only waits while save files are locked, and many games don't keep them locked. And a device took other devices' versions of a game running on it, writing a save under the game.

The change (syncengine/playing.go, daemon/sessions.go). While a game is being played on a device (the daemon's play sessions):

  • it isn't synced either way there: SyncGame declines quietly (ErrPlayingHere, which counts as ErrHeld);
  • another device asking for it, for its manifest or for deletions, is answered "playing". That device says so once per session instead of failing. The answer contains SettlingMessage, so an older build takes it as "busy, ask again";
  • the watcher snapshots a change to it at most once per 30 minutes, so a crash still leaves a recent copy.

When the session ends, the existing "After playing" snapshot is taken and the save goes to the other devices in one sync.

A session must end when the game does (second commit), since it now holds the save. A process named like the game stayed behind for fifteen minutes after one closed, with under 1 MB and no processor time; a killed one left two that kept their memory and used none. On Windows, processes are now measured (memory and processor time). One holding under 32 MB that used no processor time since the last poll, or one that used none for a minute whatever it holds, is not the game. A game being played uses some all the time; a paused one still draws its menu. A process that can't be measured still counts.

Tests:

  • syncengine: TestPlaying_NotSyncedWhilePlayedHere, TestPlaying_PeerPlayingIsWaitedFor
  • daemon: checkpoint timing, TestSessionEndsWhenOnlyAStubIsLeft (fails without the idle filter), TestActiveProcesses_HoldingMemoryDoingNothing
  • sessions: TestIdle

Left for a follow-up (P2): finding a running game wherever it is installed, and protected games whose path can't be read.

Checks: the full unit and e2e suites pass locally on today's dev-public.

🤖 Generated with Claude Code

ChakraFusion and others added 2 commits October 20, 2026 21:28
… ends

A game autosaves every few minutes, and each autosave was synced as it
came: one afternoon a device took eleven versions of a game in 26 minutes
from the device it was being played on, each with its own snapshot, and
the playing device snapshotted every autosave too. And a device took
other devices' versions of a game running on it, writing a save under the
game.

While a game is being played on a device (its play sessions):
- it is not synced either way there (ErrPlayingHere, quiet as ErrHeld);
- another device asking for it is answered "playing", for its manifest
  and for deletions, and says so once per session instead of failing; the
  answer contains SettlingMessage, so an older build takes it as busy;
- the watcher snapshots a change to it at most once per 30 minutes.
When the session ends, the existing "After playing" snapshot is taken
and the save goes to the other devices in one sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a session is open the game's save is now held back from syncing,
so a session that outlives the game holds the save with it. A process
named like the game stayed behind for fifteen minutes after one closed,
with under 1 MB and no processor time; a killed one left two that kept
their memory and used no processor time at all.

Processes are measured on Windows (memory, processor time; sessions.Proc).
One holding under 32 MB that used no processor time since the last poll
(sessions.Idle), or one that used none for a minute whatever it holds, is
not the game (daemon.activeProcesses). A game being played uses some all
the time; a paused one still draws its menu. One that cannot be measured
still counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 21, 2026

Copy link
Copy Markdown

@ChakraFusion is attempting to deploy a commit to the sivadaboi's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Omar-VF

Omar-VF commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Thanks, this reads well, and it fits with the Deck plugin's play sessions (#109). One question on idleFor: a game minimised in the background can throttle to almost nothing, and some suspend outright. If one used no processor time for a minute, its session would end while it's still running, and the save would be synced under it, which is the case this PR is there to stop. Would a longer quiet period (a few minutes) still catch the stubs you saw, which lingered for fifteen?

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants