1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-26 16:34:23 +03:00

fix lingering after closing down

The root cause of this was a bit of a hack to ensure that we didn't
prematurely shut down while waiting for ssh sessions.

Introduce an Activity token that will extend the lifetime of the
event loop even if there are no windows present.

This cleans things up both on macos the application would linger in
the application switcher until you had tabbed away and back again,
and also for the null frontend which had grown a less gross hack.
This commit is contained in:
Wez Furlong 2020-01-06 11:34:09 -08:00
parent 9670fc3fdf
commit 93256f3339
5 changed files with 49 additions and 45 deletions

29
src/frontend/activity.rs Normal file
View File

@ -0,0 +1,29 @@
//! Keeps track of the number of user-initiated activities
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNT: AtomicUsize = AtomicUsize::new(0);
/// Create and hold on to an Activity while you are processing
/// the direct result of a user initiated action, such as preparing
/// to open a window.
/// Once you have opened the window, drop the activity.
/// The activity is used to keep the frontend alive even if there
/// may be no windows present in the mux.
pub struct Activity {}
impl Activity {
pub fn new() -> Self {
COUNT.fetch_add(1, Ordering::SeqCst);
Self {}
}
pub fn count() -> usize {
COUNT.load(Ordering::SeqCst)
}
}
impl Drop for Activity {
fn drop(&mut self) {
COUNT.fetch_sub(1, Ordering::SeqCst);
}
}

View File

@ -87,40 +87,13 @@ impl FrontEnd for GuiFrontEnd {
} }
fn run_forever(&self) -> anyhow::Result<()> { fn run_forever(&self) -> anyhow::Result<()> {
// We run until we've run out of windows in the Mux.
// When we're running ssh we have a transient window
// or two during authentication and we want to de-bounce
// our decision to quit until we're sure that we have
// no windows, so we track it here.
struct State {
when: Option<Instant>,
}
impl State {
fn mark(&mut self, is_empty: bool) {
if is_empty {
let now = Instant::now();
if let Some(start) = self.when.as_ref() {
let diff = now - *start;
if diff > Duration::new(5, 0) {
Connection::get().unwrap().terminate_message_loop();
}
} else {
self.when = Some(now);
}
} else {
self.when = None;
}
}
}
let state = Arc::new(Mutex::new(State { when: None }));
self.connection self.connection
.schedule_timer(std::time::Duration::from_millis(200), move || { .schedule_timer(std::time::Duration::from_millis(200), move || {
let mux = Mux::get().unwrap(); let mux = Mux::get().unwrap();
mux.prune_dead_windows(); mux.prune_dead_windows();
state.lock().unwrap().mark(mux.is_empty()); if mux.is_empty() && crate::frontend::activity::Activity::count() == 0 {
Connection::get().unwrap().terminate_message_loop();
}
}); });
self.connection.run_message_loop() self.connection.run_message_loop()

View File

@ -10,6 +10,7 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Mutex; use std::sync::Mutex;
pub mod activity;
pub mod gui; pub mod gui;
pub mod muxserver; pub mod muxserver;

View File

@ -33,9 +33,6 @@ impl Executor for MuxExecutor {
pub struct MuxServerFrontEnd { pub struct MuxServerFrontEnd {
tx: Sender<SpawnFunc>, tx: Sender<SpawnFunc>,
rx: Receiver<SpawnFunc>, rx: Receiver<SpawnFunc>,
/// Indicates if we're the null frontend or not.
/// If so, our termination behavior is altered.
is_null: bool,
} }
impl MuxServerFrontEnd { impl MuxServerFrontEnd {
@ -46,8 +43,7 @@ impl MuxServerFrontEnd {
if start_listener { if start_listener {
spawn_listener()?; spawn_listener()?;
} }
let is_null = !start_listener; Ok(Rc::new(Self { tx, rx }))
Ok(Rc::new(Self { tx, rx, is_null }))
} }
pub fn try_new() -> Result<Rc<dyn FrontEnd>, Error> { pub fn try_new() -> Result<Rc<dyn FrontEnd>, Error> {
@ -77,7 +73,7 @@ impl FrontEnd for MuxServerFrontEnd {
Err(err) => bail!("while waiting for events: {:?}", err), Err(err) => bail!("while waiting for events: {:?}", err),
} }
if !self.is_null && Mux::get().unwrap().is_empty() { if Mux::get().unwrap().is_empty() && crate::frontend::activity::Activity::count() == 0 {
info!("No more tabs; all done!"); info!("No more tabs; all done!");
return Ok(()); return Ok(());
} }

View File

@ -27,6 +27,7 @@ mod ssh;
mod stats; mod stats;
mod termwiztermtab; mod termwiztermtab;
use crate::frontend::activity::Activity;
use crate::frontend::{executor, front_end, FrontEndSelection}; use crate::frontend::{executor, front_end, FrontEndSelection};
use crate::mux::domain::{Domain, LocalDomain}; use crate::mux::domain::{Domain, LocalDomain};
use crate::mux::Mux; use crate::mux::Mux;
@ -361,6 +362,10 @@ fn run_ssh(config: config::ConfigHandle, opts: &SshCommand) -> anyhow::Result<()
let mux = Rc::new(mux::Mux::new(None)); let mux = Rc::new(mux::Mux::new(None));
Mux::set_mux(&mux); Mux::set_mux(&mux);
// Keep the frontend alive until we've run through the ssh authentication
// phase. This is passed into the thread and dropped when it is done.
let activity = Activity::new();
// Initiate an ssh connection; since that is a blocking process with // Initiate an ssh connection; since that is a blocking process with
// callbacks, we have to run it in another thread // callbacks, we have to run it in another thread
std::thread::spawn(move || { std::thread::spawn(move || {
@ -406,6 +411,11 @@ fn run_ssh(config: config::ConfigHandle, opts: &SshCommand) -> anyhow::Result<()
let window_id = mux.new_empty_window(); let window_id = mux.new_empty_window();
let tab = domain.spawn(PtySize::default(), cmd, window_id)?; let tab = domain.spawn(PtySize::default(), cmd, window_id)?;
gui.spawn_new_window(&fontconfig, &tab, window_id)?; gui.spawn_new_window(&fontconfig, &tab, window_id)?;
// This captures the activity ownership into this future, but also
// ensures that we drop it either when we error out, or if not,
// only once we reach this point in the processing flow.
drop(activity);
Ok(()) Ok(())
}); });
}); });
@ -772,6 +782,10 @@ fn run() -> anyhow::Result<()> {
let sock_path = unix_dom.socket_path(); let sock_path = unix_dom.socket_path();
let stream = unix_connect_with_retry(&sock_path)?; let stream = unix_connect_with_retry(&sock_path)?;
// Keep the threads below alive forever; they'll
// exit the process when they're done.
let activity = Activity::new();
// Spawn a thread to pull data from the socket and write // Spawn a thread to pull data from the socket and write
// it to stdout // it to stdout
let duped = stream.try_clone()?; let duped = stream.try_clone()?;
@ -785,15 +799,6 @@ fn run() -> anyhow::Result<()> {
let stdin = std::io::stdin(); let stdin = std::io::stdin();
consume_stream_then_exit_process(stdin.lock(), stream); consume_stream_then_exit_process(stdin.lock(), stream);
}); });
// This is a bit gross; we run a Null frontend while we
// manage our streams to avoid a panic with some code
// that spawns some background processing via the executors.
// However, since we don't register any domains, and the
// threads we spawn above to consume the streams are not
// associated with the mux layer, this call to run_forever
// really will run forever.
// For that reason, we've arranged for the threads above
// to exit the process if they run out of data to consume.
front_end.run_forever()?; front_end.run_forever()?;
} }
} }