perf: move session autosaves off main loop
This commit is contained in:
parent
b017d21290
commit
b425260f9d
|
|
@ -128,6 +128,7 @@ pub struct App {
|
|||
pub(crate) selection_autoscroll_deadline: Option<Instant>,
|
||||
pub(crate) selection_highlight_clear_deadline: Option<Instant>,
|
||||
pub(crate) session_save_deadline: Option<Instant>,
|
||||
pub(crate) session_save_thread: Option<std::thread::JoinHandle<()>>,
|
||||
pub(crate) persist_pane_history: bool,
|
||||
pub(crate) last_render_at: Option<Instant>,
|
||||
pub(crate) suppressed_repeat_keys:
|
||||
|
|
@ -719,6 +720,7 @@ impl App {
|
|||
agent_metadata_deadline: None,
|
||||
pending_agent_resume_deadline: None,
|
||||
session_save_deadline: None,
|
||||
session_save_thread: None,
|
||||
selection_autoscroll_deadline: None,
|
||||
selection_highlight_clear_deadline: None,
|
||||
persist_pane_history: config.experimental.pane_history,
|
||||
|
|
@ -1739,6 +1741,14 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
fn unique_temp_path(name: &str) -> std::path::PathBuf {
|
||||
let stamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos();
|
||||
std::env::temp_dir().join(format!("herdr-{name}-{}-{stamp}", std::process::id()))
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn exiting_test_command() -> &'static str {
|
||||
"C:\\Windows\\System32\\whoami.exe"
|
||||
|
|
@ -4075,6 +4085,71 @@ mod tests {
|
|||
assert!(app.session_save_deadline.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn due_session_save_starts_background_writer() {
|
||||
let _guard = crate::config::test_config_env_lock().lock().unwrap();
|
||||
let config_home = unique_temp_path("background-session-save");
|
||||
std::env::set_var("XDG_CONFIG_HOME", &config_home);
|
||||
std::env::remove_var(crate::session::SESSION_ENV_VAR);
|
||||
|
||||
let mut app = test_app();
|
||||
app.no_session = false;
|
||||
app.state.workspaces = vec![Workspace::test_new("autosave")];
|
||||
app.state.ensure_test_terminals();
|
||||
app.session_save_deadline = Some(Instant::now() - Duration::from_secs(1));
|
||||
|
||||
app.handle_scheduled_tasks(Instant::now(), false);
|
||||
|
||||
assert!(app.session_save_thread.is_some());
|
||||
assert!(app.session_save_deadline.is_none());
|
||||
app.save_session_now();
|
||||
assert!(crate::session::data_dir().join("session.json").exists());
|
||||
|
||||
std::env::remove_var("XDG_CONFIG_HOME");
|
||||
let _ = std::fs::remove_dir_all(config_home);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_session_save_reschedules_when_writer_is_busy() {
|
||||
let mut app = test_app();
|
||||
app.no_session = false;
|
||||
let (release_tx, release_rx) = std::sync::mpsc::channel();
|
||||
app.session_save_thread = Some(std::thread::spawn(move || {
|
||||
let _ = release_rx.recv();
|
||||
}));
|
||||
|
||||
app.start_background_session_save();
|
||||
|
||||
assert!(app.session_save_thread.is_some());
|
||||
assert!(app.session_save_deadline.is_some());
|
||||
|
||||
release_tx.send(()).unwrap();
|
||||
app.no_session = true;
|
||||
app.save_session_now();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn final_session_save_joins_background_writer_before_returning() {
|
||||
let mut app = test_app();
|
||||
app.no_session = true;
|
||||
let (release_tx, release_rx) = std::sync::mpsc::channel();
|
||||
let (done_tx, done_rx) = std::sync::mpsc::channel();
|
||||
app.session_save_thread = Some(std::thread::spawn(move || {
|
||||
let _ = release_rx.recv();
|
||||
done_tx.send(()).unwrap();
|
||||
}));
|
||||
let releaser = std::thread::spawn(move || {
|
||||
std::thread::sleep(Duration::from_millis(30));
|
||||
release_tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
app.save_session_now();
|
||||
|
||||
releaser.join().unwrap();
|
||||
done_rx.try_recv().unwrap();
|
||||
assert!(app.session_save_thread.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_loop_deadline_includes_selection_autoscroll_deadline() {
|
||||
let mut app = test_app();
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ impl App {
|
|||
.session_save_deadline
|
||||
.is_some_and(|deadline| now >= deadline)
|
||||
{
|
||||
self.save_session_now();
|
||||
self.start_background_session_save();
|
||||
}
|
||||
|
||||
if let Some(deadline) = self
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
use std::time::Instant;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::{App, SESSION_SAVE_DEBOUNCE};
|
||||
|
||||
enum SessionSaveJob {
|
||||
Clear,
|
||||
Save {
|
||||
snapshot: crate::persist::SessionSnapshot,
|
||||
history: Option<crate::persist::SessionHistorySnapshot>,
|
||||
},
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub(super) fn schedule_session_save(&mut self) {
|
||||
if !self.no_session {
|
||||
|
|
@ -16,16 +24,23 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn save_session_now(&mut self) {
|
||||
if self.no_session {
|
||||
self.session_save_deadline = None;
|
||||
return;
|
||||
fn reap_finished_session_save(&mut self) {
|
||||
if self
|
||||
.session_save_thread
|
||||
.as_ref()
|
||||
.is_some_and(std::thread::JoinHandle::is_finished)
|
||||
{
|
||||
if let Some(thread) = self.session_save_thread.take() {
|
||||
let _ = thread.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn capture_session_save_job(&self) -> SessionSaveJob {
|
||||
if self.state.workspaces.is_empty() {
|
||||
crate::persist::clear();
|
||||
SessionSaveJob::Clear
|
||||
} else {
|
||||
let snap = crate::persist::capture(
|
||||
let snapshot = crate::persist::capture(
|
||||
&self.state.workspaces,
|
||||
&self.state.terminals,
|
||||
&self.terminal_runtimes,
|
||||
|
|
@ -38,9 +53,56 @@ impl App {
|
|||
let history = self.persist_pane_history.then(|| {
|
||||
crate::persist::capture_history(&self.state.workspaces, &self.terminal_runtimes)
|
||||
});
|
||||
crate::persist::save(&snap, history.as_ref());
|
||||
SessionSaveJob::Save { snapshot, history }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start_background_session_save(&mut self) {
|
||||
if self.no_session {
|
||||
self.session_save_deadline = None;
|
||||
return;
|
||||
}
|
||||
|
||||
self.reap_finished_session_save();
|
||||
if self.session_save_thread.is_some() {
|
||||
self.session_save_deadline = Some(Instant::now() + Duration::from_millis(250));
|
||||
return;
|
||||
}
|
||||
|
||||
let job = self.capture_session_save_job();
|
||||
self.session_save_deadline = None;
|
||||
match std::thread::Builder::new()
|
||||
.name("herdr-session-save".into())
|
||||
.spawn(move || run_session_save_job(job))
|
||||
{
|
||||
Ok(thread) => self.session_save_thread = Some(thread),
|
||||
Err(err) => {
|
||||
tracing::warn!(err = %err, "failed to spawn session save thread; saving inline");
|
||||
run_session_save_job(self.capture_session_save_job());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn save_session_now(&mut self) {
|
||||
if let Some(thread) = self.session_save_thread.take() {
|
||||
let _ = thread.join();
|
||||
}
|
||||
|
||||
if self.no_session {
|
||||
self.session_save_deadline = None;
|
||||
return;
|
||||
}
|
||||
|
||||
run_session_save_job(self.capture_session_save_job());
|
||||
self.session_save_deadline = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn run_session_save_job(job: SessionSaveJob) {
|
||||
match job {
|
||||
SessionSaveJob::Clear => crate::persist::clear(),
|
||||
SessionSaveJob::Save { snapshot, history } => {
|
||||
crate::persist::save(&snapshot, history.as_ref());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3716,7 +3716,7 @@ impl HeadlessServer {
|
|||
.session_save_deadline
|
||||
.is_some_and(|deadline| now >= deadline)
|
||||
{
|
||||
self.app.save_session_now();
|
||||
self.app.start_background_session_save();
|
||||
}
|
||||
|
||||
if let Some(deadline) = self
|
||||
|
|
|
|||
Loading…
Reference in New Issue