mirror of
https://github.com/alacritty/alacritty.git
synced 2025-07-31 22:03:40 -04:00
Fix signal handling on Unix systems
This removes the the signal handling machinery in tty::unix, and replaces it with functionality from signal-hook, which should be more robust. Signals caught by signal-hook wake up the existing I/O event loop, which then delegates back to the PTY to handle them. In particular, this allows `SIGCHLD` (i.e. child process exits) to shut down the terminal promptly, instead of sometimes leaving the window lingering. Fixes #915. Fixes #1276. Fixes #1313. As a side effect, this fixes a very rare bug on Linux, where a `read` from the PTY on the master side would sometimes "fail" with `EIO` if the child closed the client side at a particular moment. This was subject to a race condition, and was very difficult to trigger in practice.
This commit is contained in:
parent
e240da9ab3
commit
62c1d999e1
10 changed files with 207 additions and 134 deletions
|
@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Windows: Conpty backend could close immediately on startup in certain situations
|
- Windows: Conpty backend could close immediately on startup in certain situations
|
||||||
- FreeBSD: SpawnNewInstance will now open new instances in the shell's current
|
- FreeBSD: SpawnNewInstance will now open new instances in the shell's current
|
||||||
working directory as long as linprocfs(5) is mounted on `/compat/linux/proc`
|
working directory as long as linprocfs(5) is mounted on `/compat/linux/proc`
|
||||||
|
- Fix lingering Alacritty window after child process has exited
|
||||||
|
|
||||||
## Version 0.2.9
|
## Version 0.2.9
|
||||||
|
|
||||||
|
|
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -57,6 +57,7 @@ dependencies = [
|
||||||
"mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"notify 4.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"notify 4.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"objc 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"objc 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -65,6 +66,7 @@ dependencies = [
|
||||||
"serde_derive 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_derive 1.0.87 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_yaml 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"signal-hook 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"tempfile 3.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"tempfile 3.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"terminfo 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"terminfo 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -114,6 +116,11 @@ dependencies = [
|
||||||
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arc-swap"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "argon2rs"
|
name = "argon2rs"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
|
@ -1276,6 +1283,16 @@ dependencies = [
|
||||||
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio-uds"
|
||||||
|
version = "0.6.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miow"
|
name = "miow"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -2062,6 +2079,17 @@ dependencies = [
|
||||||
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"arc-swap 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.48 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "siphasher"
|
name = "siphasher"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -2736,6 +2764,7 @@ dependencies = [
|
||||||
"checksum android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407"
|
"checksum android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407"
|
||||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||||
"checksum approx 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c57ff1a5b00753647aebbbcf4ea67fa1e711a65ea7a30eb90dbf07de2485aee"
|
"checksum approx 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c57ff1a5b00753647aebbbcf4ea67fa1e711a65ea7a30eb90dbf07de2485aee"
|
||||||
|
"checksum arc-swap 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1025aeae2b664ca0ea726a89d574fe8f4e77dd712d443236ad1de00379450cf6"
|
||||||
"checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392"
|
"checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392"
|
||||||
"checksum arraydeque 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e300327073b806ffc81fccb228b2d4131ac7ef1b1a015f7b0c399c7f886cacc6"
|
"checksum arraydeque 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e300327073b806ffc81fccb228b2d4131ac7ef1b1a015f7b0c399c7f886cacc6"
|
||||||
"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
|
"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71"
|
||||||
|
@ -2863,6 +2892,7 @@ dependencies = [
|
||||||
"checksum mio-anonymous-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8c274c3c52dcd1d78c5d7ed841eca1e9ea2db8353f3b8ec25789cc62c471aaf"
|
"checksum mio-anonymous-pipes 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8c274c3c52dcd1d78c5d7ed841eca1e9ea2db8353f3b8ec25789cc62c471aaf"
|
||||||
"checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40"
|
"checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40"
|
||||||
"checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3"
|
"checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3"
|
||||||
|
"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
|
||||||
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
|
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
|
||||||
"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
|
"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
|
||||||
"checksum named_pipe 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ed10a5ac4f5f7e5d75552b12c1d5d542debca81e573279dd1e4c19fde6efa6d"
|
"checksum named_pipe 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ed10a5ac4f5f7e5d75552b12c1d5d542debca81e573279dd1e4c19fde6efa6d"
|
||||||
|
@ -2949,6 +2979,7 @@ dependencies = [
|
||||||
"checksum servo-fontconfig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a088f8d775a5c5314aae09bd77340bc9c67d72b9a45258be34c83548b4814cd9"
|
"checksum servo-fontconfig 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a088f8d775a5c5314aae09bd77340bc9c67d72b9a45258be34c83548b4814cd9"
|
||||||
"checksum servo-fontconfig-sys 4.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b46d201addcfbd25c1798ad1281d98c40743824e0b0f1e611bd3d5d0d31a7b8d"
|
"checksum servo-fontconfig-sys 4.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b46d201addcfbd25c1798ad1281d98c40743824e0b0f1e611bd3d5d0d31a7b8d"
|
||||||
"checksum shared_library 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
|
"checksum shared_library 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11"
|
||||||
|
"checksum signal-hook 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "97a47ae722318beceb0294e6f3d601205a1e6abaa4437d9d33e3a212233e3021"
|
||||||
"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
|
"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
|
||||||
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||||
"checksum smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "88aea073965ab29f6edb5493faf96ad662fb18aa9eeb186a3b7057951605ed15"
|
"checksum smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "88aea073965ab29f6edb5493faf96ad662fb18aa9eeb186a3b7057951605ed15"
|
||||||
|
|
|
@ -51,6 +51,10 @@ url = "1.7.1"
|
||||||
time = "0.1.40"
|
time = "0.1.40"
|
||||||
crossbeam-channel = "0.3.8"
|
crossbeam-channel = "0.3.8"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
nix = "0.12"
|
||||||
|
signal-hook = { version = "0.1", features = ["mio-support"] }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))'.dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os="dragonfly", target_os="openbsd"))'.dependencies]
|
||||||
x11-dl = "2"
|
x11-dl = "2"
|
||||||
|
|
||||||
|
|
|
@ -176,7 +176,8 @@ impl<'a, N: Notify + 'a> input::ActionContext for ActionContext<'a, N> {
|
||||||
let proc_prefix = "";
|
let proc_prefix = "";
|
||||||
#[cfg(target_os = "freebsd")]
|
#[cfg(target_os = "freebsd")]
|
||||||
let proc_prefix = "/compat/linux";
|
let proc_prefix = "/compat/linux";
|
||||||
if let Ok(path) = fs::read_link(format!("{}/proc/{}/cwd", proc_prefix, unsafe { tty::PID })) {
|
let link_path = format!("{}/proc/{}/cwd", proc_prefix, tty::child_pid());
|
||||||
|
if let Ok(path) = fs::read_link(link_path) {
|
||||||
vec!["--working-directory".into(), path]
|
vec!["--working-directory".into(), path]
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub enum Msg {
|
||||||
///
|
///
|
||||||
/// Handles all the pty I/O and runs the pty parser which updates terminal
|
/// Handles all the pty I/O and runs the pty parser which updates terminal
|
||||||
/// state.
|
/// state.
|
||||||
pub struct EventLoop<T: tty::EventedReadWrite> {
|
pub struct EventLoop<T: tty::EventedPty> {
|
||||||
poll: mio::Poll,
|
poll: mio::Poll,
|
||||||
pty: T,
|
pty: T,
|
||||||
rx: Receiver<Msg>,
|
rx: Receiver<Msg>,
|
||||||
|
@ -160,12 +160,9 @@ impl Writing {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `mio::Token` for the event loop channel
|
|
||||||
const CHANNEL: mio::Token = mio::Token(0);
|
|
||||||
|
|
||||||
impl<T> EventLoop<T>
|
impl<T> EventLoop<T>
|
||||||
where
|
where
|
||||||
T: tty::EventedReadWrite + Send + 'static,
|
T: tty::EventedPty + Send + 'static,
|
||||||
{
|
{
|
||||||
/// Create a new event loop
|
/// Create a new event loop
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
@ -217,13 +214,13 @@ impl<T> EventLoop<T>
|
||||||
|
|
||||||
// Returns a `bool` indicating whether or not the event loop should continue running
|
// Returns a `bool` indicating whether or not the event loop should continue running
|
||||||
#[inline]
|
#[inline]
|
||||||
fn channel_event(&mut self, state: &mut State) -> bool {
|
fn channel_event(&mut self, token: mio::Token, state: &mut State) -> bool {
|
||||||
if self.drain_recv_channel(state).is_shutdown() {
|
if self.drain_recv_channel(state).is_shutdown() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.poll
|
self.poll
|
||||||
.reregister(&self.rx, CHANNEL, Ready::readable(), PollOpt::edge() | PollOpt::oneshot())
|
.reregister(&self.rx, token, Ready::readable(), PollOpt::edge() | PollOpt::oneshot())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
true
|
true
|
||||||
|
@ -341,17 +338,19 @@ impl<T> EventLoop<T>
|
||||||
let mut state = state.unwrap_or_else(Default::default);
|
let mut state = state.unwrap_or_else(Default::default);
|
||||||
let mut buf = [0u8; 0x1000];
|
let mut buf = [0u8; 0x1000];
|
||||||
|
|
||||||
|
let mut tokens = (0..).map(Into::into);
|
||||||
|
|
||||||
let poll_opts = PollOpt::edge() | PollOpt::oneshot();
|
let poll_opts = PollOpt::edge() | PollOpt::oneshot();
|
||||||
|
|
||||||
let tokens = [1, 2];
|
let channel_token = tokens.next().unwrap();
|
||||||
|
|
||||||
self.poll
|
self.poll
|
||||||
.register(&self.rx, CHANNEL, Ready::readable(), poll_opts)
|
.register(&self.rx, channel_token, Ready::readable(), poll_opts)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Register TTY through EventedRW interface
|
// Register TTY through EventedRW interface
|
||||||
self.pty
|
self.pty
|
||||||
.register(&self.poll, &mut tokens.iter(), Ready::readable(), poll_opts).unwrap();
|
.register(&self.poll, &mut tokens, Ready::readable(), poll_opts)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut events = Events::with_capacity(1024);
|
let mut events = Events::with_capacity(1024);
|
||||||
|
|
||||||
|
@ -371,41 +370,50 @@ impl<T> EventLoop<T>
|
||||||
|
|
||||||
for event in events.iter() {
|
for event in events.iter() {
|
||||||
match event.token() {
|
match event.token() {
|
||||||
CHANNEL => if !self.channel_event(&mut state) {
|
token if token == channel_token => {
|
||||||
break 'event_loop;
|
if !self.channel_event(channel_token, &mut state) {
|
||||||
|
break 'event_loop;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
token if token == self.pty.child_event_token() => {
|
||||||
|
if let Some(tty::ChildEvent::Exited) = self.pty.next_child_event() {
|
||||||
|
self.terminal.lock().exit();
|
||||||
|
self.display.notify();
|
||||||
|
break 'event_loop;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
token if token == self.pty.read_token() || token == self.pty.write_token() => {
|
token if token == self.pty.read_token() || token == self.pty.write_token() => {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)] {
|
||||||
{
|
if UnixReady::from(event.readiness()).is_hup() {
|
||||||
if UnixReady::from(event.readiness()).is_hup() {
|
// don't try to do I/O on a dead PTY
|
||||||
break 'event_loop;
|
continue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if event.readiness().is_readable() {
|
if event.readiness().is_readable() {
|
||||||
if let Err(err) = self.pty_read(&mut state, &mut buf, pipe.as_mut())
|
if let Err(e) = self.pty_read(&mut state, &mut buf, pipe.as_mut()) {
|
||||||
{
|
#[cfg(target_os = "linux")] {
|
||||||
error!(
|
// On Linux, a `read` on the master side of a PTY can fail
|
||||||
"[{}:{}] Event loop exiting due to error: {}",
|
// with `EIO` if the client side hangs up. In that case,
|
||||||
file!(),
|
// just loop back round for the inevitable `Exited` event.
|
||||||
line!(),
|
// This sucks, but checking the process is either racy or
|
||||||
err
|
// blocking.
|
||||||
);
|
if e.kind() == ErrorKind::Other {
|
||||||
break 'event_loop;
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if crate::tty::process_should_exit() {
|
error!("Error reading from PTY in event loop: {}", e);
|
||||||
break 'event_loop;
|
break 'event_loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.readiness().is_writable() {
|
if event.readiness().is_writable() {
|
||||||
if let Err(err) = self.pty_write(&mut state) {
|
if let Err(e) = self.pty_write(&mut state) {
|
||||||
error!(
|
error!("Error writing to PTY in event loop: {}", e);
|
||||||
"[{}:{}] Event loop exiting due to error: {}",
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
err
|
|
||||||
);
|
|
||||||
break 'event_loop;
|
break 'event_loop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,7 +236,7 @@ fn run(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin shutdown if the flag was raised
|
// Begin shutdown if the flag was raised
|
||||||
if terminal_lock.should_exit() {
|
if terminal_lock.should_exit() || tty::process_should_exit() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@ use crate::url::UrlParser;
|
||||||
use crate::message_bar::MessageBuffer;
|
use crate::message_bar::MessageBuffer;
|
||||||
use crate::term::color::Rgb;
|
use crate::term::color::Rgb;
|
||||||
use crate::term::cell::{LineLength, Cell};
|
use crate::term::cell::{LineLength, Cell};
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
use crate::tty;
|
use crate::tty;
|
||||||
|
|
||||||
pub mod cell;
|
pub mod cell;
|
||||||
|
@ -1336,7 +1338,7 @@ impl Term {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn should_exit(&self) -> bool {
|
pub fn should_exit(&self) -> bool {
|
||||||
tty::process_should_exit() || self.should_exit
|
self.should_exit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ pub trait EventedReadWrite {
|
||||||
fn register(
|
fn register(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &mio::Poll,
|
_: &mio::Poll,
|
||||||
_: &mut dyn Iterator<Item = &usize>,
|
_: &mut dyn Iterator<Item = mio::Token>,
|
||||||
_: mio::Ready,
|
_: mio::Ready,
|
||||||
_: mio::PollOpt,
|
_: mio::PollOpt,
|
||||||
) -> io::Result<()>;
|
) -> io::Result<()>;
|
||||||
|
@ -53,6 +53,29 @@ pub trait EventedReadWrite {
|
||||||
fn write_token(&self) -> mio::Token;
|
fn write_token(&self) -> mio::Token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Events concerning TTY child processes
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub enum ChildEvent {
|
||||||
|
/// Indicates the child has exited
|
||||||
|
Exited
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A pseudoterminal (or PTY)
|
||||||
|
///
|
||||||
|
/// This is a refinement of EventedReadWrite that also provides a channel through which we can be
|
||||||
|
/// notified if the PTY child process does something we care about (other than writing to the TTY).
|
||||||
|
/// In particular, this allows for race-free child exit notification on UNIX (cf. `SIGCHLD`).
|
||||||
|
pub trait EventedPty : EventedReadWrite {
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn child_event_token(&self) -> mio::Token;
|
||||||
|
|
||||||
|
/// Tries to retrieve an event
|
||||||
|
///
|
||||||
|
/// Returns `Some(event)` on success, or `None` if there are no events to retrieve.
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn next_child_event(&mut self) -> Option<ChildEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
// Setup environment variables
|
// Setup environment variables
|
||||||
pub fn setup_env(config: &Config) {
|
pub fn setup_env(config: &Config) {
|
||||||
// Default to 'alacritty' terminfo if it is available, otherwise
|
// Default to 'alacritty' terminfo if it is available, otherwise
|
||||||
|
|
183
src/tty/unix.rs
183
src/tty/unix.rs
|
@ -15,54 +15,33 @@
|
||||||
//! tty related functionality
|
//! tty related functionality
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use crate::tty::EventedReadWrite;
|
use crate::tty::{EventedReadWrite, EventedPty, ChildEvent};
|
||||||
use crate::term::SizeInfo;
|
use crate::term::SizeInfo;
|
||||||
use crate::display::OnResize;
|
use crate::display::OnResize;
|
||||||
use crate::config::{Config, Shell};
|
use crate::config::{Config, Shell};
|
||||||
use crate::cli::Options;
|
use crate::cli::Options;
|
||||||
use mio;
|
use mio;
|
||||||
|
|
||||||
use libc::{self, c_int, pid_t, winsize, SIGCHLD, TIOCSCTTY, WNOHANG};
|
use libc::{self, c_int, pid_t, winsize, TIOCSCTTY};
|
||||||
|
use nix::pty::openpty;
|
||||||
|
use signal_hook::{self as sighook, iterator::Signals};
|
||||||
|
|
||||||
use std::os::unix::io::{FromRawFd, RawFd};
|
use std::os::unix::{process::CommandExt, io::{FromRawFd, AsRawFd, RawFd}};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::os::unix::process::CommandExt;
|
use std::process::{Command, Stdio, Child};
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use mio::unix::EventedFd;
|
use mio::unix::EventedFd;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
|
||||||
/// Process ID of child process
|
/// Process ID of child process
|
||||||
///
|
///
|
||||||
/// Necessary to put this in static storage for `sigchld` to have access
|
/// Necessary to put this in static storage for `sigchld` to have access
|
||||||
pub static mut PID: pid_t = 0;
|
static PID: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
/// Exit flag
|
pub fn child_pid() -> pid_t {
|
||||||
///
|
PID.load(Ordering::Relaxed) as pid_t
|
||||||
/// Calling exit() in the SIGCHLD handler sometimes causes opengl to deadlock,
|
|
||||||
/// and the process hangs. Instead, this flag is set, and its status can be
|
|
||||||
/// checked via `process_should_exit`.
|
|
||||||
static mut SHOULD_EXIT: bool = false;
|
|
||||||
|
|
||||||
extern "C" fn sigchld(_a: c_int) {
|
|
||||||
let mut status: c_int = 0;
|
|
||||||
unsafe {
|
|
||||||
let p = libc::waitpid(PID, &mut status, WNOHANG);
|
|
||||||
if p < 0 {
|
|
||||||
die!("Waiting for pid {} failed: {}\n", PID, errno());
|
|
||||||
}
|
|
||||||
|
|
||||||
if PID == p {
|
|
||||||
SHOULD_EXIT = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_should_exit() -> bool {
|
|
||||||
unsafe { SHOULD_EXIT }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current value of errno
|
/// Get the current value of errno
|
||||||
|
@ -71,50 +50,15 @@ fn errno() -> c_int {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get raw fds for master/slave ends of a new pty
|
/// Get raw fds for master/slave ends of a new pty
|
||||||
#[cfg(target_os = "linux")]
|
fn make_pty(size: winsize) -> (RawFd, RawFd) {
|
||||||
fn openpty(rows: u8, cols: u8) -> (c_int, c_int) {
|
let mut win_size = size;
|
||||||
let mut master: c_int = 0;
|
win_size.ws_xpixel = 0;
|
||||||
let mut slave: c_int = 0;
|
win_size.ws_ypixel = 0;
|
||||||
|
|
||||||
let win = winsize {
|
let ends = openpty(Some(&win_size), None)
|
||||||
ws_row: libc::c_ushort::from(rows),
|
.expect("openpty failed");
|
||||||
ws_col: libc::c_ushort::from(cols),
|
|
||||||
ws_xpixel: 0,
|
|
||||||
ws_ypixel: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = unsafe {
|
(ends.master, ends.slave)
|
||||||
libc::openpty(&mut master, &mut slave, ptr::null_mut(), ptr::null(), &win)
|
|
||||||
};
|
|
||||||
|
|
||||||
if res < 0 {
|
|
||||||
die!("openpty failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
(master, slave)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "macos",target_os = "freebsd",target_os = "openbsd"))]
|
|
||||||
fn openpty(rows: u8, cols: u8) -> (c_int, c_int) {
|
|
||||||
let mut master: c_int = 0;
|
|
||||||
let mut slave: c_int = 0;
|
|
||||||
|
|
||||||
let mut win = winsize {
|
|
||||||
ws_row: libc::c_ushort::from(rows),
|
|
||||||
ws_col: libc::c_ushort::from(cols),
|
|
||||||
ws_xpixel: 0,
|
|
||||||
ws_ypixel: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = unsafe {
|
|
||||||
libc::openpty(&mut master, &mut slave, ptr::null_mut(), ptr::null_mut(), &mut win)
|
|
||||||
};
|
|
||||||
|
|
||||||
if res < 0 {
|
|
||||||
die!("openpty failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
(master, slave)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Really only needed on BSD, but should be fine elsewhere
|
/// Really only needed on BSD, but should be fine elsewhere
|
||||||
|
@ -185,9 +129,12 @@ fn get_pw_entry(buf: &mut [i8; 1024]) -> Passwd<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Pty {
|
pub struct Pty {
|
||||||
|
child: Child,
|
||||||
pub fd: File,
|
pub fd: File,
|
||||||
pub raw_fd: RawFd,
|
|
||||||
token: mio::Token,
|
token: mio::Token,
|
||||||
|
signals: Signals,
|
||||||
|
signals_token: mio::Token,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pty {
|
impl Pty {
|
||||||
|
@ -215,11 +162,11 @@ pub fn new<T: ToWinsize>(
|
||||||
size: &T,
|
size: &T,
|
||||||
window_id: Option<usize>,
|
window_id: Option<usize>,
|
||||||
) -> Pty {
|
) -> Pty {
|
||||||
let win = size.to_winsize();
|
let win_size = size.to_winsize();
|
||||||
let mut buf = [0; 1024];
|
let mut buf = [0; 1024];
|
||||||
let pw = get_pw_entry(&mut buf);
|
let pw = get_pw_entry(&mut buf);
|
||||||
|
|
||||||
let (master, slave) = openpty(win.ws_row as _, win.ws_col as _);
|
let (master, slave) = make_pty(win_size);
|
||||||
|
|
||||||
let default_shell = if cfg!(target_os = "macos") {
|
let default_shell = if cfg!(target_os = "macos") {
|
||||||
let shell_name = pw.shell.rsplit('/').next().unwrap();
|
let shell_name = pw.shell.rsplit('/').next().unwrap();
|
||||||
|
@ -292,15 +239,14 @@ pub fn new<T: ToWinsize>(
|
||||||
builder.current_dir(dir.as_path());
|
builder.current_dir(dir.as_path());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare signal handling before spawning child
|
||||||
|
let signals = Signals::new(&[sighook::SIGCHLD]).expect("error preparing signal handling");
|
||||||
|
|
||||||
match builder.spawn() {
|
match builder.spawn() {
|
||||||
Ok(child) => {
|
Ok(child) => {
|
||||||
unsafe {
|
// Remember child PID so other modules can use it
|
||||||
// Set PID for SIGCHLD handler
|
PID.store(child.id() as usize, Ordering::Relaxed);
|
||||||
PID = child.id() as _;
|
|
||||||
|
|
||||||
// Handle SIGCHLD
|
|
||||||
libc::signal(SIGCHLD, sigchld as _);
|
|
||||||
}
|
|
||||||
unsafe {
|
unsafe {
|
||||||
// Maybe this should be done outside of this function so nonblocking
|
// Maybe this should be done outside of this function so nonblocking
|
||||||
// isn't forced upon consumers. Although maybe it should be?
|
// isn't forced upon consumers. Although maybe it should be?
|
||||||
|
@ -308,9 +254,11 @@ pub fn new<T: ToWinsize>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let pty = Pty {
|
let pty = Pty {
|
||||||
fd: unsafe {File::from_raw_fd(master) },
|
child,
|
||||||
raw_fd: master,
|
fd: unsafe { File::from_raw_fd(master) },
|
||||||
token: mio::Token::from(0)
|
token: mio::Token::from(0),
|
||||||
|
signals,
|
||||||
|
signals_token: mio::Token::from(0),
|
||||||
};
|
};
|
||||||
pty.resize(size);
|
pty.resize(size);
|
||||||
pty
|
pty
|
||||||
|
@ -329,32 +277,53 @@ impl EventedReadWrite for Pty {
|
||||||
fn register(
|
fn register(
|
||||||
&mut self,
|
&mut self,
|
||||||
poll: &mio::Poll,
|
poll: &mio::Poll,
|
||||||
token: &mut dyn Iterator<Item = &usize>,
|
token: &mut dyn Iterator<Item = mio::Token>,
|
||||||
interest: mio::Ready,
|
interest: mio::Ready,
|
||||||
poll_opts: mio::PollOpt,
|
poll_opts: mio::PollOpt,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
self.token = (*token.next().unwrap()).into();
|
self.token = token.next().unwrap();
|
||||||
poll.register(
|
poll.register(
|
||||||
&EventedFd(&self.raw_fd),
|
&EventedFd(&self.fd.as_raw_fd()),
|
||||||
self.token,
|
self.token,
|
||||||
interest,
|
interest,
|
||||||
poll_opts
|
poll_opts
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.signals_token = token.next().unwrap();
|
||||||
|
poll.register(
|
||||||
|
&self.signals,
|
||||||
|
self.signals_token,
|
||||||
|
mio::Ready::readable(),
|
||||||
|
mio::PollOpt::level()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn reregister(&mut self, poll: &mio::Poll, interest: mio::Ready, poll_opts: mio::PollOpt) -> io::Result<()> {
|
fn reregister(
|
||||||
|
&mut self,
|
||||||
|
poll: &mio::Poll,
|
||||||
|
interest: mio::Ready,
|
||||||
|
poll_opts: mio::PollOpt
|
||||||
|
) -> io::Result<()> {
|
||||||
poll.reregister(
|
poll.reregister(
|
||||||
&EventedFd(&self.raw_fd),
|
&EventedFd(&self.fd.as_raw_fd()),
|
||||||
self.token,
|
self.token,
|
||||||
interest,
|
interest,
|
||||||
poll_opts
|
poll_opts
|
||||||
|
)?;
|
||||||
|
|
||||||
|
poll.reregister(
|
||||||
|
&self.signals,
|
||||||
|
self.signals_token,
|
||||||
|
mio::Ready::readable(),
|
||||||
|
mio::PollOpt::level()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> {
|
fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> {
|
||||||
poll.deregister(&EventedFd(&self.raw_fd))
|
poll.deregister(&EventedFd(&self.fd.as_raw_fd()))?;
|
||||||
|
poll.deregister(&self.signals)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -378,6 +347,38 @@ impl EventedReadWrite for Pty {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EventedPty for Pty {
|
||||||
|
#[inline]
|
||||||
|
fn next_child_event(&mut self) -> Option<ChildEvent> {
|
||||||
|
self.signals
|
||||||
|
.pending()
|
||||||
|
.next()
|
||||||
|
.and_then(|signal| {
|
||||||
|
if signal != sighook::SIGCHLD {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.child.try_wait() {
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error checking child process termination: {}", e);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
Ok(None) => None,
|
||||||
|
Ok(_) => Some(ChildEvent::Exited),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn child_event_token(&self) -> mio::Token {
|
||||||
|
self.signals_token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_should_exit() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Types that can produce a `libc::winsize`
|
/// Types that can produce a `libc::winsize`
|
||||||
pub trait ToWinsize {
|
pub trait ToWinsize {
|
||||||
/// Get a `libc::winsize`
|
/// Get a `libc::winsize`
|
||||||
|
|
|
@ -28,7 +28,7 @@ use crate::cli::Options;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::display::OnResize;
|
use crate::display::OnResize;
|
||||||
use crate::term::SizeInfo;
|
use crate::term::SizeInfo;
|
||||||
use crate::tty::EventedReadWrite;
|
use crate::tty::{EventedReadWrite, EventedPty};
|
||||||
|
|
||||||
mod conpty;
|
mod conpty;
|
||||||
mod winpty;
|
mod winpty;
|
||||||
|
@ -232,12 +232,12 @@ impl<'a> EventedReadWrite for Pty<'a> {
|
||||||
fn register(
|
fn register(
|
||||||
&mut self,
|
&mut self,
|
||||||
poll: &mio::Poll,
|
poll: &mio::Poll,
|
||||||
token: &mut dyn Iterator<Item = &usize>,
|
token: &mut dyn Iterator<Item = mio::Token>,
|
||||||
interest: mio::Ready,
|
interest: mio::Ready,
|
||||||
poll_opts: mio::PollOpt,
|
poll_opts: mio::PollOpt,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
self.read_token = (*token.next().unwrap()).into();
|
self.read_token = token.next().unwrap();
|
||||||
self.write_token = (*token.next().unwrap()).into();
|
self.write_token = token.next().unwrap();
|
||||||
|
|
||||||
if interest.is_readable() {
|
if interest.is_readable() {
|
||||||
poll.register(
|
poll.register(
|
||||||
|
@ -339,3 +339,5 @@ impl<'a> EventedReadWrite for Pty<'a> {
|
||||||
self.write_token
|
self.write_token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> EventedPty for Pty<'a> { }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue