From 4a68c6d90b8942c81f25f465964eeb28fb5bb95f Mon Sep 17 00:00:00 2001 From: Aram Drevekenin Date: Wed, 16 Jul 2025 12:03:32 +0200 Subject: [PATCH] fix(resurrection): do not parse resurrection layouts on startup (#4292) * fix(resurrection): do not parse resurrection layouts on startup * proper error when file is corrupt * docs(changelog): add PR * fix attach command * style(fmt): rustfmt --- CHANGELOG.md | 1 + src/commands.rs | 12 ++- .../src/web_client/session_management.rs | 15 ++-- zellij-client/src/web_client/types.rs | 2 + zellij-utils/src/sessions.rs | 84 +++++++++---------- 5 files changed, 64 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 443ff569..a206b782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * fix: don't serialize when only ui elements present and provide post command disovery hook (https://github.com/zellij-org/zellij/pull/4276) * fix: use plugin `/host` folder as cwd when opening new panes (https://github.com/zellij-org/zellij/pull/4290) * fix: better command detection when serializing layouts for resurrection (https://github.com/zellij-org/zellij/pull/4287) +* fix: slow startup on very large caches (https://github.com/zellij-org/zellij/pull/4292) * fix: don't show popups in the welcome screen (https://github.com/zellij-org/zellij/pull/4294) ## [0.42.2] - 2025-04-15 diff --git a/src/commands.rs b/src/commands.rs index 952102c8..87f256d6 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -90,7 +90,7 @@ pub(crate) fn delete_all_sessions(yes: bool, force: bool) { } else { resurrectable_sessions .iter() - .filter(|(name, _, _)| !active_sessions.contains(name)) + .filter(|(name, _)| !active_sessions.contains(name)) .cloned() .collect() }; @@ -699,7 +699,15 @@ pub(crate) fn start_client(opts: CliArgs) { .and_then(|s| session_exists(&s).ok()) .unwrap_or(false); let resurrection_layout = - session_name.as_ref().and_then(|s| resurrection_layout(&s)); + session_name + .as_ref() + .and_then(|s| match resurrection_layout(&s) { + Ok(layout) => layout, + Err(e) => { + eprintln!("{}", e); + process::exit(2); + }, + }); if (create || should_create_detached) && !session_exists && resurrection_layout.is_none() diff --git a/zellij-client/src/web_client/session_management.rs b/zellij-client/src/web_client/session_management.rs index c46224fd..6e765597 100644 --- a/zellij-client/src/web_client/session_management.rs +++ b/zellij-client/src/web_client/session_management.rs @@ -113,12 +113,15 @@ pub fn spawn_session_if_needed( } else { let force_run_commands = false; let resurrection_layout = - resurrection_layout(&session_name).map(|mut resurrection_layout| { - if force_run_commands { - resurrection_layout.recursively_add_start_suspended(Some(false)); - } - resurrection_layout - }); + resurrection_layout(&session_name) + .ok() + .flatten() + .map(|mut resurrection_layout| { + if force_run_commands { + resurrection_layout.recursively_add_start_suspended(Some(false)); + } + resurrection_layout + }); match resurrection_layout { Some(resurrection_layout) => spawn_new_session( diff --git a/zellij-client/src/web_client/types.rs b/zellij-client/src/web_client/types.rs index 1a197d0d..d20edc4b 100644 --- a/zellij-client/src/web_client/types.rs +++ b/zellij-client/src/web_client/types.rs @@ -62,6 +62,8 @@ impl SessionManager for RealSessionManager { session_name: &str, ) -> Option { zellij_utils::sessions::resurrection_layout(session_name) + .ok() + .flatten() } fn spawn_session_if_needed( diff --git a/zellij-utils/src/sessions.rs b/zellij-utils/src/sessions.rs index 5ecc73db..d7976005 100644 --- a/zellij-utils/src/sessions.rs +++ b/zellij-utils/src/sessions.rs @@ -21,16 +21,17 @@ pub fn get_sessions() -> Result, io::ErrorKind> { Ok(files) => { let mut sessions = Vec::new(); files.for_each(|file| { - let file = file.unwrap(); - let file_name = file.file_name().into_string().unwrap(); - let ctime = std::fs::metadata(&file.path()) - .ok() - .and_then(|f| f.created().ok()) - .and_then(|d| d.elapsed().ok()) - .unwrap_or_default(); - let duration = Duration::from_secs(ctime.as_secs()); - if file.file_type().unwrap().is_socket() && assert_socket(&file_name) { - sessions.push((file_name, duration)); + if let Ok(file) = file { + let file_name = file.file_name().into_string().unwrap(); + let ctime = std::fs::metadata(&file.path()) + .ok() + .and_then(|f| f.created().ok()) + .and_then(|d| d.elapsed().ok()) + .unwrap_or_default(); + let duration = Duration::from_secs(ctime.as_secs()); + if file.file_type().unwrap().is_socket() && assert_socket(&file_name) { + sessions.push((file_name, duration)); + } } }); Ok(sessions) @@ -40,7 +41,7 @@ pub fn get_sessions() -> Result, io::ErrorKind> { } } -pub fn get_resurrectable_sessions() -> Vec<(String, Duration, Layout)> { +pub fn get_resurrectable_sessions() -> Vec<(String, Duration)> { match fs::read_dir(&*ZELLIJ_SESSION_INFO_CACHE_DIR) { Ok(files_in_session_info_folder) => { let files_that_are_folders = files_in_session_info_folder @@ -50,30 +51,12 @@ pub fn get_resurrectable_sessions() -> Vec<(String, Duration, Layout)> { .filter_map(|folder_name| { let layout_file_name = session_layout_cache_file_name(&folder_name.display().to_string()); - let raw_layout = match std::fs::read_to_string(&layout_file_name) { - Ok(raw_layout) => raw_layout, - Err(_e) => { - return None; - }, - }; let ctime = match std::fs::metadata(&layout_file_name) .and_then(|metadata| metadata.created()) { Ok(created) => Some(created), Err(_e) => None, }; - let layout = match Layout::from_kdl( - &raw_layout, - Some(layout_file_name.display().to_string()), - None, - None, - ) { - Ok(layout) => layout, - Err(e) => { - log::error!("Failed to parse resurrection layout file: {}", e); - return None; - }, - }; let elapsed_duration = ctime .map(|ctime| { Duration::from_secs(ctime.elapsed().ok().unwrap_or_default().as_secs()) @@ -82,7 +65,7 @@ pub fn get_resurrectable_sessions() -> Vec<(String, Duration, Layout)> { let session_name = folder_name .file_name() .map(|f| std::path::PathBuf::from(f).display().to_string())?; - Some((session_name, elapsed_duration, layout)) + Some((session_name, elapsed_duration)) }) .collect() }, @@ -291,7 +274,7 @@ pub fn list_sessions(no_formatting: bool, short: bool, reverse: bool) { let resurrectable_sessions = get_resurrectable_sessions(); let mut all_sessions: HashMap = resurrectable_sessions .iter() - .map(|(name, timestamp, _layout)| (name.clone(), (timestamp.clone(), true))) + .map(|(name, timestamp)| (name.clone(), (timestamp.clone(), true))) .collect(); for (session_name, duration) in running_sessions { all_sessions.insert(session_name.clone(), (duration, false)); @@ -362,17 +345,34 @@ pub fn session_exists(name: &str) -> Result { } // if the session is resurrecable, the returned layout is the one to be used to resurrect it -pub fn resurrection_layout(session_name_to_resurrect: &str) -> Option { - let resurrectable_sessions = get_resurrectable_sessions(); - resurrectable_sessions - .iter() - .find_map(|(name, _timestamp, layout)| { - if name == session_name_to_resurrect { - Some(layout.clone()) - } else { - None - } - }) +pub fn resurrection_layout(session_name_to_resurrect: &str) -> Result, String> { + let layout_file_name = session_layout_cache_file_name(&session_name_to_resurrect); + let raw_layout = match std::fs::read_to_string(&layout_file_name) { + Ok(raw_layout) => raw_layout, + Err(_e) => { + return Ok(None); + }, + }; + match Layout::from_kdl( + &raw_layout, + Some(layout_file_name.display().to_string()), + None, + None, + ) { + Ok(layout) => Ok(Some(layout)), + Err(e) => { + log::error!( + "Failed to parse resurrection layout file {}: {}", + layout_file_name.display(), + e + ); + return Err(format!( + "Failed to parse resurrection layout file {}: {}.", + layout_file_name.display(), + e + )); + }, + } } pub fn assert_session(name: &str) {