Make script-vars use process groups (#40)

* Make script-vars use process groups

Tail script-vars now make use of process groups to fix issues with
non-finished processes staying around.
I don't really understand any of this.
This commit is contained in:
ElKowar 2020-10-23 18:03:29 +02:00 committed by GitHub
parent 4573f056b0
commit a0929c0a84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 79 additions and 40 deletions

12
Cargo.lock generated
View file

@ -303,6 +303,7 @@ dependencies = [
"debug_stub_derive", "debug_stub_derive",
"derive_more", "derive_more",
"extend", "extend",
"filedescriptor",
"gdk", "gdk",
"gdk-pixbuf", "gdk-pixbuf",
"gdkx11", "gdkx11",
@ -345,6 +346,17 @@ dependencies = [
"syn 1.0.44", "syn 1.0.44",
] ]
[[package]]
name = "filedescriptor"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70cb4dda2f343f3b7a98a6536559d04a700136cada190822e5d6a99e4184c06"
dependencies = [
"anyhow",
"libc",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "filetime" name = "filetime"
version = "0.2.12" version = "0.2.12"

View file

@ -44,6 +44,7 @@ ref-cast = "1.0"
popol = "0.3" popol = "0.3"
nix = "0.19" nix = "0.19"
smart-default = "0.6" smart-default = "0.6"
filedescriptor = "0.7"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"

View file

@ -103,6 +103,8 @@ impl App {
// remove and close existing window with the same name // remove and close existing window with the same name
let _ = self.close_window(window_name); let _ = self.close_window(window_name);
log::info!("Opening window {}", window_name);
let mut window_def = self let mut window_def = self
.eww_config .eww_config
.get_windows() .get_windows()
@ -177,6 +179,7 @@ impl App {
} }
pub fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> { pub fn reload_all_windows(&mut self, config: config::EwwConfig) -> Result<()> {
log::info!("Reloading windows");
// refresh script-var poll stuff // refresh script-var poll stuff
util::print_result_err( util::print_result_err(
"while setting up script-var commands", "while setting up script-var commands",

View file

@ -113,20 +113,21 @@ pub enum OptAction {
impl OptAction { impl OptAction {
fn into_eww_command(self) -> (app::EwwCommand, Option<crossbeam_channel::Receiver<String>>) { fn into_eww_command(self) -> (app::EwwCommand, Option<crossbeam_channel::Receiver<String>>) {
match self { let command = match self {
OptAction::Update { fieldname, value } => (app::EwwCommand::UpdateVar(fieldname, value), None), OptAction::Update { fieldname, value } => app::EwwCommand::UpdateVar(fieldname, value),
OptAction::OpenWindow { window_name, pos, size } => (app::EwwCommand::OpenWindow { window_name, pos, size }, None), OptAction::OpenWindow { window_name, pos, size } => app::EwwCommand::OpenWindow { window_name, pos, size },
OptAction::CloseWindow { window_name } => (app::EwwCommand::CloseWindow { window_name }, None), OptAction::CloseWindow { window_name } => app::EwwCommand::CloseWindow { window_name },
OptAction::KillServer => (app::EwwCommand::KillServer, None), OptAction::KillServer => app::EwwCommand::KillServer,
OptAction::ShowState => { OptAction::ShowState => {
let (send, recv) = crossbeam_channel::unbounded(); let (send, recv) = crossbeam_channel::unbounded();
(app::EwwCommand::PrintState(send), Some(recv)) return (app::EwwCommand::PrintState(send), Some(recv));
} }
OptAction::ShowDebug => { OptAction::ShowDebug => {
let (send, recv) = crossbeam_channel::unbounded(); let (send, recv) = crossbeam_channel::unbounded();
(app::EwwCommand::PrintDebug(send), Some(recv)) return (app::EwwCommand::PrintDebug(send), Some(recv));
} }
} };
(command, None)
} }
fn is_server_command(&self) -> bool { fn is_server_command(&self) -> bool {
@ -163,7 +164,7 @@ fn try_main() -> Result<()> {
} }
fn initialize_server(opts: Opt) -> Result<()> { fn initialize_server(opts: Opt) -> Result<()> {
if opts.action == OptAction::KillServer || !opts.action.is_server_command() { if !opts.action.is_server_command() {
println!("No eww server running"); println!("No eww server running");
return Ok(()); return Ok(());
} }
@ -182,6 +183,7 @@ fn initialize_server(opts: Opt) -> Result<()> {
gtk::init()?; gtk::init()?;
let (evt_send, evt_recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); let (evt_send, evt_recv) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
log::info!("Initializing script var handler");
let mut script_var_handler = script_var_handler::ScriptVarHandler::new(evt_send.clone())?; let mut script_var_handler = script_var_handler::ScriptVarHandler::new(evt_send.clone())?;
script_var_handler.initialize_clean(eww_config.get_script_vars().clone())?; script_var_handler.initialize_clean(eww_config.get_script_vars().clone())?;
@ -203,6 +205,7 @@ fn initialize_server(opts: Opt) -> Result<()> {
} }
// run the command that eww was started with // run the command that eww was started with
log::info!("running command: {:?}", &opts.action);
let (command, maybe_response_recv) = opts.action.into_eww_command(); let (command, maybe_response_recv) = opts.action.into_eww_command();
app.handle_command(command); app.handle_command(command);
if let Some(response_recv) = maybe_response_recv { if let Some(response_recv) = maybe_response_recv {
@ -292,6 +295,8 @@ fn do_detach() -> Result<()> {
nix::unistd::ForkResult::Child => {} nix::unistd::ForkResult::Child => {}
} }
nix::unistd::setsid().context("Failed to run setsid")?;
let file = std::fs::OpenOptions::new() let file = std::fs::OpenOptions::new()
.create(true) .create(true)
.append(true) .append(true)
@ -308,7 +313,6 @@ fn do_detach() -> Result<()> {
if nix::unistd::isatty(2)? { if nix::unistd::isatty(2)? {
nix::unistd::dup2(std::io::stderr().as_raw_fd(), fd)?; nix::unistd::dup2(std::io::stderr().as_raw_fd(), fd)?;
} }
nix::unistd::close(fd)?;
Ok(()) Ok(())
} }

View file

@ -1,9 +1,4 @@
use std::{ use std::{collections::HashMap, ffi::CString, io::BufReader, time::Duration};
collections::HashMap,
io::BufReader,
process::{Child, Stdio},
time::Duration,
};
use crate::{app, config, eww_state, util, value::PrimitiveValue}; use crate::{app, config, eww_state, util, value::PrimitiveValue};
use anyhow::*; use anyhow::*;
@ -11,7 +6,7 @@ use app::EwwCommand;
use glib; use glib;
use itertools::Itertools; use itertools::Itertools;
use scheduled_executor; use scheduled_executor;
use std::io::BufRead; use std::{io::BufRead, os::unix::io::AsRawFd};
/// Handler that manages running and updating [ScriptVar]s /// Handler that manages running and updating [ScriptVar]s
pub struct ScriptVarHandler { pub struct ScriptVarHandler {
@ -54,6 +49,7 @@ impl ScriptVarHandler {
} }
self.setup_poll_tasks(&poll_script_vars)?; self.setup_poll_tasks(&poll_script_vars)?;
self.setup_tail_tasks(&tail_script_vars)?; self.setup_tail_tasks(&tail_script_vars)?;
log::info!("Finished initializing script-var-handler");
Ok(()) Ok(())
} }
@ -80,6 +76,7 @@ impl ScriptVarHandler {
) )
}) })
.collect_vec(); .collect_vec();
log::info!("finished setting up poll tasks");
Ok(()) Ok(())
} }
@ -88,13 +85,18 @@ impl ScriptVarHandler {
log::info!("initializing handler for tail script vars"); log::info!("initializing handler for tail script vars");
let mut sources = popol::Sources::with_capacity(tail_script_vars.len()); let mut sources = popol::Sources::with_capacity(tail_script_vars.len());
// TODO clean up this unnecessary vec, it really should not be needed.
// should be possibel to just keep a BufReader in TailVarProcess directly
let mut command_children = Vec::new(); let mut command_children = Vec::new();
let mut command_out_handles = HashMap::new(); let mut command_out_handles: HashMap<_, BufReader<filedescriptor::FileDescriptor>> = HashMap::new();
for var in tail_script_vars { for var in tail_script_vars {
if let Some(mut child) = try_run_command(&var.command) { match TailVarProcess::run(&var.command) {
command_out_handles.insert(var.name.clone(), BufReader::new(child.stdout.take().unwrap())); Ok(process) => {
command_children.push(child); command_out_handles.insert(var.name.clone(), BufReader::new(process.out_fd.try_clone()?));
command_children.push(process);
}
Err(err) => eprintln!("Failed to launch script-var command for tail: {:?}", err),
} }
} }
@ -130,9 +132,7 @@ impl ScriptVarHandler {
} }
// stop child processes after exit // stop child processes after exit
for mut child in command_children { command_children.drain(..).for_each(|process| process.kill());
let _ = child.kill();
}
}); });
self.tail_handler_thread = Some(thread_handle); self.tail_handler_thread = Some(thread_handle);
Ok(()) Ok(())
@ -145,23 +145,42 @@ impl Drop for ScriptVarHandler {
} }
} }
/// Run a command in sh, returning its stdout-handle wrapped in a #[derive(Debug)]
/// [`BufReader`]. If running the command fails, will print a warning struct TailVarProcess {
/// and return `None`. pid: nix::unistd::Pid,
fn try_run_command(command: &str) -> Option<Child> { out_fd: filedescriptor::FileDescriptor,
let result = std::process::Command::new("sh") }
.arg("-c")
.arg(command)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.stdin(Stdio::null())
.spawn();
match result { impl TailVarProcess {
Ok(handle) => Some(handle), pub fn run(command: &str) -> Result<Self> {
Err(err) => { use nix::unistd::*;
eprintln!("WARN: Error running command from script-variable: {:?}", err);
None let pipe = filedescriptor::Pipe::new()?;
match unsafe { fork()? } {
ForkResult::Child => {
std::mem::drop(pipe.read);
dup2(pipe.write.as_raw_fd(), std::io::stdout().as_raw_fd())?;
setpgid(Pid::from_raw(0), Pid::from_raw(0))?;
execv(
CString::new("/bin/sh")?.as_ref(),
&[CString::new("/bin/sh")?, CString::new("-c")?, CString::new(command)?],
)?;
unreachable!("Child fork called exec, thus the process was replaced by the command the user provided");
}
ForkResult::Parent { child, .. } => {
std::mem::drop(pipe.write);
setpgid(child, child)?;
Ok(TailVarProcess {
pid: child,
out_fd: pipe.read,
})
}
} }
} }
pub fn kill(self) {
let result = nix::sys::signal::kill(self.pid, Some(nix::sys::signal::SIGTERM));
util::print_result_err("Killing tail-var process", &result);
}
} }