refactor(IO): random fixes (#521)

* refactor(os_input_output): use Pid for child process

* fix(debug): change debug_to_file to write &[u8]

This patch changes debug_to_file to write entire slices in one call, to
avoid the overhead of opening and closing files for each byte written.

* refactor(ServerOsApi): remove unnecessary muts from methods

Co-authored-by: KOVACS Tamas <ktamas@fastmail.fm>
This commit is contained in:
kxt 2021-05-18 17:39:36 +02:00 committed by GitHub
parent 12de9b1073
commit 68445af63f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 48 additions and 49 deletions

View File

@ -8,7 +8,7 @@ use std::path::PathBuf;
use std::sync::{mpsc, Arc, Condvar, Mutex};
use std::time::{Duration, Instant};
use zellij_client::os_input_output::ClientOsApi;
use zellij_server::os_input_output::ServerOsApi;
use zellij_server::os_input_output::{Pid, ServerOsApi};
use zellij_tile::data::Palette;
use zellij_utils::{
channels::{ChannelWithContext, SenderType, SenderWithContext},
@ -22,7 +22,7 @@ const MIN_TIME_BETWEEN_SNAPSHOTS: Duration = Duration::from_millis(150);
#[derive(Clone)]
pub enum IoEvent {
Kill(RawFd),
Kill(Pid),
SetTerminalSizeUsingFd(RawFd, u16, u16),
IntoRawMode(RawFd),
UnsetRawMode(RawFd),
@ -127,7 +127,7 @@ impl FakeInputOutput {
}
self.stdin_commands = Arc::new(Mutex::new(stdin_commands));
}
pub fn add_terminal(&mut self, fd: RawFd) {
pub fn add_terminal(&self, fd: RawFd) {
self.stdin_writes.lock().unwrap().insert(fd, vec![]);
}
pub fn add_sigwinch_event(&mut self, new_position_and_size: PositionAndSize) {
@ -225,7 +225,7 @@ impl ClientOsApi for FakeInputOutput {
}
impl ServerOsApi for FakeInputOutput {
fn set_terminal_size_using_fd(&mut self, pid: RawFd, cols: u16, rows: u16) {
fn set_terminal_size_using_fd(&self, pid: RawFd, cols: u16, rows: u16) {
let terminal_input = self
.possible_tty_inputs
.get(&cols)
@ -239,12 +239,15 @@ impl ServerOsApi for FakeInputOutput {
.unwrap()
.push(IoEvent::SetTerminalSizeUsingFd(pid, cols, rows));
}
fn spawn_terminal(&mut self, _file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
fn spawn_terminal(&self, _file_to_open: Option<PathBuf>) -> (RawFd, Pid) {
let next_terminal_id = self.stdin_writes.lock().unwrap().keys().len() as RawFd + 1;
self.add_terminal(next_terminal_id);
(next_terminal_id as i32, next_terminal_id + 1000) // secondary number is arbitrary here
(
next_terminal_id as i32,
Pid::from_raw(next_terminal_id + 1000),
) // secondary number is arbitrary here
}
fn write_to_tty_stdin(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
fn write_to_tty_stdin(&self, pid: RawFd, buf: &[u8]) -> Result<usize, nix::Error> {
let mut stdin_writes = self.stdin_writes.lock().unwrap();
let write_buffer = stdin_writes.get_mut(&pid).unwrap();
let mut bytes_written = 0;
@ -254,7 +257,7 @@ impl ServerOsApi for FakeInputOutput {
}
Ok(bytes_written)
}
fn read_from_tty_stdout(&mut self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
fn read_from_tty_stdout(&self, pid: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
let mut read_buffers = self.read_buffers.lock().unwrap();
let mut bytes_read = 0;
match read_buffers.get_mut(&pid) {
@ -271,15 +274,15 @@ impl ServerOsApi for FakeInputOutput {
None => Err(nix::Error::Sys(nix::errno::Errno::EAGAIN)),
}
}
fn tcdrain(&mut self, pid: RawFd) -> Result<(), nix::Error> {
fn tcdrain(&self, pid: RawFd) -> Result<(), nix::Error> {
self.io_events.lock().unwrap().push(IoEvent::TcDrain(pid));
Ok(())
}
fn box_clone(&self) -> Box<dyn ServerOsApi> {
Box::new((*self).clone())
}
fn kill(&mut self, fd: RawFd) -> Result<(), nix::Error> {
self.io_events.lock().unwrap().push(IoEvent::Kill(fd));
fn kill(&self, pid: Pid) -> Result<(), nix::Error> {
self.io_events.lock().unwrap().push(IoEvent::Kill(pid));
Ok(())
}
fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext) {
@ -292,7 +295,7 @@ impl ServerOsApi for FakeInputOutput {
fn send_to_client(&self, msg: ServerToClientMsg) {
self.send_instructions_to_client.send(msg).unwrap();
}
fn add_client_sender(&mut self) {}
fn add_client_sender(&self) {}
fn update_receiver(&mut self, _stream: LocalSocketStream) {}
fn load_palette(&self) -> Palette {
default_palette()

View File

@ -4,7 +4,7 @@ use nix::pty::{forkpty, Winsize};
use nix::sys::signal::{kill, Signal};
use nix::sys::termios;
use nix::sys::wait::waitpid;
use nix::unistd::{self, ForkResult, Pid};
use nix::unistd::{self, ForkResult};
use signal_hook::consts::*;
use std::env;
use std::os::unix::io::RawFd;
@ -18,6 +18,7 @@ use zellij_utils::ipc::{
};
use zellij_utils::shared::default_palette;
pub use nix::unistd::Pid;
pub(crate) fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
// TODO: do this with the nix ioctl
use libc::ioctl;
@ -76,8 +77,8 @@ fn handle_command_exit(mut child: Child) {
/// set.
// FIXME this should probably be split into different functions, or at least have less levels
// of indentation in some way
fn spawn_terminal(file_to_open: Option<PathBuf>, orig_termios: termios::Termios) -> (RawFd, RawFd) {
let (pid_primary, pid_secondary): (RawFd, RawFd) = {
fn spawn_terminal(file_to_open: Option<PathBuf>, orig_termios: termios::Termios) -> (RawFd, Pid) {
let (pid_primary, pid_secondary): (RawFd, Pid) = {
match forkpty(None, Some(&orig_termios)) {
Ok(fork_pty_res) => {
let pid_primary = fork_pty_res.master;
@ -112,7 +113,7 @@ fn spawn_terminal(file_to_open: Option<PathBuf>, orig_termios: termios::Termios)
}
},
};
(pid_primary, pid_secondary.as_raw())
(pid_primary, pid_secondary)
}
Err(e) => {
panic!("failed to fork {:?}", e);
@ -133,20 +134,17 @@ pub struct ServerOsInputOutput {
/// Zellij server requires.
pub trait ServerOsApi: Send + Sync {
/// Sets the size of the terminal associated to file descriptor `fd`.
fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16);
fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16);
/// Spawn a new terminal, with an optional file to open in a terminal program.
fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd);
fn spawn_terminal(&self, file_to_open: Option<PathBuf>) -> (RawFd, Pid);
/// Read bytes from the standard output of the virtual terminal referred to by `fd`.
fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
/// Write bytes to the standard input of the virtual terminal referred to by `fd`.
fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error>;
fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result<usize, nix::Error>;
/// Wait until all output written to the object referred to by `fd` has been transmitted.
fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error>;
fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error>;
/// Terminate the process with process ID `pid`.
// FIXME `RawFd` is semantically the wrong type here. It should either be a raw libc::pid_t,
// or a nix::unistd::Pid. See `man kill.3`, nix::sys::signal::kill (both take an argument
// called `pid` and of type `pid_t`, and not `fd`)
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error>;
fn kill(&self, pid: Pid) -> Result<(), nix::Error>;
/// Returns a [`Box`] pointer to this [`ServerOsApi`] struct.
fn box_clone(&self) -> Box<dyn ServerOsApi>;
/// Receives a message on server-side IPC channel
@ -154,41 +152,41 @@ pub trait ServerOsApi: Send + Sync {
/// Sends a message to client
fn send_to_client(&self, msg: ServerToClientMsg);
/// Adds a sender to client
fn add_client_sender(&mut self);
fn add_client_sender(&self);
/// Update the receiver socket for the client
fn update_receiver(&mut self, stream: LocalSocketStream);
fn load_palette(&self) -> Palette;
}
impl ServerOsApi for ServerOsInputOutput {
fn set_terminal_size_using_fd(&mut self, fd: RawFd, cols: u16, rows: u16) {
fn set_terminal_size_using_fd(&self, fd: RawFd, cols: u16, rows: u16) {
set_terminal_size_using_fd(fd, cols, rows);
}
fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> (RawFd, RawFd) {
fn spawn_terminal(&self, file_to_open: Option<PathBuf>) -> (RawFd, Pid) {
let orig_termios = self.orig_termios.lock().unwrap();
spawn_terminal(file_to_open, orig_termios.clone())
}
fn read_from_tty_stdout(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
fn read_from_tty_stdout(&self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
unistd::read(fd, buf)
}
fn write_to_tty_stdin(&mut self, fd: RawFd, buf: &mut [u8]) -> Result<usize, nix::Error> {
fn write_to_tty_stdin(&self, fd: RawFd, buf: &[u8]) -> Result<usize, nix::Error> {
unistd::write(fd, buf)
}
fn tcdrain(&mut self, fd: RawFd) -> Result<(), nix::Error> {
fn tcdrain(&self, fd: RawFd) -> Result<(), nix::Error> {
termios::tcdrain(fd)
}
fn box_clone(&self) -> Box<dyn ServerOsApi> {
Box::new((*self).clone())
}
fn kill(&mut self, pid: RawFd) -> Result<(), nix::Error> {
fn kill(&self, pid: Pid) -> Result<(), nix::Error> {
// TODO:
// Ideally, we should be using SIGINT rather than SIGKILL here, but there are cases in which
// the terminal we're trying to kill hangs on SIGINT and so all the app gets stuck
// that's why we're sending SIGKILL here
// A better solution would be to send SIGINT here and not wait for it, and then have
// a background thread do the waitpid stuff and send SIGKILL if the process is stuck
kill(Pid::from_raw(pid), Some(Signal::SIGKILL)).unwrap();
waitpid(Pid::from_raw(pid), None).unwrap();
kill(pid, Some(Signal::SIGKILL)).unwrap();
waitpid(pid, None).unwrap();
Ok(())
}
fn recv_from_client(&self) -> (ClientToServerMsg, ErrorContext) {
@ -207,7 +205,7 @@ impl ServerOsApi for ServerOsInputOutput {
.unwrap()
.send(msg);
}
fn add_client_sender(&mut self) {
fn add_client_sender(&self) {
assert!(self.send_instructions_to_client.lock().unwrap().is_none());
let sender = self
.receive_instructions_from_client

View File

@ -8,7 +8,7 @@ use std::pin::*;
use std::time::{Duration, Instant};
use crate::{
os_input_output::ServerOsApi,
os_input_output::{Pid, ServerOsApi},
panes::PaneId,
screen::ScreenInstruction,
thread_bus::{Bus, ThreadSenders},
@ -37,7 +37,7 @@ impl ReadFromPid {
impl Stream for ReadFromPid {
type Item = Vec<u8>;
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let mut read_buffer = [0; 65535];
let pid = self.pid;
let read_result = &self.os_input.read_from_tty_stdout(pid, &mut read_buffer);
@ -97,7 +97,7 @@ impl From<&PtyInstruction> for PtyContext {
pub(crate) struct Pty {
pub bus: Bus<PtyInstruction>,
pub id_to_child_pid: HashMap<RawFd, RawFd>,
pub id_to_child_pid: HashMap<RawFd, Pid>,
debug_to_file: bool,
task_handles: HashMap<RawFd, JoinHandle<()>>,
}
@ -177,9 +177,7 @@ fn stream_terminal_bytes(
while let Some(bytes) = terminal_bytes.next().await {
let bytes_is_empty = bytes.is_empty();
if debug {
for byte in bytes.iter() {
debug_to_file(*byte, pid).unwrap();
}
debug_to_file(&bytes, pid).unwrap();
}
if !bytes_is_empty {
let _ = senders.send_to_screen(ScreenInstruction::PtyBytes(pid, bytes));
@ -235,7 +233,7 @@ impl Pty {
}
}
pub fn spawn_terminal(&mut self, file_to_open: Option<PathBuf>) -> RawFd {
let (pid_primary, pid_secondary): (RawFd, RawFd) = self
let (pid_primary, pid_secondary): (RawFd, Pid) = self
.bus
.os_input
.as_mut()
@ -255,7 +253,7 @@ impl Pty {
let total_panes = layout.total_terminal_panes();
let mut new_pane_pids = vec![];
for _ in 0..total_panes {
let (pid_primary, pid_secondary): (RawFd, RawFd) =
let (pid_primary, pid_secondary): (RawFd, Pid) =
self.bus.os_input.as_mut().unwrap().spawn_terminal(None);
self.id_to_child_pid.insert(pid_primary, pid_secondary);
new_pane_pids.push(pid_primary);

View File

@ -189,7 +189,7 @@ fn route_action(action: Action, session: &SessionMetaData, os_input: &dyn Server
pub(crate) fn route_thread_main(
sessions: Arc<RwLock<Option<SessionMetaData>>>,
mut os_input: Box<dyn ServerOsApi>,
os_input: Box<dyn ServerOsApi>,
to_server: SenderWithContext<ServerInstruction>,
) {
loop {

View File

@ -232,7 +232,7 @@ impl Tab {
position: usize,
name: String,
full_screen_ws: &PositionAndSize,
mut os_api: Box<dyn ServerOsApi>,
os_api: Box<dyn ServerOsApi>,
senders: ThreadSenders,
max_panes: Option<usize>,
pane_id: Option<PaneId>,
@ -624,9 +624,9 @@ impl Tab {
match pane_id {
PaneId::Terminal(active_terminal_id) => {
let active_terminal = self.panes.get(&pane_id).unwrap();
let mut adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes);
let adjusted_input = active_terminal.adjust_input_to_terminal(input_bytes);
self.os_api
.write_to_tty_stdin(active_terminal_id, &mut adjusted_input)
.write_to_tty_stdin(active_terminal_id, &adjusted_input)
.expect("failed to write to terminal");
self.os_api
.tcdrain(active_terminal_id)

View File

@ -71,7 +71,7 @@ pub fn _delete_log_dir() -> io::Result<()> {
}
}
pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> {
pub fn debug_to_file(message: &[u8], pid: RawFd) -> io::Result<()> {
let mut path = PathBuf::new();
path.push(&*ZELLIJ_TMP_LOG_DIR);
path.push(format!("zellij-{}.log", pid.to_string()));
@ -81,5 +81,5 @@ pub fn debug_to_file(message: u8, pid: RawFd) -> io::Result<()> {
.create(true)
.open(&path)?;
set_permissions(&path)?;
file.write_all(&[message])
file.write_all(message)
}