Skip to content

Reclaim decommissioned installs off the listing path #251

Description

@willwashburn

Problem

installs --json is a discovery call the desktop awaits inside bootstrapIfNeeded() before the supervisor starts, and it also reclaims decommissioned installs. #245 made that reclamation bounded — one install per listing, and within it at most 32 snapshots, one page of journal rows, one sweep page, one page of receipts — so a listing is no longer unbounded by construction.

Two things remain.

A listing still does maintenance. Every caller of a read inherits whatever that maintenance costs, and the bound is a property of the current implementation rather than of the contract. Reclamation belongs behind its own call.

The residue is permanent and grows. A reclaimed install keeps history.db (deliberately — its origin_id keys every record the Cloud holds, so reconnecting the same account resumes incrementally) and never regains a config.json. So it is examined by every future listing forever, at the cost of one database open each. That floor rises by one per cloud uninstall or account switch, for the life of the machine, and it sits on the desktop's launch path where a timeout does not help: a launch that is slow but under the timeout is still a slow launch, and it gets slower.

Shape

A reclaim bridge command, so installs becomes a pure read and the desktop calls reclamation off the launch path.

That leaves the question of what stops a reclaim from re-examining every decommissioned directory. A versioned marker beside the lock files — written when a pass finds nothing left to reclaim, skipped on a stat rather than a database open — turns the residue from an open into a directory-entry check. Versioning keeps it honest: if reclamation later learns to release something a previous pass did not, reclaimed_v2 re-examines everything reclaimed_v1 skipped.

Constraints

These are the parts that are not visible from the sketch, and each corresponds to a way a straightforward implementation goes wrong.

A decommissioned install is not terminal. It is exactly the state a reconnect starts from — retaining history.db is the whole point. So decommission → marker → reconnect → decommission again leaves a stale marker on the directory that now has a fresh generation, a fresh journal and fresh logs: the one with the most to reclaim, skipped permanently. Versioning never catches this, because nothing about reclamation changed.

Clearing the marker on config write would put the invariant in two modules. The sweep writes it, setup clears it, and any future path that writes config.json without clearing — another setup entry point, a restore, a support workaround — starves that directory silently, invisibly at the point where it breaks.

Prefer a self-invalidating marker. Record what the pass observed of history.db (mtime and size) and trust the marker only while those still match. A reconnect that captured anything must have written to that database, so the marker invalidates itself without setup needing to know it exists. Same cost class: a stat on one file instead of another.

Argue any implementation against the asymmetry, not against the expected sequence. The marker's failure mode must be a wasted open, never a skipped reclaim. A false "dirty" costs one database open on one launch; a false "clean" starves a directory forever. A WAL checkpoint moving the mtime with no reclaimable work is exactly the error worth making.

Smaller notes. disconnect never writes the marker: it always does work, so it is never a no-op, and the marker is always written by a later listing. The marker lives beside the lock files rather than in decommissioned_files, so a reclaim cannot delete what it just wrote.

Cross-repo

relay-desktop is sizing an installs() timeout of 30 s against #245's per-call constants — that call is currently the only bridge call with no timeout at all. When reclaim exists, the constants to size against move to it, and installs becomes a plain read.

Follow-up to #245. Related: #243, #53.

No activity

Activity on this issue will appear here.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions