From 7c8ccd31a7a99d5a137e28fd07772b476a1b1aa9 Mon Sep 17 00:00:00 2001 From: David Anthoff Date: Wed, 16 Oct 2024 18:16:41 -0700 Subject: [PATCH] Fix locking for version db update --- src/operations.rs | 203 +++++++++++++++++++++++++++------------------- 1 file changed, 119 insertions(+), 84 deletions(-) diff --git a/src/operations.rs b/src/operations.rs index 2879cea9..87e0c86a 100644 --- a/src/operations.rs +++ b/src/operations.rs @@ -1,3 +1,4 @@ +use crate::config_file::load_config_db; use crate::config_file::load_mut_config_db; use crate::config_file::save_config_db; use crate::config_file::JuliaupConfig; @@ -15,12 +16,15 @@ use crate::utils::is_valid_julia_path; use anyhow::{anyhow, bail, Context, Result}; use bstr::ByteSlice; use bstr::ByteVec; +use chrono::DateTime; +use chrono::Utc; use console::style; #[cfg(not(target_os = "freebsd"))] use flate2::read::GzDecoder; use indicatif::{ProgressBar, ProgressStyle}; use indoc::formatdoc; use semver::Version; +use tempfile::TempPath; #[cfg(not(windows))] use std::os::unix::fs::PermissionsExt; #[cfg(not(target_os = "freebsd"))] @@ -1341,46 +1345,119 @@ mod tests { } pub fn update_version_db(paths: &GlobalPaths) -> Result<()> { - let mut config_file = load_mut_config_db(paths).with_context(|| { - "`run_command_update_version_db` command failed to load configuration db." - })?; + let old_last_version_db_update: Option>; + let direct_download_etags: Vec<(String, String)>; + let temp_versiondb_download_path: Option; + let mut delete_old_version_db: bool = false; - #[cfg(feature = "selfupdate")] - let juliaup_channel = match &config_file.self_data.juliaup_channel { - Some(juliaup_channel) => juliaup_channel.to_string(), - None => "release".to_string(), - }; + { + let config_file = load_config_db(paths).with_context(|| { + "`run_command_update_version_db` command failed to load configuration db." + })?; - // TODO Figure out how we can learn about the correctn Juliaup channel here - #[cfg(not(feature = "selfupdate"))] - let juliaup_channel = "release".to_string(); - - let juliaupserver_base = - get_juliaserver_base_url().with_context(|| "Failed to get Juliaup server base URL.")?; - - let dbversion_url_path = match juliaup_channel.as_str() { - "release" => "juliaup/RELEASECHANNELDBVERSION", - "releasepreview" => "juliaup/RELEASEPREVIEWCHANNELDBVERSION", - "dev" => "juliaup/DEVCHANNELDBVERSION", - _ => bail!( - "Juliaup is configured to a channel named '{}' that does not exist.", - &juliaup_channel - ), - }; + old_last_version_db_update = config_file.data.last_version_db_update; - let dbversion_url = juliaupserver_base - .join(dbversion_url_path) - .with_context(|| { - format!( - "Failed to construct a valid url from '{}' and '{}'.", - juliaupserver_base, dbversion_url_path - ) - })?; + #[cfg(feature = "selfupdate")] + let juliaup_channel = match &config_file.self_data.juliaup_channel { + Some(juliaup_channel) => juliaup_channel.to_string(), + None => "release".to_string(), + }; + + // TODO Figure out how we can learn about the correctn Juliaup channel here + #[cfg(not(feature = "selfupdate"))] + let juliaup_channel = "release".to_string(); + + let juliaupserver_base = + get_juliaserver_base_url().with_context(|| "Failed to get Juliaup server base URL.")?; + + let dbversion_url_path = match juliaup_channel.as_str() { + "release" => "juliaup/RELEASECHANNELDBVERSION", + "releasepreview" => "juliaup/RELEASEPREVIEWCHANNELDBVERSION", + "dev" => "juliaup/DEVCHANNELDBVERSION", + _ => bail!( + "Juliaup is configured to a channel named '{}' that does not exist.", + &juliaup_channel + ), + }; + + let dbversion_url = juliaupserver_base + .join(dbversion_url_path) + .with_context(|| { + format!( + "Failed to construct a valid url from '{}' and '{}'.", + juliaupserver_base, dbversion_url_path + ) + })?; + + let online_dbversion = download_juliaup_version(&dbversion_url.to_string()) + .with_context(|| "Failed to download current version db version.")?; + + direct_download_etags = download_direct_download_etags(&config_file.data)?; + + let bundled_dbversion = get_bundled_dbversion() + .with_context(|| "Failed to determine the bundled version db version.")?; + + let local_dbversion = match std::fs::OpenOptions::new() + .read(true) + .open(&paths.versiondb) + { + Ok(file) => { + let reader = BufReader::new(&file); + + if let Ok(versiondb) = + serde_json::from_reader::, JuliaupVersionDB>(reader) + { + if let Ok(version) = semver::Version::parse(&versiondb.version) { + Some(version) + } else { + None + } + } else { + None + } + } + Err(_) => None, + }; + + if online_dbversion > bundled_dbversion { + if local_dbversion.is_none() || online_dbversion > local_dbversion.unwrap() { + let onlineversiondburl = juliaupserver_base + .join(&format!( + "juliaup/versiondb/versiondb-{}-{}.json", + online_dbversion, + get_juliaup_target() + )) + .with_context(|| "Failed to construct URL for version db download.")?; + + let foo = tempfile::NamedTempFile::new_in(&paths.versiondb.parent().unwrap()).unwrap().into_temp_path() + + download_versiondb(&onlineversiondburl.to_string(), &foo) + .with_context(|| { + format!( + "Failed to download new version db from {}.", + onlineversiondburl + ) + })?; + + temp_versiondb_download_path = Some(foo); + } + } else if local_dbversion.is_some() { + // If the bundled version is up-to-date we can delete any cached version db json file + + + delete_old_version_db = true; + } + } - let online_dbversion = download_juliaup_version(&dbversion_url.to_string()) - .with_context(|| "Failed to download current version db version.")?; + let mut config_file = load_mut_config_db(paths).with_context(|| { + "`run_command_update_version_db` command failed to load configuration db." + })?; - let direct_download_etags = download_direct_download_etags(&mut config_file.data)?; + // This is our optimistic locking check: if someone changed the last modified + // field since we released the read-lock, we just give up + if config_file.data.last_version_db_update != old_last_version_db_update { + return Ok(()); + } for (channel, etag) in direct_download_etags { let channel_data = config_file.data.installed_channels.get(&channel).unwrap(); @@ -1410,63 +1487,21 @@ pub fn update_version_db(paths: &GlobalPaths) -> Result<()> { config_file.data.last_version_db_update = Some(chrono::Utc::now()); - save_config_db(&mut config_file).with_context(|| "Failed to save configuration file.")?; - - let bundled_dbversion = get_bundled_dbversion() - .with_context(|| "Failed to determine the bundled version db version.")?; - - let local_dbversion = match std::fs::OpenOptions::new() - .read(true) - .open(&paths.versiondb) - { - Ok(file) => { - let reader = BufReader::new(&file); - - if let Ok(versiondb) = - serde_json::from_reader::, JuliaupVersionDB>(reader) - { - if let Ok(version) = semver::Version::parse(&versiondb.version) { - Some(version) - } else { - None - } - } else { - None - } - } - Err(_) => None, - }; - - if online_dbversion > bundled_dbversion { - if local_dbversion.is_none() || online_dbversion > local_dbversion.unwrap() { - let onlineversiondburl = juliaupserver_base - .join(&format!( - "juliaup/versiondb/versiondb-{}-{}.json", - online_dbversion, - get_juliaup_target() - )) - .with_context(|| "Failed to construct URL for version db download.")?; - - download_versiondb(&onlineversiondburl.to_string(), &paths.versiondb).with_context( - || { - format!( - "Failed to download new version db from {}.", - onlineversiondburl - ) - }, - )?; - } - } else if local_dbversion.is_some() { - // If the bundled version is up-to-date we can delete any cached version db json file + if let Some(foo) = temp_versiondb_download_path { + std::fs::rename(&foo, &paths.versiondb)?; + } + else if delete_old_version_db { let _ = std::fs::remove_file(&paths.versiondb); } + save_config_db(&mut config_file).with_context(|| "Failed to save configuration file.")?; + Ok(()) } #[cfg(windows)] fn download_direct_download_etags( - config_data: &mut JuliaupConfig, + config_data: &JuliaupConfig, ) -> Result> { use windows::core::HSTRING; use windows::Web::Http::HttpMethod;