mirror of
https://github.com/storytold/craft-launcher.git
synced 2026-10-13 23:08:16 +00:00
macOS: attach disk images one at a time, and retry when the helper is busy
diskimages-helper turns an attach away ('Resource temporarily unavailable')
while it is busy with another image. The DMG tests added in #51 run in
parallel and made this a flaky failure (about 1 run in 5 locally); the
launcher hits the same thing when several apps update at once. Attaches now
take a process-wide lock (which the tests' hdiutil create shares) and back
off and retry (1, 2, 4, 8 s) when something outside the launcher holds it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e69164d8e5
commit
8d76d193db
2 files changed
+31
-2
No files matched your search
@@ -732,13 +732,39 @@ pub mod macos {
|
||||
}
|
||||
}
|
||||
|
||||
/// Held while this process asks diskimages-helper for something (an attach here; the tests'
|
||||
/// `hdiutil create` too), since it turns concurrent requests away.
|
||||
pub(crate) static DISK_IMAGES: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||
|
||||
pub fn install_dmg(app: &CraftApp, dmg: &Path, existing: Option<&Installed>, paths: &Paths, expect: &Expect<'_>) -> Result<PathBuf, String> {
|
||||
let mount = paths.cache.join(format!("mount-{}", app.id));
|
||||
let _ = detach(&mount);
|
||||
std::fs::create_dir_all(&mount).map_err(|e| e.to_string())?;
|
||||
let (Some(dmg_s), Some(mount_s)) = (dmg.to_str(), mount.to_str()) else { return Err("unusual characters in the cache folder's path".into()) };
|
||||
process::capture("hdiutil", &["attach", "-nobrowse", "-readonly", "-noautoopen", "-mountpoint", mount_s, dmg_s], Duration::from_secs(120))
|
||||
.ok_or("couldn't open the disk image")?;
|
||||
// diskimages-helper refuses an attach while it is busy with another image ("Resource
|
||||
// temporarily unavailable"): attach one image at a time (several apps may be updating at
|
||||
// once), and wait a little and try again when something outside the launcher holds it.
|
||||
let attached = {
|
||||
let _one_at_a_time = DISK_IMAGES.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let attach = || {
|
||||
process::capture(
|
||||
"hdiutil",
|
||||
&["attach", "-nobrowse", "-readonly", "-noautoopen", "-mountpoint", mount_s, dmg_s],
|
||||
Duration::from_secs(120),
|
||||
)
|
||||
};
|
||||
let mut attached = attach();
|
||||
for wait in [1, 2, 4, 8] {
|
||||
if attached.is_some() {
|
||||
break;
|
||||
}
|
||||
let _ = detach(&mount);
|
||||
std::thread::sleep(Duration::from_secs(wait));
|
||||
attached = attach();
|
||||
}
|
||||
attached
|
||||
};
|
||||
attached.ok_or("couldn't open the disk image")?;
|
||||
let result = copy_app(app, &mount, existing, expect);
|
||||
let _ = detach(&mount);
|
||||
let _ = std::fs::remove_dir(&mount);
|
||||
@@ -1358,6 +1384,7 @@ mod tests {
|
||||
let src = dir.join(format!("src-{version}"));
|
||||
bundle(&src, version);
|
||||
let dmg = dir.join(format!("photocraft-{version}-macos-universal.dmg"));
|
||||
let _one_at_a_time = super::macos::DISK_IMAGES.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let ok = std::process::Command::new("hdiutil")
|
||||
.args(["create", "-quiet", "-format", "UDZO", "-volname", "PhotoCraft", "-srcfolder"])
|
||||
.arg(&src)
|
||||
|
||||
@@ -357,6 +357,7 @@ mod tests {
|
||||
let src = dir.path().join("src");
|
||||
bundle(&src, "0.2.0");
|
||||
let dmg = dir.path().join("artcraft-launcher-0.2.0-macos-universal.dmg");
|
||||
let one_at_a_time = install::macos::DISK_IMAGES.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let ok = std::process::Command::new("hdiutil")
|
||||
.args(["create", "-quiet", "-format", "UDZO", "-volname", "ArtCraft Launcher 0.2.0", "-srcfolder"])
|
||||
.arg(&src)
|
||||
@@ -365,6 +366,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.success();
|
||||
assert!(ok, "hdiutil create failed");
|
||||
drop(one_at_a_time);
|
||||
let current = apps.join("ArtCraft Launcher.app");
|
||||
let existing = Installed { version: None, source: InstallSource::MacApp, location: current.display().to_string(), package: None };
|
||||
// An unsigned test bundle: the signature check has its own tests in `install`.
|
||||
|
||||
Reference in new issue
Block a user