Handle native Mac extraction worker exit races
This commit is contained in:
1 parent
dd589e88ae
commit
feec06bc41
3 files changed
+44
-27
No files matched your search
@@ -50,29 +50,32 @@ enum Event {
|
|||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn check_resident_memory(child: &mut Child, maximum: u64) -> Result<(), ProcessError> {
|
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>();
|
let size = std::mem::size_of::<libc::proc_taskinfo>();
|
||||||
// The child stays owned and unreaped until I/O completes. A failed query is
|
// Task info can disappear just before an exiting child becomes waitable.
|
||||||
// accepted only when it has already exited; otherwise fail closed.
|
// Retry that transition briefly; an alive uninspectable child fails closed.
|
||||||
let read = unsafe {
|
for _ in 0..10 {
|
||||||
libc::proc_pidinfo(
|
let mut info = std::mem::MaybeUninit::<libc::proc_taskinfo>::uninit();
|
||||||
child.id() as i32,
|
let read = unsafe {
|
||||||
libc::PROC_PIDTASKINFO,
|
libc::proc_pidinfo(
|
||||||
0,
|
child.id() as i32,
|
||||||
info.as_mut_ptr().cast(),
|
libc::PROC_PIDTASKINFO,
|
||||||
size as i32,
|
0,
|
||||||
)
|
info.as_mut_ptr().cast(),
|
||||||
};
|
size as i32,
|
||||||
if read != size as i32 {
|
)
|
||||||
|
};
|
||||||
|
if read == size as i32 {
|
||||||
|
if unsafe { info.assume_init() }.pti_resident_size > maximum {
|
||||||
|
return Err(failed("Extraction worker resident memory exceeds budget"));
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
if child.try_wait().map_err(failed)?.is_some() {
|
if child.try_wait().map_err(failed)?.is_some() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
return Err(failed("Cannot inspect extraction memory usage"));
|
thread::sleep(Duration::from_millis(1));
|
||||||
}
|
}
|
||||||
if unsafe { info.assume_init() }.pti_resident_size > maximum {
|
Err(failed("Cannot inspect extraction memory usage"))
|
||||||
return Err(failed("Extraction worker resident memory exceeds budget"));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Includes pipe writes, response EOF and process exit in the same deadline.
|
/// Includes pipe writes, response EOF and process exit in the same deadline.
|
||||||
|
|||||||
@@ -30,15 +30,24 @@ fn actual_parent_and_worker_return_original_docx_passage_and_digest() {
|
|||||||
let input = document.finish().unwrap().into_inner();
|
let input = document.finish().unwrap().into_inner();
|
||||||
let request = RequestHeader::from_bytes(DocumentFormat::Docx, &input).unwrap();
|
let request = RequestHeader::from_bytes(DocumentFormat::Docx, &input).unwrap();
|
||||||
let expected_hash = request.input_sha256.clone();
|
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.
|
||||||
assert_eq!(response.input_sha256, expected_hash);
|
for _ in 0..10 {
|
||||||
let ExtractionOutcome::Extracted { passages } = response.outcome else {
|
let response = run(
|
||||||
panic!("Valid document rejected");
|
&config(),
|
||||||
};
|
request.clone(),
|
||||||
assert_eq!(passages.len(), 1);
|
input.clone(),
|
||||||
assert_eq!(passages[0].text, "Cancel with sixty days written notice.");
|
&AtomicBool::new(false),
|
||||||
assert_eq!(passages[0].source.kind, "paragraph");
|
)
|
||||||
assert_eq!(passages[0].source.number, 1);
|
.unwrap();
|
||||||
|
assert_eq!(response.input_sha256, expected_hash);
|
||||||
|
let ExtractionOutcome::Extracted { passages } = response.outcome else {
|
||||||
|
panic!("Valid document rejected");
|
||||||
|
};
|
||||||
|
assert_eq!(passages.len(), 1);
|
||||||
|
assert_eq!(passages[0].text, "Cancel with sixty days written notice.");
|
||||||
|
assert_eq!(passages[0].source.kind, "paragraph");
|
||||||
|
assert_eq!(passages[0].source.number, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -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
|
Windows retain their existing resource policies. An owned live-child test
|
||||||
verifies budget rejection; five process-owner tests and two actual parent/worker
|
verifies budget rejection; five process-owner tests and two actual parent/worker
|
||||||
roundtrip/rejection tests pass on the Mac.
|
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