Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

If nightly or pr check is going slowly inform user #1097

Merged
merged 6 commits into from
Nov 23, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 117 additions & 58 deletions src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::utils::get_bin_dir;
use crate::utils::get_julianightlies_base_url;
use crate::utils::get_juliaserver_base_url;
use crate::utils::is_valid_julia_path;
use anyhow::{anyhow, bail, Context, Result};
use anyhow::{anyhow, bail, Context, Error, Result};
use bstr::ByteSlice;
use bstr::ByteVec;
use console::style;
Expand Down Expand Up @@ -1430,8 +1430,6 @@ pub fn update_version_db(paths: &GlobalPaths) -> Result<()> {
let online_dbversion = download_juliaup_version(&dbversion_url.to_string())
.with_context(|| "Failed to download current version db version.")?;

let direct_download_etags = download_direct_download_etags(&old_config_file.data)?;

let bundled_dbversion = get_bundled_dbversion()
.with_context(|| "Failed to determine the bundled version db version.")?;

Expand Down Expand Up @@ -1463,6 +1461,8 @@ pub fn update_version_db(paths: &GlobalPaths) -> Result<()> {
delete_old_version_db = true;
}

let direct_download_etags = download_direct_download_etags(&old_config_file.data)?;

let mut new_config_file = load_mut_config_db(paths).with_context(|| {
"`run_command_update_version_db` command failed to load configuration db."
})?;
Expand Down Expand Up @@ -1520,83 +1520,142 @@ pub fn update_version_db(paths: &GlobalPaths) -> Result<()> {
Ok(())
}

// A generic function to run a function with a timeout and a message to inform the user why it is taking so long
fn run_with_slow_message<F, R>(func: F, timeout_secs: u64, message: &str) -> Result<R, Error>
where
F: FnOnce() -> Result<R, Error> + Send + 'static,
R: Send + 'static,
{
use std::sync::mpsc::channel;
use std::thread;
use std::time::Duration;

let (tx, rx) = channel();

// Run the function in a separate thread
thread::spawn(move || {
let result = func();
tx.send(result).unwrap();
});

// Attempt to receive the result with a timeout
match rx.recv_timeout(Duration::from_secs(timeout_secs)) {
Ok(result) => result,
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
// Function has not completed within timeout_secs seconds, inform why
eprintln!("{}", message);

// Now wait for the function to complete
let result = rx.recv().unwrap();
result
}
Err(e) => panic!("Error receiving result: {:?}", e),
}
}

#[cfg(windows)]
fn download_direct_download_etags(config_data: &JuliaupConfig) -> Result<Vec<(String, String)>> {
use windows::core::HSTRING;
use windows::Foundation::Uri;
use windows::Web::Http::HttpClient;
use windows::Web::Http::HttpMethod;
use windows::Web::Http::HttpRequestMessage;

let http_client =
windows::Web::Http::HttpClient::new().with_context(|| "Failed to create HttpClient.")?;
let http_client = HttpClient::new().with_context(|| "Failed to create HttpClient.")?;

let requests: Vec<_> = config_data
.installed_channels
.iter()
.filter_map(|(channel_name, channel)| {
if let JuliaupConfigChannel::DirectDownloadChannel {
path: _,
url,
local_etag: _,
server_etag: _,
version: _,
} = channel
{
let request_uri =
windows::Foundation::Uri::CreateUri(&windows::core::HSTRING::from(url))
.with_context(|| "Failed to convert url string to Uri.")
.unwrap();
let mut requests = Vec::new();

let request =
HttpRequestMessage::Create(&HttpMethod::Head().unwrap(), &request_uri).unwrap();
for (channel_name, channel) in &config_data.installed_channels {
if let JuliaupConfigChannel::DirectDownloadChannel { url, .. } = channel {
let http_client = http_client.clone();
let url_clone = url.clone();
let channel_name_clone = channel_name.clone();
let message = format!(
"{} for new version on channel '{}' is taking a while... This can be slow due to server caching",
style("Checking").green().bold(),
channel_name
);

let request = http_client.SendRequestAsync(&request).unwrap();
let etag = run_with_slow_message(
move || {
let request_uri = Uri::CreateUri(&HSTRING::from(&url_clone))
.with_context(|| format!("Failed to create URI from {}", &url_clone))?;

Some((channel_name, request))
} else {
None
}
})
.collect();
let request = HttpRequestMessage::Create(&HttpMethod::Head()?, &request_uri)
.with_context(|| "Failed to create HttpRequestMessage.")?;

let requests: Vec<_> = requests
.into_iter()
.map(|(channel_name, request)| {
(
channel_name.clone(),
request
.get()
.unwrap()
.Headers()
.unwrap()
.Lookup(&HSTRING::from("etag"))
.unwrap()
.to_string(),
)
})
.collect();
let async_op = http_client
.SendRequestAsync(&request)
.map_err(|e| anyhow!("Failed to send request: {:?}", e))?;

let response = async_op
.get()
.map_err(|e| anyhow!("Failed to get response: {:?}", e))?;

let headers = response
.Headers()
.map_err(|e| anyhow!("Failed to get headers: {:?}", e))?;

let etag = headers
.Lookup(&HSTRING::from("ETag"))
.map_err(|e| anyhow!("ETag header not found: {:?}", e))?
.to_string();

Ok::<String, anyhow::Error>(etag)
},
3, // Timeout in seconds
&message,
)?;

requests.push((channel_name_clone, etag));
}
}

Ok(requests)
}

#[cfg(not(windows))]
fn download_direct_download_etags(config_data: &JuliaupConfig) -> Result<Vec<(String, String)>> {
let client = reqwest::blocking::Client::new();
use std::sync::Arc;

let client = Arc::new(reqwest::blocking::Client::new());

let mut requests = Vec::new();

for (channel_name, channel) in &config_data.installed_channels {
if let JuliaupConfigChannel::DirectDownloadChannel { url, .. } = channel {
let etag = client
.head(url)
.send()?
.headers()
.get("etag")
.ok_or_else(|| anyhow!("ETag header not found in response"))?
.to_str()
.map_err(|e| anyhow!("Failed to parse ETag header: {}", e))?
.to_string();

requests.push((channel_name.clone(), etag));
let client = Arc::clone(&client);
let url_clone = url.clone();
let channel_name_clone = channel_name.clone();
let message = format!(
"{} for new version on channel '{}' is taking a while... This can be slow due to server caching",
style("Checking").green().bold(),
channel_name
);

let etag = run_with_slow_message(
move || {
let response = client.head(&url_clone).send().with_context(|| {
format!("Failed to send HEAD request to {}", &url_clone)
})?;

let etag = response
.headers()
.get("etag")
.ok_or_else(|| {
anyhow!("ETag header not found in response from {}", &url_clone)
})?
.to_str()
.map_err(|e| anyhow!("Failed to parse ETag header: {}", e))?
.to_string();

Ok::<String, anyhow::Error>(etag)
},
3, // Timeout in seconds
&message,
)?;

requests.push((channel_name_clone, etag));
}
}

Expand Down