Keep isolated document extraction working on macOS
This commit is contained in:
1 parent
5b93dd67a7
commit
dd589e88ae
3 files changed
+60
No files matched your search
@@ -48,6 +48,33 @@ enum Event {
|
||||
Stderr,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn check_resident_memory(child: &mut Child, maximum: u64) -> Result<(), ProcessError> {
|
||||
let mut info = std::mem::MaybeUninit::<libc::proc_taskinfo>::uninit();
|
||||
let size = std::mem::size_of::<libc::proc_taskinfo>();
|
||||
// The child stays owned and unreaped until I/O completes. A failed query is
|
||||
// accepted only when it has already exited; otherwise fail closed.
|
||||
let read = unsafe {
|
||||
libc::proc_pidinfo(
|
||||
child.id() as i32,
|
||||
libc::PROC_PIDTASKINFO,
|
||||
0,
|
||||
info.as_mut_ptr().cast(),
|
||||
size as i32,
|
||||
)
|
||||
};
|
||||
if read != size as i32 {
|
||||
if child.try_wait().map_err(failed)?.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(failed("Cannot inspect extraction memory usage"));
|
||||
}
|
||||
if unsafe { info.assume_init() }.pti_resident_size > maximum {
|
||||
return Err(failed("Extraction worker resident memory exceeds budget"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Includes pipe writes, response EOF and process exit in the same deadline.
|
||||
/// No parser fallback occurs when limits, transport or worker execution fail.
|
||||
pub fn run(
|
||||
@@ -161,6 +188,8 @@ pub fn run(
|
||||
if started.elapsed() >= config.timeout {
|
||||
return Err(ProcessError::TimedOut);
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
check_resident_memory(&mut owned.child, 512 * 1024 * 1024)?;
|
||||
match rx.recv_timeout(
|
||||
Duration::from_millis(5).min(config.timeout.saturating_sub(started.elapsed())),
|
||||
) {
|
||||
@@ -288,6 +317,20 @@ mod tests {
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[test]
|
||||
fn mac_memory_budget_rejects_a_live_owned_child() {
|
||||
let mut child = Command::new("/bin/sleep").arg("30").spawn().unwrap();
|
||||
// Wait for exec before measuring, then impose a deliberately tiny budget.
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
let result = check_resident_memory(&mut child, 1);
|
||||
let _ = child.kill();
|
||||
child.wait().unwrap();
|
||||
assert!(
|
||||
matches!(result, Err(ProcessError::Failed(message)) if message.contains("exceeds budget"))
|
||||
);
|
||||
}
|
||||
|
||||
fn fixtures() -> tempfile::TempDir {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let source = directory.path().join("fake.rs");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::{ensure, Result};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
const MEMORY_BYTES: usize = 512 * 1024 * 1024;
|
||||
|
||||
#[cfg(windows)]
|
||||
@@ -37,7 +38,10 @@ pub fn apply() -> Result<()> {
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn apply() -> Result<()> {
|
||||
// Darwin rejects finite AS/DATA limits. The parent enforces a sampled RSS
|
||||
// budget there; retain the CPU limit and bounded path-free transport here.
|
||||
for (resource, maximum) in [
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
(libc::RLIMIT_AS, MEMORY_BYTES as libc::rlim_t),
|
||||
(libc::RLIMIT_CPU, 30 as libc::rlim_t),
|
||||
] {
|
||||
|
||||
Reference in new issue
Block a user