mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 05:12:40 +03:00
serial: fix busy waiting on unix systems.
The serial port is always in non-blocking mode so we need to use our own timeout and `poll(2)`. While we're in here, the `wait` method could cause the gui to exit immediately on startup because we'd interpret its immediate error return result as child process completion. This commit makes the wait method look at the carrier detect signal and propagates IO errors back as completion events. refs: https://github.com/wez/wezterm/issues/1594
This commit is contained in:
parent
18c63c0526
commit
0a7e30e9bc
@ -10,7 +10,7 @@ use crate::{
|
|||||||
SlavePty,
|
SlavePty,
|
||||||
};
|
};
|
||||||
use anyhow::{ensure, Context};
|
use anyhow::{ensure, Context};
|
||||||
use filedescriptor::FileDescriptor;
|
use filedescriptor::{poll, pollfd, AsRawSocketDescriptor, FileDescriptor, POLLIN};
|
||||||
use serial::{
|
use serial::{
|
||||||
BaudRate, CharSize, FlowControl, Parity, PortSettings, SerialPort, StopBits, SystemPort,
|
BaudRate, CharSize, FlowControl, Parity, PortSettings, SerialPort, StopBits, SystemPort,
|
||||||
};
|
};
|
||||||
@ -107,7 +107,7 @@ impl SlavePty for Slave {
|
|||||||
"can only use default prog commands with serial tty implementations"
|
"can only use default prog commands with serial tty implementations"
|
||||||
);
|
);
|
||||||
Ok(Box::new(SerialChild {
|
Ok(Box::new(SerialChild {
|
||||||
_port: Arc::clone(&self.port),
|
port: Arc::clone(&self.port),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,7 +115,7 @@ impl SlavePty for Slave {
|
|||||||
/// There isn't really a child process on the end of the serial connection,
|
/// There isn't really a child process on the end of the serial connection,
|
||||||
/// so all of the Child trait impls are NOP
|
/// so all of the Child trait impls are NOP
|
||||||
struct SerialChild {
|
struct SerialChild {
|
||||||
_port: Handle,
|
port: Handle,
|
||||||
}
|
}
|
||||||
|
|
||||||
// An anemic impl of Debug to satisfy some indirect trait bounds
|
// An anemic impl of Debug to satisfy some indirect trait bounds
|
||||||
@ -131,10 +131,24 @@ impl Child for SerialChild {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn wait(&mut self) -> IoResult<ExitStatus> {
|
fn wait(&mut self) -> IoResult<ExitStatus> {
|
||||||
Err(std::io::Error::new(
|
// There isn't really a child process to wait for,
|
||||||
std::io::ErrorKind::WouldBlock,
|
// as the serial connection never really "dies",
|
||||||
"cannot wait for a serial connection to die",
|
// however, for something like a USB serial port,
|
||||||
))
|
// if it is unplugged then it logically is terminated.
|
||||||
|
// We read the CD (carrier detect) signal periodically
|
||||||
|
// to see if the device has gone away: we actually discard
|
||||||
|
// the CD value itself and just look for an error state.
|
||||||
|
// We could potentially also decide to call CD==false the
|
||||||
|
// same thing as the "child" completing.
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(Duration::from_secs(5));
|
||||||
|
|
||||||
|
let mut port = self.port.lock().unwrap();
|
||||||
|
if let Err(err) = port.read_cd() {
|
||||||
|
log::error!("Error reading carrier detect: {:#}", err);
|
||||||
|
return Ok(ExitStatus::with_exit_code(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_id(&self) -> Option<u32> {
|
fn process_id(&self) -> Option<u32> {
|
||||||
@ -221,18 +235,42 @@ struct Reader {
|
|||||||
|
|
||||||
impl Read for Reader {
|
impl Read for Reader {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
|
||||||
|
// On windows, this self.fd.read will block for up to the time we set
|
||||||
|
// as the timeout when we set up the port, but on unix it will
|
||||||
|
// never block.
|
||||||
loop {
|
loop {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
// The serial crate puts the serial port in non-blocking mode,
|
||||||
|
// so we must explicitly poll for ourselves here to avoid a
|
||||||
|
// busy loop.
|
||||||
|
let mut poll_array = [pollfd {
|
||||||
|
fd: self.fd.as_socket_descriptor(),
|
||||||
|
events: POLLIN,
|
||||||
|
revents: 0,
|
||||||
|
}];
|
||||||
|
let _ = poll(&mut poll_array, None);
|
||||||
|
}
|
||||||
|
|
||||||
match self.fd.read(buf) {
|
match self.fd.read(buf) {
|
||||||
Ok(size) => {
|
Ok(0) => {
|
||||||
if size == 0 {
|
if cfg!(windows) {
|
||||||
// Read timeout, but we expect to mostly hit this.
|
// Read timeout with no data available yet;
|
||||||
// It just means that there was no data available
|
// loop and try again.
|
||||||
// right now.
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::UnexpectedEof,
|
||||||
|
"EOF on serial port",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(size) => {
|
||||||
return Ok(size);
|
return Ok(size);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
if e.kind() == std::io::ErrorKind::WouldBlock {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
log::error!("serial read error: {}", e);
|
log::error!("serial read error: {}", e);
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user