Skip to content

Commit

Permalink
Merge pull request #499 from zellij-org/multiprocess
Browse files Browse the repository at this point in the history
Switch to a multiprocess client-sever model
  • Loading branch information
kunalmohan authored May 15, 2021
2 parents 20d4b18 + 050d846 commit 4c198b4
Show file tree
Hide file tree
Showing 32 changed files with 427 additions and 411 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
.vscode
.vim
.DS_Store
/assets/man/zellij.1
/assets/man/zellij.1
**/target
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ include = ["src/**/*", "assets/plugins/*", "assets/layouts/*", "assets/config/*"
ansi_term = "0.12.1"
backtrace = "0.3.55"
bincode = "1.3.1"
daemonize = "0.4.1"
directories-next = "2.0"
futures = "0.3.5"
libc = "0.2"
Expand Down
12 changes: 8 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,23 @@ pub struct CliArgs {
pub max_panes: Option<usize>,

/// Change where zellij looks for layouts and plugins
#[structopt(long)]
#[structopt(long, parse(from_os_str))]
pub data_dir: Option<PathBuf>,

/// Run server listening at the specified socket path
#[structopt(long, parse(from_os_str))]
pub server: Option<PathBuf>,

/// Path to a layout yaml file
#[structopt(short, long)]
#[structopt(short, long, parse(from_os_str))]
pub layout: Option<PathBuf>,

/// Change where zellij looks for the configuration
#[structopt(short, long, env=ZELLIJ_CONFIG_FILE_ENV)]
#[structopt(short, long, env=ZELLIJ_CONFIG_FILE_ENV, parse(from_os_str))]
pub config: Option<PathBuf>,

/// Change where zellij looks for the configuration
#[structopt(long, env=ZELLIJ_CONFIG_DIR_ENV)]
#[structopt(long, env=ZELLIJ_CONFIG_DIR_ENV, parse(from_os_str))]
pub config_dir: Option<PathBuf>,

#[structopt(subcommand)]
Expand Down
138 changes: 95 additions & 43 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,62 @@ pub mod pane_resizer;
pub mod panes;
pub mod tab;

use serde::{Deserialize, Serialize};
use std::io::Write;
use std::env::current_exe;
use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
use std::sync::mpsc;
use std::thread;

use crate::cli::CliArgs;
use crate::common::{
command_is_executing::CommandIsExecuting,
errors::{ClientContext, ContextType},
errors::ContextType,
input::config::Config,
input::handler::input_loop,
input::options::Options,
ipc::{ClientToServerMsg, ServerToClientMsg},
os_input_output::ClientOsApi,
thread_bus::{SenderType, SenderWithContext, SyncChannelWithContext},
utils::consts::ZELLIJ_IPC_PIPE,
};
use crate::server::ServerInstruction;

/// Instructions related to the client-side application and sent from server to client
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Debug, Clone)]
pub enum ClientInstruction {
Error(String),
Render(Option<String>),
UnblockInputThread,
Exit,
ServerError(String),
}

impl From<ServerToClientMsg> for ClientInstruction {
fn from(instruction: ServerToClientMsg) -> Self {
match instruction {
ServerToClientMsg::Exit => ClientInstruction::Exit,
ServerToClientMsg::Render(buffer) => ClientInstruction::Render(buffer),
ServerToClientMsg::UnblockInputThread => ClientInstruction::UnblockInputThread,
ServerToClientMsg::ServerError(backtrace) => ClientInstruction::ServerError(backtrace),
}
}
}

fn spawn_server(socket_path: &Path) -> io::Result<()> {
let status = Command::new(current_exe()?)
.arg("--server")
.arg(socket_path)
.status()?;
if status.success() {
Ok(())
} else {
let msg = "Process returned non-zero exit code";
let err_msg = match status.code() {
Some(c) => format!("{}: {}", msg, c),
None => msg.to_string(),
};
Err(io::Error::new(io::ErrorKind::Other, err_msg))
}
}

pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: Config) {
Expand All @@ -45,13 +77,16 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
.unwrap();
std::env::set_var(&"ZELLIJ", "0");

#[cfg(not(test))]
spawn_server(&*ZELLIJ_IPC_PIPE).unwrap();

let mut command_is_executing = CommandIsExecuting::new();

let config_options = Options::from_cli(&config.options, opts.option.clone());

let full_screen_ws = os_input.get_terminal_size_using_fd(0);
os_input.connect_to_server();
os_input.send_to_server(ServerInstruction::NewClient(
os_input.connect_to_server(&*ZELLIJ_IPC_PIPE);
os_input.send_to_server(ClientToServerMsg::NewClient(
full_screen_ws,
opts,
config_options,
Expand Down Expand Up @@ -97,15 +132,26 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
.name("signal_listener".to_string())
.spawn({
let os_input = os_input.clone();
let send_client_instructions = send_client_instructions.clone();
move || {
os_input.receive_sigwinch(Box::new({
let os_api = os_input.clone();
move || {
os_api.send_to_server(ServerInstruction::TerminalResize(
os_api.get_terminal_size_using_fd(0),
));
}
}));
os_input.handle_signals(
Box::new({
let os_api = os_input.clone();
move || {
os_api.send_to_server(ClientToServerMsg::TerminalResize(
os_api.get_terminal_size_using_fd(0),
));
}
}),
Box::new({
let send_client_instructions = send_client_instructions.clone();
move || {
send_client_instructions
.send(ClientInstruction::Exit)
.unwrap()
}
}),
);
}
})
.unwrap();
Expand All @@ -114,47 +160,53 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
.name("router".to_string())
.spawn({
let os_input = os_input.clone();
move || {
loop {
let (instruction, mut err_ctx) = os_input.recv_from_server();
err_ctx.add_call(ContextType::Client(ClientContext::from(&instruction)));
if let ClientInstruction::Exit = instruction {
break;
let mut should_break = false;
move || loop {
let (instruction, err_ctx) = os_input.recv_from_server();
err_ctx.update_thread_ctx();
match instruction {
ServerToClientMsg::Exit | ServerToClientMsg::ServerError(_) => {
should_break = true;
}
send_client_instructions.send(instruction).unwrap();
_ => {}
}
send_client_instructions.send(instruction.into()).unwrap();
if should_break {
break;
}
send_client_instructions
.send(ClientInstruction::Exit)
.unwrap();
}
})
.unwrap();

#[warn(clippy::never_loop)]
let handle_error = |backtrace: String| {
os_input.unset_raw_mode(0);
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
let restore_snapshot = "\u{1b}[?1049l";
let error = format!(
"{}\n{}{}",
goto_start_of_last_line, restore_snapshot, backtrace
);
let _ = os_input
.get_stdout_writer()
.write(error.as_bytes())
.unwrap();
std::process::exit(1);
};

loop {
let (client_instruction, mut err_ctx) = receive_client_instructions
.recv()
.expect("failed to receive app instruction on channel");

err_ctx.add_call(ContextType::Client(ClientContext::from(
&client_instruction,
)));
err_ctx.add_call(ContextType::Client((&client_instruction).into()));
match client_instruction {
ClientInstruction::Exit => break,
ClientInstruction::Error(backtrace) => {
let _ = os_input.send_to_server(ServerInstruction::ClientExit);
os_input.unset_raw_mode(0);
let goto_start_of_last_line = format!("\u{1b}[{};{}H", full_screen_ws.rows, 1);
let restore_snapshot = "\u{1b}[?1049l";
let error = format!(
"{}\n{}{}",
goto_start_of_last_line, restore_snapshot, backtrace
);
let _ = os_input
.get_stdout_writer()
.write(error.as_bytes())
.unwrap();
std::process::exit(1);
let _ = os_input.send_to_server(ClientToServerMsg::ClientExit);
handle_error(backtrace);
}
ClientInstruction::ServerError(backtrace) => {
handle_error(backtrace);
}
ClientInstruction::Render(output) => {
if output.is_none() {
Expand All @@ -172,7 +224,7 @@ pub fn start_client(mut os_input: Box<dyn ClientOsApi>, opts: CliArgs, config: C
}
}

let _ = os_input.send_to_server(ServerInstruction::ClientExit);
let _ = os_input.send_to_server(ClientToServerMsg::ClientExit);
router_thread.join().unwrap();

// cleanup();
Expand Down
40 changes: 31 additions & 9 deletions src/common/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,29 @@ const MAX_THREAD_CALL_STACK: usize = 6;
use super::thread_bus::SenderWithContext;
#[cfg(not(test))]
use std::panic::PanicInfo;

pub trait ErrorInstruction {
fn error(err: String) -> Self;
}

impl ErrorInstruction for ClientInstruction {
fn error(err: String) -> Self {
ClientInstruction::Error(err)
}
}

impl ErrorInstruction for ServerInstruction {
fn error(err: String) -> Self {
ServerInstruction::Error(err)
}
}

/// Custom panic handler/hook. Prints the [`ErrorContext`].
#[cfg(not(test))]
pub fn handle_panic(
info: &PanicInfo<'_>,
send_app_instructions: &SenderWithContext<ClientInstruction>,
) {
pub fn handle_panic<T>(info: &PanicInfo<'_>, sender: &SenderWithContext<T>)
where
T: ErrorInstruction + Clone,
{
use backtrace::Backtrace;
use std::{process, thread};
let backtrace = Backtrace::new();
Expand Down Expand Up @@ -70,7 +87,7 @@ pub fn handle_panic(
println!("{}", backtrace);
process::exit(1);
} else {
let _ = send_app_instructions.send(ClientInstruction::Error(backtrace));
let _ = sender.send(T::error(backtrace));
}
}

Expand Down Expand Up @@ -103,6 +120,11 @@ impl ErrorContext {
break;
}
}
self.update_thread_ctx()
}

/// Updates the thread local [`ErrorContext`].
pub fn update_thread_ctx(&self) {
ASYNCOPENCALLS
.try_with(|ctx| *ctx.borrow_mut() = *self)
.unwrap_or_else(|_| OPENCALLS.with(|ctx| *ctx.borrow_mut() = *self));
Expand Down Expand Up @@ -333,13 +355,15 @@ pub enum ClientContext {
Error,
UnblockInputThread,
Render,
ServerError,
}

impl From<&ClientInstruction> for ClientContext {
fn from(client_instruction: &ClientInstruction) -> Self {
match *client_instruction {
ClientInstruction::Exit => ClientContext::Exit,
ClientInstruction::Error(_) => ClientContext::Error,
ClientInstruction::ServerError(_) => ClientContext::ServerError,
ClientInstruction::Render(_) => ClientContext::Render,
ClientInstruction::UnblockInputThread => ClientContext::UnblockInputThread,
}
Expand All @@ -350,22 +374,20 @@ impl From<&ClientInstruction> for ClientContext {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum ServerContext {
NewClient,
Action,
Render,
TerminalResize,
UnblockInputThread,
ClientExit,
Error,
}

impl From<&ServerInstruction> for ServerContext {
fn from(server_instruction: &ServerInstruction) -> Self {
match *server_instruction {
ServerInstruction::NewClient(..) => ServerContext::NewClient,
ServerInstruction::Action(_) => ServerContext::Action,
ServerInstruction::TerminalResize(_) => ServerContext::TerminalResize,
ServerInstruction::Render(_) => ServerContext::Render,
ServerInstruction::UnblockInputThread => ServerContext::UnblockInputThread,
ServerInstruction::ClientExit => ServerContext::ClientExit,
ServerInstruction::Error(_) => ServerContext::Error,
}
}
}
Loading

0 comments on commit 4c198b4

Please sign in to comment.