fix: preserve staged update dir when pruning orphaned updates on macOS#50210
fix: preserve staged update dir when pruning orphaned updates on macOS#50210VerteDinde merged 1 commit intomainfrom
Conversation
…macOS The previous squirrel.mac patch cleaned up all staged update directories before starting a new download. This kept disk usage bounded but broke quitAndInstall() if called while a subsequent checkForUpdates() was in flight — the already-staged bundle would be deleted out from under it. This reworks the patch to read ShipItState.plist and preserve the directory it references, deleting only truly orphaned update.XXXXXXX directories. Disk footprint stays bounded (at most 2 dirs: staged + in-progress) and quitAndInstall() remains safe mid-check. Also adds test coverage for the quitAndInstall/checkForUpdates race and a triple-stack scenario where 3 updates arrive without a restart. Refs #50200
|
This makes sense to me. The original thinking in my first patch was that if you get
Both of these combine into a net worse behavior for certain apps despite the unbounded growth bug being fixed. Thanks for the fix Sam and sorry for the bug, @nikwen! |
VerteDinde
left a comment
There was a problem hiding this comment.
Code change looks good to me - it should address the issue and is pretty heavily tested 👍
There was a problem hiding this comment.
Thanks for working on this!
I believe it's still possible to break the logic with this patch if multiple downloads from multiple autoUpdater.checkForUpdates() calls are running in parallel (which has been totally fine historically).
Starting a new download might delete the directory of an update that is still in progress.
Example of a race condition:
- Process A: Update 1 starts downloading
- Process A: Update 1 finishes downloading
- Process B: Update 2 starts downloading
- Process B: As a result, the update directory for update 1 is being wiped
- Process A: Squirrel updates
ShipItState.plistto point to update directory 1, which no longer exists
It is a narrow time window, but this is the autoUpdater, which should be safe against any kinds of race conditions.
I'm thinking about how to solve this better.
Some ideas:
- After we update the
ShipItState.plistto a different directory, delete the directory that it previously referenced- This might not be fully safe though because a race condition with
relaunchToInstallUpdatemight write back the old URL
- This might not be fully safe though because a race condition with
- Keeping a list of updates that are safe to delete and only deleting those
I'll think about it further and update here when I have a good idea.
|
How about this? After we update the The delay is a generous buffer so that no in-progress call to That would be a pretty minimal patch to |
This isn't true. |
|
Still taking a look to add my two cents. I did take a look at our documentation just to sanity check user expectations. It seems we do call out the fact that electron/docs/api/auto-updater.md Lines 167 to 169 in 3678edf |
| + NSParameterAssert(storageURL != nil); | ||
| + | ||
| + NSFileManager *manager = [[NSFileManager alloc] init]; | ||
| + NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:storageURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) { |
There was a problem hiding this comment.
I'm assuming we expect Squirrel.Mac to always put every update in the same shallow storageURL? That's a sensible behavior and Squirrel.Mac is fairly stable. I did just want to call out what I see as a potentially hidden assumption here.
There was a problem hiding this comment.
IMO this is fine because of the e2e test. It expects a specific number of files so if 0 start appearing it will fail. Also yeah, squirrel.mac is fairly changed
There was a problem hiding this comment.
Eh, if, for some reason, one or so still stuck around in storageURL, and then others were placed elsewhere, that wouldn't result in zero appearing. Again, I don't think there will be any changes to where updates are placed anytime in the near future. Just wanted to call it out!
nikwen
left a comment
There was a problem hiding this comment.
This isn't true.
checkForUpdatescan't be run multiple times concurrently
That's great news! Thanks for the correction. 🙌
With that information, the patch looks good to me. It should fix the issue.
On a side note, the tests are beautiful! 👍
patches/squirrel.mac/fix_clean_up_orphaned_staged_updates_before_downloading_new_update.patch
Show resolved
Hide resolved
|
Release Notes Persisted
|
|
I have automatically backported this PR to "39-x-y", please check out #50215 |
|
I have automatically backported this PR to "40-x-y", please check out #50216 |
|
I have automatically backported this PR to "41-x-y", please check out #50217 |
The previous squirrel.mac patch cleaned up all staged update directories before starting a new download. This kept disk usage bounded, but it broke
quitAndInstall()if called while a subsequentcheckForUpdates()was in flight — the already-staged bundle would be deleted out from under it.This reworks the patch to:
ShipItState.plistand preserve the directory it referencesupdate.XXXXXXXdirectoriespruneUpdateDirectoriesstill does a full wipe on launchDisk footprint stays bounded (at most 2 dirs: staged + in-progress) and
quitAndInstall()remains safe to call mid-check.Fixes #50200
Test plan
e test --runners=main -g "autoUpdater behavior"passes on macOSshould preserve the staged update directory and prune orphaned ones when a new update is downloaded— asserts 1 dir during first download, 2 dirs during second (staged preserved + new), and that the original staged dir is among themupdate-racefixture — callsquitAndInstall()while a secondcheckForUpdates()is downloading; the staged install should succeedupdate-triple-stackfixture — 3 sequential update downloads without restart; directory count never exceeds 2Notes: Fixed an issue on macOS where calling
autoUpdater.quitAndInstall()could fail ifcheckForUpdates()was called again after an update was already downloaded.