Handle native Mac extraction worker exit races
This commit is contained in:
1 parent
dd589e88ae
commit
feec06bc41
3 files changed
+28
-11
No files matched your search
@@ -50,10 +50,11 @@ enum Event {
|
||||
|
||||
#[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.
|
||||
// Task info can disappear just before an exiting child becomes waitable.
|
||||
// Retry that transition briefly; an alive uninspectable child fails closed.
|
||||
for _ in 0..10 {
|
||||
let mut info = std::mem::MaybeUninit::<libc::proc_taskinfo>::uninit();
|
||||
let read = unsafe {
|
||||
libc::proc_pidinfo(
|
||||
child.id() as i32,
|
||||
@@ -63,16 +64,18 @@ fn check_resident_memory(child: &mut Child, maximum: u64) -> Result<(), ProcessE
|
||||
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 read == size as i32 {
|
||||
if unsafe { info.assume_init() }.pti_resident_size > maximum {
|
||||
return Err(failed("Extraction worker resident memory exceeds budget"));
|
||||
}
|
||||
Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
if child.try_wait().map_err(failed)?.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
}
|
||||
Err(failed("Cannot inspect extraction memory usage"))
|
||||
}
|
||||
|
||||
/// Includes pipe writes, response EOF and process exit in the same deadline.
|
||||
|
||||
@@ -30,7 +30,15 @@ fn actual_parent_and_worker_return_original_docx_passage_and_digest() {
|
||||
let input = document.finish().unwrap().into_inner();
|
||||
let request = RequestHeader::from_bytes(DocumentFormat::Docx, &input).unwrap();
|
||||
let expected_hash = request.input_sha256.clone();
|
||||
let response = run(&config(), request, input, &AtomicBool::new(false)).unwrap();
|
||||
// Exercise short-lived native worker exit/task-inspection races too.
|
||||
for _ in 0..10 {
|
||||
let response = run(
|
||||
&config(),
|
||||
request.clone(),
|
||||
input.clone(),
|
||||
&AtomicBool::new(false),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(response.input_sha256, expected_hash);
|
||||
let ExtractionOutcome::Extracted { passages } = response.outcome else {
|
||||
panic!("Valid document rejected");
|
||||
@@ -40,6 +48,7 @@ fn actual_parent_and_worker_return_original_docx_passage_and_digest() {
|
||||
assert_eq!(passages[0].source.kind, "paragraph");
|
||||
assert_eq!(passages[0].source.number, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_rejection_and_cancellation_remain_distinct() {
|
||||
|
||||
@@ -114,3 +114,8 @@ it is not equivalent to a kernel-enforced hard address-space limit. Linux and
|
||||
Windows retain their existing resource policies. An owned live-child test
|
||||
verifies budget rejection; five process-owner tests and two actual parent/worker
|
||||
roundtrip/rejection tests pass on the Mac.
|
||||
|
||||
Resident-memory inspection also retries the short exit-to-waitable transition
|
||||
before treating a live child as uninspectable. Ten fresh installed CLI/helper
|
||||
DOCX runs returned the expected passage; the native roundtrip test repeats that
|
||||
short-lived worker path ten times to cover the race.
|
||||
Reference in new issue
Block a user