Skip to content

Commit 67e3c25

Browse files
Shut down gracefully if parent exits (#830)
* shut down gracefully if parent exits * only use on linux * Move monitoring into `sys/unix/linux` to abstract around the OS details (#831) --------- Co-authored-by: Davis Vaughan <[email protected]>
1 parent fa595e3 commit 67e3c25

File tree

9 files changed

+172
-0
lines changed

9 files changed

+172
-0
lines changed

.vscode/launch.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
"osx": {
1313
"program": "ark"
1414
},
15+
"linux": {
16+
"program": "ark"
17+
},
1518
"windows": {
1619
"program": "ark.exe"
1720
},

crates/ark/src/start.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ pub fn start_kernel(
118118
panic!("Couldn't connect to frontend: {err:?}");
119119
}
120120

121+
// Start parent process monitoring for graceful shutdown if applicable. Currently we
122+
// only do this for Linux since it uses `prctl()`.
123+
if let Err(err) = crate::sys::parent_monitor::start_parent_monitoring(r_request_tx.clone()) {
124+
log::error!("Failed to start parent process monitoring: {err}");
125+
}
126+
121127
// Start R
122128
crate::interface::RMain::start(
123129
r_args,

crates/ark/src/sys/unix.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,13 @@ pub mod interface;
1111
pub mod path;
1212
pub mod signals;
1313
pub mod traps;
14+
15+
cfg_if::cfg_if! {
16+
if #[cfg(target_os = "linux")] {
17+
mod linux;
18+
pub use self::linux::*;
19+
} else if #[cfg(target_os = "macos")] {
20+
mod macos;
21+
pub use self::macos::*;
22+
}
23+
}

crates/ark/src/sys/unix/linux.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* linux.rs
3+
*
4+
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
5+
*
6+
*/
7+
8+
pub mod parent_monitor;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//
2+
// parent_monitor.rs
3+
//
4+
// Copyright (C) 2025 Posit Software, PBC. All rights reserved.
5+
//
6+
7+
//! Parent process monitoring for graceful shutdown on Linux.
8+
//!
9+
//! This module implements Linux-specific functionality to monitor the parent process
10+
//! and trigger graceful shutdown of the kernel when the parent exits.
11+
12+
use std::sync::atomic::AtomicBool;
13+
use std::sync::atomic::Ordering;
14+
use std::thread;
15+
use std::time::Duration;
16+
17+
use crossbeam::channel::Sender;
18+
use nix::sys::signal::SigHandler;
19+
use nix::sys::signal::Signal;
20+
use nix::sys::signal::{self};
21+
use stdext::spawn;
22+
23+
use crate::request::RRequest;
24+
25+
/// Flag to track if parent monitoring is active
26+
static PARENT_MONITORING_ACTIVE: AtomicBool = AtomicBool::new(false);
27+
28+
/// Start monitoring the parent process for exit.
29+
///
30+
/// On Linux, this uses `prctl(PR_SET_PDEATHSIG)` to set up a signal that will be
31+
/// delivered when the parent process exits. When this signal is received, a graceful
32+
/// shutdown request is sent to the R execution thread.
33+
///
34+
/// # Arguments
35+
/// * `r_request_tx` - Channel to send shutdown requests to the R execution thread
36+
///
37+
pub fn start_parent_monitoring(r_request_tx: Sender<RRequest>) -> anyhow::Result<()> {
38+
// Check if already active to avoid multiple monitors
39+
if PARENT_MONITORING_ACTIVE.swap(true, Ordering::SeqCst) {
40+
log::warn!("Parent process monitoring is already active");
41+
return Ok(());
42+
}
43+
44+
log::info!("Starting parent process monitoring");
45+
46+
// Set up SIGUSR1 to be delivered when parent dies
47+
const PR_SET_PDEATHSIG: libc::c_int = 1;
48+
const SIGUSR1: libc::c_int = 10;
49+
50+
let result = unsafe { libc::prctl(PR_SET_PDEATHSIG, SIGUSR1, 0, 0, 0) };
51+
if result != 0 {
52+
PARENT_MONITORING_ACTIVE.store(false, Ordering::SeqCst);
53+
let errno = unsafe { *libc::__errno_location() };
54+
return Err(anyhow::anyhow!(
55+
"Failed to set parent death signal: errno {errno}"
56+
));
57+
}
58+
59+
// Spawn a thread to monitor for the signal and handle shutdown
60+
spawn!("parent-monitor", move || {
61+
monitor_parent_death_signal(r_request_tx);
62+
});
63+
64+
log::info!("Parent process monitoring started successfully");
65+
Ok(())
66+
}
67+
68+
fn monitor_parent_death_signal(r_request_tx: Sender<RRequest>) {
69+
// Use a static flag to signal when SIGUSR1 is received
70+
static SIGUSR1_RECEIVED: AtomicBool = AtomicBool::new(false);
71+
72+
// Signal handler that just sets a flag
73+
extern "C" fn sigusr1_handler(_: libc::c_int) {
74+
SIGUSR1_RECEIVED.store(true, Ordering::SeqCst);
75+
}
76+
77+
// Install signal handler for SIGUSR1
78+
unsafe {
79+
if let Err(err) = signal::signal(Signal::SIGUSR1, SigHandler::Handler(sigusr1_handler)) {
80+
log::error!("Failed to install SIGUSR1 handler: {err}");
81+
return;
82+
}
83+
}
84+
85+
log::trace!("Parent death signal monitoring thread started");
86+
87+
// Poll for the signal flag
88+
loop {
89+
if SIGUSR1_RECEIVED.load(Ordering::SeqCst) {
90+
log::info!("Parent process has exited, initiating graceful shutdown");
91+
92+
// Send shutdown request to R execution thread (false = final shutdown, not restart)
93+
if let Err(err) = r_request_tx.send(RRequest::Shutdown(false)) {
94+
log::error!("Failed to send shutdown request, exiting: {err}");
95+
// If we can't send the shutdown request, force exit as fallback
96+
std::process::exit(1);
97+
}
98+
break;
99+
}
100+
101+
// Sleep for 5s to avoid busy-waiting; the goal is to ensure Ark doesn't
102+
// hang around forever after the parent exits, no need for
103+
// high-frequency checks
104+
thread::sleep(Duration::from_millis(5000));
105+
}
106+
107+
log::trace!("Parent death signal monitoring thread exiting");
108+
}

crates/ark/src/sys/unix/macos.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
* macos.rs
3+
*
4+
* Copyright (C) 2025 Posit Software, PBC. All rights reserved.
5+
*
6+
*/
7+
8+
pub mod parent_monitor;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// parent_monitor.rs
3+
//
4+
// Copyright (C) 2025 Posit Software, PBC. All rights reserved.
5+
//
6+
7+
use crossbeam::channel::Sender;
8+
9+
use crate::request::RRequest;
10+
11+
/// No parent monitoring on MacOS
12+
pub fn start_parent_monitoring(_r_request_tx: Sender<RRequest>) -> anyhow::Result<()> {
13+
Ok(())
14+
}

crates/ark/src/sys/windows.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub mod console;
99
pub mod control;
1010
pub mod interface;
1111
mod locale;
12+
pub mod parent_monitor;
1213
pub mod path;
1314
pub mod signals;
1415
mod strings;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// parent_monitor.rs
3+
//
4+
// Copyright (C) 2025 Posit Software, PBC. All rights reserved.
5+
//
6+
7+
use crossbeam::channel::Sender;
8+
9+
use crate::request::RRequest;
10+
11+
/// No parent monitoring on Windows
12+
pub fn start_parent_monitoring(_r_request_tx: Sender<RRequest>) -> anyhow::Result<()> {
13+
Ok(())
14+
}

0 commit comments

Comments
 (0)