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",
"derive_more",
"extend",
"filedescriptor",
"gdk",
"gdk-pixbuf",
"gdkx11",
@ -345,6 +346,17 @@ dependencies = [
"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]]
name = "filetime"
version = "0.2.12"

View file

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

View file

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

View file

@ -113,20 +113,21 @@ pub enum OptAction {
impl OptAction {
fn into_eww_command(self) -> (app::EwwCommand, Option<crossbeam_channel::Receiver<String>>) {
match self {
OptAction::Update { fieldname, value } => (app::EwwCommand::UpdateVar(fieldname, value), None),
OptAction::OpenWindow { window_name, pos, size } => (app::EwwCommand::OpenWindow { window_name, pos, size }, None),
OptAction::CloseWindow { window_name } => (app::EwwCommand::CloseWindow { window_name }, None),
OptAction::KillServer => (app::EwwCommand::KillServer, None),
let command = match self {
OptAction::Update { fieldname, value } => app::EwwCommand::UpdateVar(fieldname, value),
OptAction::OpenWindow { window_name, pos, size } => app::EwwCommand::OpenWindow { window_name, pos, size },
OptAction::CloseWindow { window_name } => app::EwwCommand::CloseWindow { window_name },
OptAction::KillServer => app::EwwCommand::KillServer,
OptAction::ShowState => {
let (send, recv) = crossbeam_channel::unbounded();
(app::EwwCommand::PrintState(send), Some(recv))
return (app::EwwCommand::PrintState(send), Some(recv));
}
OptAction::ShowDebug => {
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 {
@ -163,7 +164,7 @@ fn try_main() -> 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");
return Ok(());
}
@ -182,6 +183,7 @@ fn initialize_server(opts: Opt) -> Result<()> {
gtk::init()?;
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())?;
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
log::info!("running command: {:?}", &opts.action);
let (command, maybe_response_recv) = opts.action.into_eww_command();
app.handle_command(command);
if let Some(response_recv) = maybe_response_recv {
@ -292,6 +295,8 @@ fn do_detach() -> Result<()> {
nix::unistd::ForkResult::Child => {}
}
nix::unistd::setsid().context("Failed to run setsid")?;
let file = std::fs::OpenOptions::new()
.create(true)
.append(true)
@ -308,7 +313,6 @@ fn do_detach() -> Result<()> {
if nix::unistd::isatty(2)? {
nix::unistd::dup2(std::io::stderr().as_raw_fd(), fd)?;
}
nix::unistd::close(fd)?;
Ok(())
}

View file

@ -1,9 +1,4 @@
use std::{
collections::HashMap,
io::BufReader,
process::{Child, Stdio},
time::Duration,
};
use std::{collections::HashMap, ffi::CString, io::BufReader, time::Duration};
use crate::{app, config, eww_state, util, value::PrimitiveValue};
use anyhow::*;
@ -11,7 +6,7 @@ use app::EwwCommand;
use glib;
use itertools::Itertools;
use scheduled_executor;
use std::io::BufRead;
use std::{io::BufRead, os::unix::io::AsRawFd};
/// Handler that manages running and updating [ScriptVar]s
pub struct ScriptVarHandler {
@ -54,6 +49,7 @@ impl ScriptVarHandler {
}
self.setup_poll_tasks(&poll_script_vars)?;
self.setup_tail_tasks(&tail_script_vars)?;
log::info!("Finished initializing script-var-handler");
Ok(())
}
@ -80,6 +76,7 @@ impl ScriptVarHandler {
)
})
.collect_vec();
log::info!("finished setting up poll tasks");
Ok(())
}
@ -88,13 +85,18 @@ impl ScriptVarHandler {
log::info!("initializing handler for tail script vars");
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_out_handles = HashMap::new();
let mut command_out_handles: HashMap<_, BufReader<filedescriptor::FileDescriptor>> = HashMap::new();
for var in tail_script_vars {
if let Some(mut child) = try_run_command(&var.command) {
command_out_handles.insert(var.name.clone(), BufReader::new(child.stdout.take().unwrap()));
command_children.push(child);
match TailVarProcess::run(&var.command) {
Ok(process) => {
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
for mut child in command_children {
let _ = child.kill();
}
command_children.drain(..).for_each(|process| process.kill());
});
self.tail_handler_thread = Some(thread_handle);
Ok(())
@ -145,23 +145,42 @@ impl Drop for ScriptVarHandler {
}
}
/// Run a command in sh, returning its stdout-handle wrapped in a
/// [`BufReader`]. If running the command fails, will print a warning
/// and return `None`.
fn try_run_command(command: &str) -> Option<Child> {
let result = std::process::Command::new("sh")
.arg("-c")
.arg(command)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.stdin(Stdio::null())
.spawn();
#[derive(Debug)]
struct TailVarProcess {
pid: nix::unistd::Pid,
out_fd: filedescriptor::FileDescriptor,
}
match result {
Ok(handle) => Some(handle),
Err(err) => {
eprintln!("WARN: Error running command from script-variable: {:?}", err);
None
impl TailVarProcess {
pub fn run(command: &str) -> Result<Self> {
use nix::unistd::*;
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);
}
}