feat(updater): gate update installs on device state and power - #1282
feat(updater): gate update installs on device state and power#1282wizzomafizzo wants to merge 4 commits into
Conversation
An update rewrites the running binary, so losing power partway through is the failure the whole install pipeline is arranged around. Nothing in Core could tell whether the device was on mains or running down a battery. pkg/helpers/power reports one of four answers: no battery, external power, running on battery with a charge percentage, or unknown. Linux reads /sys/class/power_supply through afero so the parsing is testable without a real device, Windows calls GetSystemPowerStatus, and darwin shells out to pmset because there is no syscall for it. The pmset parser lives in an untagged file so Linux CI exercises it; only the exec wrapper is darwin-tagged. Unknown is the answer on a platform with no reader, and callers are expected to treat it as "this device could lose power at any moment" rather than as "no battery". Reporting no battery there would hand every such build a green light no reading supports. platforms.PowerStatus prefers a platform's own PowerStatusProvider when it has one and falls back to the host reader otherwise, so a platform that knows better than /sys can say so.
An update restarts the service. Until now the only thing that could stop one was the update already running, so applying an update while a media scan was half done, a backup was uploading, a token was part-way through being written or a game was running would take all of it away without warning. CanApplyUpdate reads the device's current work through GateDeps and either returns a refusal naming the reason or holds the device still until the restart. Indexing, optimizing, scraping, an active backup and a token write are hard refusals: each is either data being written or work that would be lost. Running media, background media and an active playlist are refusals a person can override, because they are the user's own session and only the user knows whether it matters. Power is refused below 20% on battery for a manual install and below 40% for an automatic one, and an unknown power source is refused outright unless a person forces it. The gate takes the restore gate and then the media gate, in that order, because that is the order the rest of the service takes them and the other way round is a lock inversion. It holds both until the restart so nothing can start underneath the install. The power reading is taken again in PreQuiesce, at the last point the install can still be called off, because a download can easily outlast a charger being unplugged. Reader writes are recorded against the reader doing them, so the gate asks State.AnyReaderWriteActive rather than about a single reader ID. Alongside the gate: RolloutEligible places a device in a stable bucket from its device ID and the release tag, so widening a rollout keeps the devices that already have the release while a new release draws an unrelated set, and a device with no ID waits for a full rollout rather than silently landing in the first wave. progressReporter turns stage changes and downloaded byte counts into the progress values the API forwards to clients, throttled to twice a second.
Applying an update decides what code the device runs from then on and stops whatever is playing to do it, so it is now its own capability rather than something any admin-resolved request can do. update.apply is also in authenticatedCapabilities, which means it needs a request from the device itself or from a paired client: an unpaired remote request resolves to admin for backward compatibility, and handing that a binary replacement is too much. update.check is closed to unpaired remote clients too, because a check makes the device fetch signed metadata and write the result to disk. update.apply reports its refusals from the updater's gate, so a client can tell a low battery from a running game from a backup in progress, and can offer force only where the gate says forcing is allowed. update.check returns the same gate reading, so a client knows what is in the way before it shows an update button rather than finding out from a failed apply. An update in progress reports its stage and downloaded bytes through the new update.state notification. The three top-level update keys are replaced by an [updates] table with channel, check and install. This is a breaking config change with no migration: the old keys were only ever set on internal builds, and carrying them forward would be permanent cruft.
Covers the update.apply capability and its authenticated-connection requirement, the refusal reasons update.check and update.apply report and which of them force can override, the update.state notification, and the updateChannel, updateCheck and updateInstall settings replacing the old top-level config keys.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 25 minutes Limit details: You’ve used the included review currently available. Your 90 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits within each organization. For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (53)
Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
An update replaces the running binary and restarts the service. Until now
the only thing that could stop one was another update already running, so
applying an update mid-index, mid-backup, mid-token-write or mid-game took
all of it away without warning, and nothing checked whether the device was
about to run out of battery.
Gate
updater.CanApplyUpdatereads what the device is doing and either refuseswith a named reason or holds the device still until the restart.
Hard refusals: media indexing, optimizing, scraping, an active backup or
restore, and a reader part-way through writing a token. Forceable
refusals: active media, background media, an active playlist — the user's
own session, where only the user knows whether it matters.
Power is refused below 20% on battery for a manual install and below 40%
for an automatic one. An unknown power source is refused unless a person
forces it. The reading is taken again in
PreQuiesce, at the last pointthe install can still be called off, because a download can outlast a
charger being unplugged.
The gate takes the restore gate and then the media gate, matching the
order the rest of the service takes them, and holds both until the restart
so nothing starts underneath the install.
Power
New
pkg/helpers/powerreports no battery, external power, battery with acharge percentage, or unknown. Linux reads
/sys/class/power_supplythrough afero, Windows calls
GetSystemPowerStatus, darwin parsespmset -g batt. Unknown means "could lose power at any moment", not "nobattery".
platforms.PowerStatusprefers a platform's own provider whenit has one.
API
update.applyis now its own capability, and one that requires anauthenticated connection: a request from the device itself or from a
paired client. Unpaired remote clients resolve to admin for backward
compatibility, which is not enough to replace the running binary.
update.checkis closed to them as well, since a check makes the devicefetch signed metadata and write to disk.
Both methods report the gate's reading, so a client can show what is in
the way and offer force only where forcing is allowed. Progress during an
install arrives on the new
update.statenotification.Rollout
RolloutEligiblebuckets a device from its device ID and the release tag,so widening a rollout keeps the devices that already have the release
while a new release draws an unrelated set. A device with no ID waits for
a full rollout rather than landing in the first wave.
Breaking change
The top-level
auto_update,auto_update_installandupdate_channelconfig keys are replaced by an
[updates]table withchannel,checkand
install. There is no migration: the old keys were only ever set oninternal builds.
updateInstallis recorded and reported but nothing acts on it yet; theautomatic install scheduler is a follow-up.