From 3de59fcc49c151915196a246e375574c09d58532 Mon Sep 17 00:00:00 2001 From: Ritam Mukherjee Date: Wed, 22 May 2024 21:03:54 +0530 Subject: [PATCH 1/5] feat: allows cli to serve locally cached server when update service not available --- cli/src/commands/serve_web.rs | 24 ++++++++++++++++++++++-- cli/src/download_cache.rs | 25 ++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/cli/src/commands/serve_web.rs b/cli/src/commands/serve_web.rs index fba92723426..313386a380c 100644 --- a/cli/src/commands/serve_web.rs +++ b/cli/src/commands/serve_web.rs @@ -516,7 +516,7 @@ impl ConnectionManager { platform, args, log: ctx.log.clone(), - cache: DownloadCache::new(ctx.paths.web_server_storage()), + cache: DownloadCache::load(ctx.paths.web_server_storage()), update_service: UpdateService::new( ctx.log.clone(), Arc::new(ReqwestSimpleHttp::with_client(ctx.http.clone())), @@ -544,6 +544,7 @@ impl ConnectionManager { pub async fn get_latest_release(&self) -> Result { let mut latest = self.latest_version.lock().await; let now = Instant::now(); + let target_kind = TargetKind::Web; if let Some((checked_at, release)) = &*latest { if checked_at.elapsed() < Duration::from_secs(RELEASE_CACHE_SECS) { return Ok(release.clone()); @@ -558,7 +559,7 @@ impl ConnectionManager { let release = self .update_service - .get_latest_commit(self.platform, TargetKind::Web, quality) + .get_latest_commit(self.platform, target_kind, quality) .await .map_err(|e| CodeError::UpdateCheckFailed(e.to_string())); @@ -568,6 +569,25 @@ impl ConnectionManager { return Ok(previous.clone()); } + // If the update service is unavailable and we have cached data, use that + if let Err(e) = &release { + warning!(self.log, "error getting latest release: {}", e); + if let Some(latest_commit) = self.cache.get().first() { + warning!(self.log, "using latest release available from cache"); + let release = Release { + name: String::from("0.0.0"), // Version information not stored on cache + commit: latest_commit.clone(), + platform: self.platform, + target: target_kind, + quality + }; + + *latest = Some((now, release.clone())); + + return Ok(release) + } + } + let release = release?; debug!(self.log, "refreshed latest release: {}", release); *latest = Some((now, release.clone())); diff --git a/cli/src/download_cache.rs b/cli/src/download_cache.rs index d3f05d2237f..5f245e810a1 100644 --- a/cli/src/download_cache.rs +++ b/cli/src/download_cache.rs @@ -20,6 +20,7 @@ const KEEP_LRU: usize = 5; const STAGING_SUFFIX: &str = ".staging"; const RENAME_ATTEMPTS: u32 = 20; const RENAME_DELAY: std::time::Duration = std::time::Duration::from_millis(200); +const PERSISTED_STATE_FILE_NAME: &str = "lru.json"; #[derive(Clone)] pub struct DownloadCache { @@ -30,11 +31,33 @@ pub struct DownloadCache { impl DownloadCache { pub fn new(path: PathBuf) -> DownloadCache { DownloadCache { - state: PersistedState::new(path.join("lru.json")), + state: PersistedState::new(path.join(PERSISTED_STATE_FILE_NAME)), path, } } + /// Gets an DownloadCache with previously persisted value if it exists + /// on the persistant storage, else returns a new DownloadCache. + pub fn load(path: PathBuf) -> DownloadCache { + let state = PersistedState::>::new(path.join(PERSISTED_STATE_FILE_NAME)); + match state.load().is_empty() { + true => DownloadCache { + state: PersistedState::new(path.join(PERSISTED_STATE_FILE_NAME)), + path, + }, + false => DownloadCache { + state, + path, + } + } + } + + /// Gets the value stored on the state + pub fn get(&self) -> Vec { + let state_value = self.state.load(); + state_value + } + /// Gets the download cache path. Names of cache entries can be formed by /// joining them to the path. pub fn path(&self) -> &Path { From 23f2247b7274c2a750d3e7fa778362bf16a33ddb Mon Sep 17 00:00:00 2001 From: Ritam Mukherjee Date: Wed, 22 May 2024 22:33:16 +0530 Subject: [PATCH 2/5] refactor: fix clippy let-and-return error in linting --- cli/src/download_cache.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/src/download_cache.rs b/cli/src/download_cache.rs index 5f245e810a1..dae12a6ea62 100644 --- a/cli/src/download_cache.rs +++ b/cli/src/download_cache.rs @@ -54,8 +54,7 @@ impl DownloadCache { /// Gets the value stored on the state pub fn get(&self) -> Vec { - let state_value = self.state.load(); - state_value + self.state.load() } /// Gets the download cache path. Names of cache entries can be formed by From bc5e7b51a2d538c0d36da7e1a0dd0d9829707a82 Mon Sep 17 00:00:00 2001 From: Ritam Mukherjee Date: Wed, 22 May 2024 23:05:10 +0530 Subject: [PATCH 3/5] refactor: removed unnecessary check for empty vector in DownloadCache load --- cli/src/download_cache.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cli/src/download_cache.rs b/cli/src/download_cache.rs index dae12a6ea62..5a343315d86 100644 --- a/cli/src/download_cache.rs +++ b/cli/src/download_cache.rs @@ -40,15 +40,10 @@ impl DownloadCache { /// on the persistant storage, else returns a new DownloadCache. pub fn load(path: PathBuf) -> DownloadCache { let state = PersistedState::>::new(path.join(PERSISTED_STATE_FILE_NAME)); - match state.load().is_empty() { - true => DownloadCache { - state: PersistedState::new(path.join(PERSISTED_STATE_FILE_NAME)), - path, - }, - false => DownloadCache { - state, - path, - } + state.load(); + DownloadCache { + state, + path, } } From 785aaa3fdf3f80b9c7147777b4e28866f32d1e58 Mon Sep 17 00:00:00 2001 From: Ritam Date: Thu, 15 Aug 2024 18:55:06 +0530 Subject: [PATCH 4/5] refactor: moved the cli serve-web cache seeding to ConnectionManager initialization from get_latest_release --- cli/src/commands/serve_web.rs | 55 +++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/cli/src/commands/serve_web.rs b/cli/src/commands/serve_web.rs index 96e9c08548d..3528bcb7ab6 100644 --- a/cli/src/commands/serve_web.rs +++ b/cli/src/commands/serve_web.rs @@ -538,18 +538,47 @@ impl ConnectionManager { pub fn new(ctx: &CommandContext, platform: Platform, args: ServeWebArgs) -> Arc { let base_path = normalize_base_path(args.server_base_path.as_deref().unwrap_or_default()); + let cache = DownloadCache::load(ctx.paths.web_server_storage()); + let latest_version: tokio::sync::Mutex>; + let target_kind = TargetKind::Web; + + //Set the instant to now minus the RELEASE_CACHE_SECS + //This allows the service to skip use of the cache for the first run + let instant = Instant::now() - Duration::from_secs(RELEASE_CACHE_SECS); + + let quality = VSCODE_CLI_QUALITY + .map_or(Quality::Stable, |q| { + match Quality::try_from(q) { + Ok(q) => q, + Err(_) => Quality::Stable + } + }); + + if let Some(latest_commit) = cache.get().first() { + let release = Release { + name: String::from("0.0.0"), // Version information not stored on cache + commit: latest_commit.clone(), + platform, + target: target_kind, + quality + }; + latest_version = tokio::sync::Mutex::new(Some((instant, release))); + } else { + latest_version = tokio::sync::Mutex::default(); + } + Arc::new(Self { platform, args, base_path, log: ctx.log.clone(), - cache: DownloadCache::load(ctx.paths.web_server_storage()), + cache, update_service: UpdateService::new( ctx.log.clone(), Arc::new(ReqwestSimpleHttp::with_client(ctx.http.clone())), ), state: ConnectionStateMap::default(), - latest_version: tokio::sync::Mutex::default(), + latest_version, }) } @@ -591,30 +620,12 @@ impl ConnectionManager { .map_err(|e| CodeError::UpdateCheckFailed(e.to_string())); // If the update service is unavailable and we have stale data, use that - if let (Err(e), Some((_, previous))) = (&release, &*latest) { + if let (Err(e), Some((_, previous))) = (&release, latest.clone()) { warning!(self.log, "error getting latest release, using stale: {}", e); + *latest = Some((now, previous.clone())); return Ok(previous.clone()); } - // If the update service is unavailable and we have cached data, use that - if let Err(e) = &release { - warning!(self.log, "error getting latest release: {}", e); - if let Some(latest_commit) = self.cache.get().first() { - warning!(self.log, "using latest release available from cache"); - let release = Release { - name: String::from("0.0.0"), // Version information not stored on cache - commit: latest_commit.clone(), - platform: self.platform, - target: target_kind, - quality - }; - - *latest = Some((now, release.clone())); - - return Ok(release) - } - } - let release = release?; debug!(self.log, "refreshed latest release: {}", release); *latest = Some((now, release.clone())); From b97dc796498648db409fc082a5a3032c4c0cc642 Mon Sep 17 00:00:00 2001 From: Ritam Date: Sun, 25 Aug 2024 16:10:27 +0530 Subject: [PATCH 5/5] feat: serve-web now checks for update every 1 hour by default and can be configured by the flag "update_check_interval" --- cli/src/commands/args.rs | 4 ++ cli/src/commands/serve_web.rs | 72 +++++++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 8 deletions(-) diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index 101f1eac29f..895dad08f95 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -222,6 +222,10 @@ pub struct ServeWebArgs { /// Set the root path for extensions. #[clap(long)] pub extensions_dir: Option, + /// Set update check interval in seconds, defaults to 3600 seconds. Set to 0 to disable update checks + #[clap(long)] + pub update_check_interval: Option, + } #[derive(Args, Debug, Clone)] diff --git a/cli/src/commands/serve_web.rs b/cli/src/commands/serve_web.rs index 3528bcb7ab6..1e227402808 100644 --- a/cli/src/commands/serve_web.rs +++ b/cli/src/commands/serve_web.rs @@ -15,7 +15,7 @@ use std::time::{Duration, Instant}; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Request, Response, Server}; use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::pin; +use tokio::{pin,time}; use crate::async_pipe::{ get_socket_name, get_socket_rw_stream, listen_socket_rw_stream, AsyncPipe, @@ -86,7 +86,15 @@ pub async fn serve_web(ctx: CommandContext, mut args: ServeWebArgs) -> Result = ConnectionManager::new(&ctx, platform, args.clone()); + + let update_check_interval = args.update_check_interval.unwrap_or(3600); + + if update_check_interval > 0 { + // Start the update checker + cm.clone().start_update_checker(Duration::from_secs(update_check_interval)); + } + let key = get_server_key_half(&ctx.paths); let make_svc = move || { let ctx = HandleContext { @@ -175,7 +183,7 @@ async fn handle_proxied(ctx: &HandleContext, req: Request) -> Response r, Err(e) => { error!(ctx.log, "error getting latest version: {}", e); @@ -582,6 +590,33 @@ impl ConnectionManager { }) } + // spawns a task that checks for updates every n seconds duration + pub fn start_update_checker(self: Arc, duration: Duration) { + debug!(self.log, "starting update checker"); + tokio::spawn(async move { + let mut interval = time::interval(duration); + loop { + interval.tick().await; + debug!(self.log, "checking for updates"); + match self.get_latest_release().await { + Ok(_) => {}, + Err(e) => { + error!(self.log, "error getting latest version: {}", e); + } + }; + } + }); + } + + // Returns the latest release, available on the cache + pub async fn get_release_from_cache(&self) -> Result { + let latest = self.latest_version.lock().await; + if let Some((_, release)) = &*latest { + return Ok(release.clone()); + } + Err(CodeError::ServerNotYetDownloaded) + } + /// Gets a connection to a server version pub async fn get_connection( &self, @@ -601,11 +636,6 @@ impl ConnectionManager { let mut latest = self.latest_version.lock().await; let now = Instant::now(); let target_kind = TargetKind::Web; - if let Some((checked_at, release)) = &*latest { - if checked_at.elapsed() < Duration::from_secs(RELEASE_CACHE_SECS) { - return Ok(release.clone()); - } - } let quality = VSCODE_CLI_QUALITY .ok_or_else(|| CodeError::UpdatesNotConfigured("no configured quality")) @@ -626,6 +656,32 @@ impl ConnectionManager { return Ok(previous.clone()); } + // If the new release and previous release are different, download the new version + if let Ok(new_release) = &release { + let (_, previous_release) = latest.clone().unwrap_or((Instant::now(), Release { + name: String::from("0.0.0"), + commit: String::from("0.0.0"), + platform:self.platform, + target: target_kind, + quality + })); + if new_release.commit != previous_release.commit { + match self.get_version_data_inner(new_release.clone()) { + Ok(mut r) => { + match r.wait().await { + Ok(_) => {}, + Err(e) => { + info!(self.log, "{}", e); + } + }; + }, + Err(e) => { + info!(self.log, "{}", e); + } + } + } + } + let release = release?; debug!(self.log, "refreshed latest release: {}", release); *latest = Some((now, release.clone()));