feat(prefetch): Pre-fetch patches for sibling releases on the same track#357
Draft
lukemmtt wants to merge 4 commits into
Draft
feat(prefetch): Pre-fetch patches for sibling releases on the same track#357lukemmtt wants to merge 4 commits into
lukemmtt wants to merge 4 commits into
Conversation
Closes the patchless-first-launch gap from shorebirdtech/shorebird#3755. When a user's native binary auto-updates between Shorebird patch-checks, the device today must boot the unpatched new release until the next patch-check completes. This change lets the running binary park raw deltas for *other* release_versions on the same track, so that when the user later updates to one of them, the matching delta is already on disk and is promoted into the active boot path during init. Built on top of the per-patch lifecycle state machine introduced in shorebirdtech#352. A prefetched patch is just a `PatchState::Downloaded` document stored in a parallel namespace keyed by release_version: <storage_dir>/prefetched_patches/<rv>/patches/<n>/state.json <download_dir>/prefetched_patches/<rv>/<n> This namespace is independent of the per-release `patches/` and `download_dir/<n>` paths the active lifecycle owns, so the release-version reset wipes the active store but leaves the prefetched store intact. We construct an independent `PatchLifecycle` against the prefetched roots to reuse `record_download_started`, `record_download_complete`, `read_state`, and `decide_start` for free. Wire shape * Server side: extend `PatchCheckResponse` with an optional `available_release_versions: Vec<String>` field. Server-side, this is the set of release_versions on the requesting app's track that have a publishable patch. Older clients ignore the field. * Client side: after the existing patch-check + install flow runs for the current release, iterate over the advertised siblings (excluding the current release_version), issue one extra `/patches/check` per sibling with `release_version` overridden, download the raw delta, and record it as `PatchState::Downloaded` in the prefetched namespace. No inflation or hash/signature verification happens at prefetch time -- the running binary is the wrong inflation base for any release other than its own. * Promotion: `handle_prior_boot_failure_if_necessary` now also tries to promote a prefetched delta whose release_version matches the now-running binary. The compressed bytes are moved into the active download root, a `Downloaded` state is written into the active `patches/<n>/`, then the existing `inflate -> check_hash -> record_install_complete -> promote_to_next_boot` pipeline runs against it. The new binary's `patch_public_key` is the right key for both hash verification and Strict-mode boot signing; no special handling needed. Lifecycle and cleanup * Cached siblings the server stops advertising are pruned via `retain_prefetched_release_versions` on every successful patch-check. * A prefetched entry that fails inflation, hash check, or matches a known-bad patch number is dropped on attempt so the device doesn't loop. The promoted patch is marked `Bad` in the active lifecycle so `decide_start` short-circuits subsequent updates within the release. * Errors in the prefetch tail are best-effort; they never affect the main update result. Open items for the Shorebird team * Server change to populate `available_release_versions` from the same-track release set with publishable patches. * Decide whether `available_release_versions` should respect gradual-rollout bucketing for siblings (current behavior asks the server again per sibling, so per-release rollout decisions ride for free). * Promotion currently runs on every init; if you'd rather gate it on a fresh `release_version` mismatch detected by `load_or_new_on_error`, that signal would have to be surfaced explicitly.
…ease-patches # Conflicts: # library/src/updater.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes shorebirdtech/shorebird#3755.
Note: This is a draft, proof-of-concept implementation, built by Claude Opus, offered merely as a scope exploration rather than a ready-to-merge change.
Summary
When a user's native binary auto-updates between Shorebird patch-checks, the device today must boot the unpatched new release until the next patch-check completes. This change lets the running binary park raw deltas for other
release_versions on the same track, so that when the user later updates to one of them, the matching delta is already on disk and is promoted into the active boot path during init.Approach
Built on top of the per-patch lifecycle state machine introduced in #352. A prefetched patch is just a
PatchState::Downloadeddocument stored in a parallel namespace keyed byrelease_version:This namespace is independent of the per-release
patches/anddownload_dir/<n>paths the active lifecycle owns, so the release-version reset wipes the active store but leaves the prefetched store intact. An independentPatchLifecycleis constructed against the prefetched roots to reuserecord_download_started,record_download_complete,read_state, anddecide_start.Wire shape
PatchCheckResponsewith optionalavailable_release_versions: Vec<String>, the set of release_versions on the requesting app's track that have a publishable patch. Older clients ignore the field./patches/checkper sibling withrelease_versionoverridden, download the raw delta, and record it asPatchState::Downloadedin the prefetched namespace. No inflation or hash/signature verification happens at prefetch time, since the running binary is the wrong inflation base for any release other than its own.handle_prior_boot_failure_if_necessaryalso tries to promote a prefetched delta whose release_version matches the now-running binary. The compressed bytes are moved into the active download root, aDownloadedstate is written into the activepatches/<n>/, then the existinginflate → check_hash → record_install_complete → promote_to_next_bootpipeline runs against it. The new binary'spatch_public_keyis the right key for both hash verification and Strict-mode boot signing; no special handling needed.Lifecycle and cleanup
retain_prefetched_release_versionson every successful patch-check.Badin the active lifecycle sodecide_startshort-circuits subsequent updates within the release.Open items for the Shorebird team
available_release_versionsfrom the same-track release set with publishable patches.available_release_versionsshould respect gradual-rollout bucketing for siblings. Current behavior asks the server again per sibling, so per-release rollout decisions ride for free.release_versionmismatch detected byload_or_new_on_error, that signal would have to be surfaced explicitly.prefetchUpcomingPatches: falseinshorebird.yaml) for unusual cases like metered networks or embedded devices is not wired up here.Test plan
cargo test --workspacepasses 242/242 on this branch.