diff --git a/libshpool/src/daemon/server.rs b/libshpool/src/daemon/server.rs index b60bff15..09e5225c 100644 --- a/libshpool/src/daemon/server.rs +++ b/libshpool/src/daemon/server.rs @@ -977,8 +977,6 @@ impl Server { (None, Some(config::SessionRestoreMode::Lines(l))) => *l as usize, (None, _) => DEFAULT_OUTPUT_SPOOL_LINES, }, - session_restore_mode: - self.config.get().session_restore_mode.clone().unwrap_or_default(), client_connection: client_connection_rx, client_connection_ack: client_connection_ack_tx, tty_size_change: tty_size_change_rx, diff --git a/libshpool/src/daemon/shell.rs b/libshpool/src/daemon/shell.rs index 22c80c3b..94399535 100644 --- a/libshpool/src/daemon/shell.rs +++ b/libshpool/src/daemon/shell.rs @@ -203,7 +203,6 @@ pub struct ShellToClientArgs { pub conn_id: usize, pub tty_size: TtySize, pub scrollback_lines: usize, - pub session_restore_mode: config::SessionRestoreMode, pub client_connection: crossbeam_channel::Receiver, pub client_connection_ack: crossbeam_channel::Sender, pub tty_size_change: crossbeam_channel::Receiver, @@ -242,13 +241,8 @@ impl SessionInner { let closure = move || { let _s = span!(Level::INFO, "shell->client", s = name, cid = args.conn_id).entered(); - let mut output_spool = session_restore::new( - config, - // TODO: #173 - fetch session restore mode from config dynamically - &args.session_restore_mode, - &args.tty_size, - args.scrollback_lines, - ); + let mut output_spool = + session_restore::new(config, &args.tty_size, args.scrollback_lines); let mut buf: Vec = vec![0; consts::BUF_SIZE]; let mut poll_fds = [poll::PollFd::new( watchable_master.borrow_fd().ok_or(anyhow!("no master fd"))?, @@ -432,7 +426,7 @@ impl SessionInner { } if do_reattach { - info!("executing reattach protocol (mode={:?})", &args.session_restore_mode); + info!("executing reattach protocol"); let restore_buf = output_spool.restore_buffer(); if let (true, ClientConnectionMsg::New(conn)) = (!restore_buf.is_empty(), &mut client_conn) diff --git a/libshpool/src/session_restore/mod.rs b/libshpool/src/session_restore.rs similarity index 96% rename from libshpool/src/session_restore/mod.rs rename to libshpool/src/session_restore.rs index 44d0a5da..83b01a2e 100644 --- a/libshpool/src/session_restore/mod.rs +++ b/libshpool/src/session_restore.rs @@ -64,6 +64,7 @@ impl SessionSpool for NullSpool { fn resize(&mut self, _: TtySize) {} fn restore_buffer(&self) -> Vec { + info!("generating null restore buf"); vec![] } @@ -121,10 +122,10 @@ impl SessionSpool for Vt100Lines { /// Creates a spool given a `mode`. pub fn new( config: config::Manager, - mode: &SessionRestoreMode, size: &TtySize, scrollback_lines: usize, ) -> Box { + let mode = config.get().session_restore_mode.clone().unwrap_or_default(); let vterm_width = config.vterm_width(); match mode { SessionRestoreMode::Simple => Box::new(NullSpool), @@ -134,7 +135,7 @@ pub fn new( }), SessionRestoreMode::Lines(nlines) => Box::new(Vt100Lines { parser: shpool_vt100::Parser::new(size.rows, vterm_width, scrollback_lines), - nlines: *nlines, + nlines, config, }), } diff --git a/shpool/tests/attach.rs b/shpool/tests/attach.rs index 69f9f8df..f01f96e3 100644 --- a/shpool/tests/attach.rs +++ b/shpool/tests/attach.rs @@ -1621,3 +1621,72 @@ fn up_arrow_no_crash() -> anyhow::Result<()> { Ok(()) } + +#[test] +#[timeout(30000)] +#[cfg_attr(target_os = "macos", ignore)] +fn dynamic_session_restore_mode() -> anyhow::Result<()> { + let tmp_dir = tmpdir::Dir::new("/tmp/shpool-test")?; + let config_tmpl = fs::read_to_string(support::testdata_file("dynamic_restore.toml.tmpl"))?; + let config_file = tmp_dir.path().join("shpool.toml"); + + // Start with "simple" mode + let config_contents = config_tmpl.replace("REPLACE_ME", "\"simple\""); + fs::write(&config_file, &config_contents)?; + + let mut daemon_proc = support::daemon::Proc::new(&config_file, DaemonArgs::default()) + .context("starting daemon proc")?; + + // Register all expected events up front. + let mut waiter = daemon_proc.events.take().unwrap().waiter([ + "daemon-bidi-stream-done", // s1 detach + "daemon-reload-config", // config reload + "daemon-bidi-stream-done", // s2 detach + ]); + + // Create session s1 + { + let mut a1 = daemon_proc.attach("s1", Default::default()).context("starting s1")?; + let mut lm1 = a1.line_matcher()?; + a1.run_cmd("echo foo")?; + lm1.scan_until_re("foo$")?; + } // detach s1 + waiter.wait_event("daemon-bidi-stream-done")?; + + // Change config to { lines = 2 } + let config_contents = config_tmpl.replace("REPLACE_ME", "{ lines = 2 }"); + fs::write(&config_file, config_contents)?; + + waiter.wait_event("daemon-reload-config")?; + + // Create session s2 + { + let mut a2 = daemon_proc.attach("s2", Default::default()).context("starting s2")?; + let mut lm2 = a2.line_matcher()?; + a2.run_cmd("echo bar")?; + lm2.scan_until_re("bar$")?; + } // detach s2 + daemon_proc.events = Some(waiter.wait_final_event("daemon-bidi-stream-done")?); + + // Verify s1 still has "simple" behavior (no restore) + { + let mut a1 = daemon_proc.attach("s1", Default::default()).context("reattaching s1")?; + let mut lm1 = a1.line_matcher()?; + lm1.never_matches("foo$")?; + + a1.run_cmd("echo s1_alive")?; + lm1.scan_until_re("s1_alive$")?; + + a1.proc.kill()?; + } + + // Verify s2 has "lines = 2" behavior (restores bar) + { + let mut a2 = daemon_proc.attach("s2", Default::default()).context("reattaching s2")?; + let mut lm2 = a2.line_matcher()?; + // It SHOULD see "bar" again on re-attach + lm2.scan_until_re("bar$")?; + } + + Ok(()) +} diff --git a/shpool/tests/data/dynamic_restore.toml.tmpl b/shpool/tests/data/dynamic_restore.toml.tmpl new file mode 100644 index 00000000..9b402a3a --- /dev/null +++ b/shpool/tests/data/dynamic_restore.toml.tmpl @@ -0,0 +1,9 @@ +norc = true +noecho = true +shell = "/bin/bash" +session_restore_mode = REPLACE_ME +prompt_prefix = "" + +[env] +PS1 = "prompt> " +TERM = ""