Kill the child processes at once on a second interrupt during the grace period
This commit is contained in:
+1
-1
@@ -114,6 +114,6 @@ pub fn run(config: SandboxConfig) -> Result<ExitStatus, SandboxError> {
|
|||||||
// bwrap ignores them: its death would SIGKILL the entrypoint through --die-with-parent.
|
// bwrap ignores them: its death would SIGKILL the entrypoint through --die-with-parent.
|
||||||
let bwrap_process = bwrap.spawn(signal_forwarder.ignore_forwarded_signals())?;
|
let bwrap_process = bwrap.spawn(signal_forwarder.ignore_forwarded_signals())?;
|
||||||
let status = signal_forwarder.forward_until_exit(&bwrap_process)?;
|
let status = signal_forwarder.forward_until_exit(&bwrap_process)?;
|
||||||
process_tree::terminate_and_reap_all(CLEANUP_GRACE_PERIOD)?;
|
process_tree::terminate_and_reap_all(CLEANUP_GRACE_PERIOD, signals::has_pending_interrupt)?;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -16,10 +16,13 @@ pub fn become_subreaper() -> io::Result<()> {
|
|||||||
Ok(prctl::set_child_subreaper(true)?)
|
Ok(prctl::set_child_subreaper(true)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn terminate_and_reap_all(grace_period: Duration) -> io::Result<()> {
|
pub fn terminate_and_reap_all(
|
||||||
|
grace_period: Duration,
|
||||||
|
should_stop_waiting: impl Fn() -> bool,
|
||||||
|
) -> io::Result<()> {
|
||||||
signal_all_children(&[Signal::TERM, Signal::CONT]);
|
signal_all_children(&[Signal::TERM, Signal::CONT]);
|
||||||
|
|
||||||
if reap_until(Instant::now() + grace_period)? {
|
if reap_until(Instant::now() + grace_period, should_stop_waiting)? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,11 +103,13 @@ fn send_signals(pidfd: &OwnedFd, signals: &[Signal]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reap_until(deadline: Instant) -> io::Result<bool> {
|
fn reap_until(deadline: Instant, should_stop_waiting: impl Fn() -> bool) -> io::Result<bool> {
|
||||||
loop {
|
loop {
|
||||||
match process::wait(WaitOptions::NOHANG) {
|
match process::wait(WaitOptions::NOHANG) {
|
||||||
Ok(Some(_)) => {}
|
Ok(Some(_)) => {}
|
||||||
Ok(None) if Instant::now() < deadline => thread::sleep(REAP_POLL_INTERVAL),
|
Ok(None) if Instant::now() < deadline && !should_stop_waiting() => {
|
||||||
|
thread::sleep(REAP_POLL_INTERVAL)
|
||||||
|
}
|
||||||
Ok(None) => return Ok(false),
|
Ok(None) => return Ok(false),
|
||||||
Err(Errno::CHILD) => return Ok(true),
|
Err(Errno::CHILD) => return Ok(true),
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
|
|||||||
@@ -177,6 +177,15 @@ fn reap_until_exit_of(child: Pid) -> io::Result<Option<ExitStatus>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_pending_interrupt() -> bool {
|
||||||
|
let interrupt = SigSet::from(Signal::SIGINT);
|
||||||
|
let no_wait = libc::timespec {
|
||||||
|
tv_sec: 0,
|
||||||
|
tv_nsec: 0,
|
||||||
|
};
|
||||||
|
unsafe { libc::sigtimedwait(interrupt.as_ref(), ptr::null_mut(), &no_wait) == libc::SIGINT }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn exit_with_status(status: ExitStatus) -> ! {
|
pub fn exit_with_status(status: ExitStatus) -> ! {
|
||||||
match status.signal() {
|
match status.signal() {
|
||||||
Some(number) => kill_self_with(number),
|
Some(number) => kill_self_with(number),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::os::unix::process::{CommandExt, ExitStatusExt};
|
use std::os::unix::process::{CommandExt, ExitStatusExt};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::{Child, Stdio};
|
use std::process::{Child, Stdio};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
@@ -113,6 +115,46 @@ fn entrypoint_inherits_no_signalfd() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_interrupt_keeps_grace_period_for_child_processes() {
|
||||||
|
let dir = TempDir::new().expect("failed to create temp dir");
|
||||||
|
let script = "setsid -f sh -c 'trap \"\" INT; trap \"sleep 0.5; touch cleaned; exit\" TERM; touch ready; sleep 60 & wait' </dev/null >/dev/null 2>&1
|
||||||
|
until [ -e ready ]; do sleep 0.01; done
|
||||||
|
: > entrypoint-ready
|
||||||
|
sleep 60";
|
||||||
|
|
||||||
|
let (_sandbox, mut child) = spawn_sandbox(&["--blacklist"], dir.path(), script);
|
||||||
|
wait_for_file(&dir.path().join("entrypoint-ready"));
|
||||||
|
send_signal(&child, libc::SIGINT);
|
||||||
|
child.wait().unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
dir.path().join("cleaned").exists(),
|
||||||
|
"child process did not get its grace period"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interrupt_during_grace_period_kills_child_processes_at_once() {
|
||||||
|
let dir = TempDir::new().expect("failed to create temp dir");
|
||||||
|
let script =
|
||||||
|
"setsid -f sh -c 'trap \"\" TERM; touch ready; sleep 60' </dev/null >/dev/null 2>&1
|
||||||
|
until [ -e ready ]; do sleep 0.01; done";
|
||||||
|
|
||||||
|
let (_sandbox, mut child) = spawn_sandbox(&["--blacklist"], dir.path(), script);
|
||||||
|
wait_for_file(&dir.path().join("ready"));
|
||||||
|
thread::sleep(Duration::from_millis(300));
|
||||||
|
let interrupted_at = Instant::now();
|
||||||
|
send_signal(&child, libc::SIGINT);
|
||||||
|
child.wait().unwrap();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
interrupted_at.elapsed() < Duration::from_secs(2),
|
||||||
|
"agent-sandbox waited {:?} after the interrupt",
|
||||||
|
interrupted_at.elapsed()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_sandbox(args: &[&str], dir: &Path, script: &str) -> (Sandbox, Child) {
|
fn spawn_sandbox(args: &[&str], dir: &Path, script: &str) -> (Sandbox, Child) {
|
||||||
let mut sandbox = Sandbox::new(args);
|
let mut sandbox = Sandbox::new(args);
|
||||||
let child = sandbox
|
let child = sandbox
|
||||||
|
|||||||
Reference in New Issue
Block a user