Supervise daemons with pidfds and restart deadlines
This commit is contained in:
1 parent
181b7bce9a
commit
8bb0b1da9c
3 files changed
+142
-16
No files matched your search
+134
-1
@@ -1,7 +1,15 @@
|
|||||||
//! Session-owned services. Never grant shell/effects privileges to daemons.
|
//! Session-owned services. Never grant shell/effects privileges to daemons.
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::os::fd::{FromRawFd, OwnedFd};
|
||||||
use std::process::Child;
|
use std::process::Child;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use smithay::reexports::calloop::generic::Generic;
|
||||||
|
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
|
||||||
|
use smithay::reexports::calloop::{Interest, Mode, PostAction, RegistrationToken};
|
||||||
|
|
||||||
use crate::Ferese;
|
use crate::Ferese;
|
||||||
use crate::config::DaemonConfig;
|
use crate::config::DaemonConfig;
|
||||||
use crate::private_client::ClientCapabilities;
|
use crate::private_client::ClientCapabilities;
|
||||||
@@ -13,9 +21,109 @@ struct Service {
|
|||||||
finished: bool,
|
finished: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Runner(Vec<Service>);
|
pub(crate) struct Runner(
|
||||||
|
Vec<Service>,
|
||||||
|
HashMap<u32, RegistrationToken>,
|
||||||
|
Option<(RegistrationToken, Instant)>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn pidfd(pid: u32) -> std::io::Result<OwnedFd> {
|
||||||
|
// SAFETY: pidfd_open has no pointer arguments and returns a new owned fd.
|
||||||
|
let fd = unsafe { libc::syscall(libc::SYS_pidfd_open, pid, 0) };
|
||||||
|
if fd < 0 {
|
||||||
|
return Err(std::io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
// SAFETY: a successful syscall returned a unique valid descriptor.
|
||||||
|
Ok(unsafe { OwnedFd::from_raw_fd(fd as i32) })
|
||||||
|
}
|
||||||
|
|
||||||
impl Runner {
|
impl Runner {
|
||||||
|
pub fn start(state: &mut Ferese) -> Rc<RefCell<Self>> {
|
||||||
|
let runner = Rc::new(RefCell::new(Self::new(
|
||||||
|
&state.autostart,
|
||||||
|
state.direct_backend.is_none(),
|
||||||
|
)));
|
||||||
|
Self::refresh(&runner, state);
|
||||||
|
runner
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh(owner: &Rc<RefCell<Self>>, state: &mut Ferese) {
|
||||||
|
let mut runner = owner.borrow_mut();
|
||||||
|
runner.tick(state);
|
||||||
|
let pids = runner
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter_map(|service| service.child.as_ref().map(Child::id))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
runner.1.retain(|pid, token| {
|
||||||
|
if pids.contains(pid) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
state.loop_handle.remove(*token);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut watch_retry = None;
|
||||||
|
for pid in pids {
|
||||||
|
if runner.1.contains_key(&pid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let weak = Rc::downgrade(owner);
|
||||||
|
let source = pidfd(pid).map(|fd| Generic::new(fd, Interest::READ, Mode::Level));
|
||||||
|
let result = source.and_then(|source| {
|
||||||
|
state
|
||||||
|
.loop_handle
|
||||||
|
.insert_source(source, move |_, _, state| {
|
||||||
|
if let Some(owner) = weak.upgrade() {
|
||||||
|
owner.borrow_mut().1.remove(&pid);
|
||||||
|
Self::refresh(&owner, state);
|
||||||
|
}
|
||||||
|
Ok(PostAction::Remove)
|
||||||
|
})
|
||||||
|
.map_err(std::io::Error::other)
|
||||||
|
});
|
||||||
|
match result {
|
||||||
|
Ok(token) => {
|
||||||
|
runner.1.insert(pid, token);
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
tracing::warn!(%error, pid, "could not watch daemon exit; retrying registration");
|
||||||
|
watch_retry = Some(Instant::now() + Duration::from_secs(5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let deadline = runner
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter(|service| service.child.is_none() && !service.finished)
|
||||||
|
.map(|service| service.next_start)
|
||||||
|
.chain(watch_retry)
|
||||||
|
.min();
|
||||||
|
if runner.2.as_ref().map(|(_, deadline)| *deadline) == deadline {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some((token, _)) = runner.2.take() {
|
||||||
|
state.loop_handle.remove(token);
|
||||||
|
}
|
||||||
|
if let Some(deadline) = deadline {
|
||||||
|
let weak = Rc::downgrade(owner);
|
||||||
|
match state
|
||||||
|
.loop_handle
|
||||||
|
.insert_source(Timer::from_deadline(deadline), move |_, _, state| {
|
||||||
|
if let Some(owner) = weak.upgrade() {
|
||||||
|
owner.borrow_mut().2 = None;
|
||||||
|
Self::refresh(&owner, state);
|
||||||
|
}
|
||||||
|
TimeoutAction::Drop
|
||||||
|
}) {
|
||||||
|
Ok(token) => runner.2 = Some((token, deadline)),
|
||||||
|
Err(error) => tracing::warn!(%error, "could not arm daemon restart deadline"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reconcile(&mut self, configs: &[DaemonConfig], nested: bool) {
|
pub fn reconcile(&mut self, configs: &[DaemonConfig], nested: bool) {
|
||||||
let mut previous = std::mem::take(&mut self.0);
|
let mut previous = std::mem::take(&mut self.0);
|
||||||
for config in configs.iter().filter(|c| c.enabled && (!nested || c.nested)) {
|
for config in configs.iter().filter(|c| c.enabled && (!nested || c.nested)) {
|
||||||
@@ -62,6 +170,8 @@ impl Runner {
|
|||||||
finished: false,
|
finished: false,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
HashMap::new(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +227,29 @@ impl Drop for Runner {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn pidfd_wakes_the_loop_once_and_the_child_is_reaped() {
|
||||||
|
let mut child = std::process::Command::new("/bin/sh")
|
||||||
|
.args(["-c", "exit 7"])
|
||||||
|
.spawn()
|
||||||
|
.unwrap();
|
||||||
|
let fd = pidfd(child.id()).unwrap();
|
||||||
|
let mut events = smithay::reexports::calloop::EventLoop::<usize>::try_new().unwrap();
|
||||||
|
events
|
||||||
|
.handle()
|
||||||
|
.insert_source(Generic::new(fd, Interest::READ, Mode::Level), move |_, _, count| {
|
||||||
|
assert_eq!(child.wait().unwrap().code(), Some(7));
|
||||||
|
*count += 1;
|
||||||
|
Ok(PostAction::Remove)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let mut count = 0;
|
||||||
|
events.dispatch(Some(Duration::from_secs(2)), &mut count).unwrap();
|
||||||
|
assert_eq!(count, 1);
|
||||||
|
events.dispatch(Some(Duration::ZERO), &mut count).unwrap();
|
||||||
|
assert_eq!(count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn live_reconcile_keeps_existing_process_and_changes_policy_without_restart() {
|
fn live_reconcile_keeps_existing_process_and_changes_policy_without_restart() {
|
||||||
let mut config = DaemonConfig {
|
let mut config = DaemonConfig {
|
||||||
|
|||||||
+3
-15
@@ -80,7 +80,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
let directory = config::config_path()
|
let directory = config::config_path()
|
||||||
.and_then(|path| path.parent().map(ToOwned::to_owned))
|
.and_then(|path| path.parent().map(ToOwned::to_owned))
|
||||||
.unwrap_or_else(|| std::path::PathBuf::from("."));
|
.unwrap_or_else(|| std::path::PathBuf::from("."));
|
||||||
let (config, runtime, candidate) = theme::prepare(source, &directory)?;
|
let (_, runtime, candidate) = theme::prepare(source, &directory)?;
|
||||||
let mut event_loop = EventLoop::try_new()?;
|
let mut event_loop = EventLoop::try_new()?;
|
||||||
let signals = Signals::new(&[Signal::SIGINT, Signal::SIGTERM])?;
|
let signals = Signals::new(&[Signal::SIGINT, Signal::SIGTERM])?;
|
||||||
event_loop
|
event_loop
|
||||||
@@ -95,22 +95,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
theme::init(&mut event_loop, &mut state, candidate)?;
|
theme::init(&mut event_loop, &mut state, candidate)?;
|
||||||
overview::init_font_loader(&mut event_loop, &mut state)?;
|
overview::init_font_loader(&mut event_loop, &mut state)?;
|
||||||
backends::init(launch.backend, &mut event_loop, &mut state)?;
|
backends::init(launch.backend, &mut event_loop, &mut state)?;
|
||||||
use calloop::timer::{TimeoutAction, Timer};
|
|
||||||
info!(socket = ?state.socket_name, backend = ?launch.backend, "Ferese is accepting Wayland clients");
|
info!(socket = ?state.socket_name, backend = ?launch.backend, "Ferese is accepting Wayland clients");
|
||||||
let mut child = spawn_client(&mut state, launch.client, launch.client_capabilities);
|
let mut child = spawn_client(&mut state, launch.client, launch.client_capabilities);
|
||||||
let runner = std::rc::Rc::new(std::cell::RefCell::new(daemon::Runner::new(
|
let runner = daemon::Runner::start(&mut state);
|
||||||
&config.autostart,
|
state.daemons = Some(runner.clone());
|
||||||
launch.backend == backends::BackendKind::Nested,
|
|
||||||
)));
|
|
||||||
runner.borrow_mut().tick(&mut state);
|
|
||||||
let services = runner.clone();
|
|
||||||
event_loop.handle().insert_source(
|
|
||||||
Timer::from_duration(Duration::from_secs(1)),
|
|
||||||
move |_, _, state: &mut Ferese| {
|
|
||||||
services.borrow_mut().tick(state);
|
|
||||||
TimeoutAction::ToDuration(Duration::from_secs(1))
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
let reload_handle = event_loop.handle();
|
let reload_handle = event_loop.handle();
|
||||||
let result = event_loop.run(None, &mut state, |state| {
|
let result = event_loop.run(None, &mut state, |state| {
|
||||||
state.poll_theme();
|
state.poll_theme();
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ pub struct Ferese {
|
|||||||
output_identity_ids: HashMap<String, OutputId>,
|
output_identity_ids: HashMap<String, OutputId>,
|
||||||
pub(crate) windows: WindowRegistry<Window>,
|
pub(crate) windows: WindowRegistry<Window>,
|
||||||
pub(crate) render: crate::render::RenderResources,
|
pub(crate) render: crate::render::RenderResources,
|
||||||
|
pub(crate) daemons: Option<std::rc::Rc<std::cell::RefCell<crate::daemon::Runner>>>,
|
||||||
pub(crate) display_presentation: crate::display_presentation::DisplayPresentation,
|
pub(crate) display_presentation: crate::display_presentation::DisplayPresentation,
|
||||||
pub(crate) nested_backend: Option<NestedBackend>,
|
pub(crate) nested_backend: Option<NestedBackend>,
|
||||||
pub(crate) wallpaper: WallpaperState,
|
pub(crate) wallpaper: WallpaperState,
|
||||||
@@ -563,6 +564,7 @@ impl Ferese {
|
|||||||
windows: WindowRegistry::default(),
|
windows: WindowRegistry::default(),
|
||||||
render: Default::default(),
|
render: Default::default(),
|
||||||
display_presentation: Default::default(),
|
display_presentation: Default::default(),
|
||||||
|
daemons: None,
|
||||||
nested_backend: None,
|
nested_backend: None,
|
||||||
wallpaper: WallpaperState::with_wakeup(config.wallpaper, Some(event_loop.get_signal())),
|
wallpaper: WallpaperState::with_wakeup(config.wallpaper, Some(event_loop.get_signal())),
|
||||||
window_stack: WindowStack::default(),
|
window_stack: WindowStack::default(),
|
||||||
@@ -747,6 +749,9 @@ impl Ferese {
|
|||||||
self.viewport_spring_config = config.viewport_spring_config;
|
self.viewport_spring_config = config.viewport_spring_config;
|
||||||
self.output_profiles = config.output_profiles;
|
self.output_profiles = config.output_profiles;
|
||||||
self.autostart = config.autostart;
|
self.autostart = config.autostart;
|
||||||
|
if let Some(runner) = self.daemons.clone() {
|
||||||
|
crate::daemon::Runner::refresh(&runner, self);
|
||||||
|
}
|
||||||
|
|
||||||
if touchpad_changed {
|
if touchpad_changed {
|
||||||
crate::backends::direct::reload_input_devices(self);
|
crate::backends::direct::reload_input_devices(self);
|
||||||
|
|||||||
Reference in new issue
Block a user