MCPcopy Create free account
hub / github.com/ScriptedAlchemy/tracedecay / round_trip_with_timeout

Function round_trip_with_timeout

src/extraction_worker.rs:407–460  ·  view source on GitHub ↗

Sends one extract request and reads the response, killing the worker process and returning [`RoundTripOutcome::Timeout`] if the read takes longer than `timeout`. The worker's child handle is `kill()`ed in place to unblock the read; the caller is expected to `spawn_worker` a fresh subprocess after either a timeout or a crash.

(
    worker: &mut WorkerHandle,
    req: &ExtractRequest,
    timeout: Duration,
)

Source from the content-addressed store, hash-verified

405/// to unblock the read; the caller is expected to `spawn_worker` a fresh
406/// subprocess after either a timeout or a crash.
407fn round_trip_with_timeout(
408 worker: &mut WorkerHandle,
409 req: &ExtractRequest,
410 timeout: Duration,
411) -> RoundTripOutcome {
412 let Some(stdin) = worker.stdin.as_mut() else {
413 return RoundTripOutcome::Err(io::Error::other("worker stdin already closed"));
414 };
415 if let Err(e) = write_message(stdin, req).and_then(|()| stdin.flush()) {
416 return RoundTripOutcome::Err(e);
417 }
418
419 // Split-borrow `stdout` (owned by the read thread) and `child` (owned by
420 // the watchdog thread). Rust allows this because they're disjoint fields
421 // of `*worker`.
422 let WorkerHandle {
423 ref mut stdout,
424 ref mut child,
425 ..
426 } = *worker;
427
428 let timed_out = AtomicBool::new(false);
429 let (cancel_tx, cancel_rx) = std::sync::mpsc::channel::<()>();
430
431 let read_result: io::Result<ExtractResponse> = std::thread::scope(|s| {
432 // `move` the Receiver into the watchdog so it owns it (Receiver
433 // is `Send` but not `Sync`). `&timed_out` and `&mut *child` are
434 // borrowed from the outer scope under `'scope`.
435 let timed_out = &timed_out;
436 s.spawn(move || {
437 // Watchdog: if the read doesn't finish in `timeout`, kill the
438 // child so the read returns EOF and unblocks. The kill failing
439 // (child already exited) is fine — we just won't have a clean
440 // way to distinguish "crashed at exactly the wrong moment" from
441 // "timed out", and that's OK; both cases get respawned.
442 if cancel_rx.recv_timeout(timeout).is_err() {
443 timed_out.store(true, Ordering::SeqCst);
444 let _ = child.kill();
445 }
446 });
447 let r = read_message(stdout);
448 let _ = cancel_tx.send(());
449 r
450 });
451
452 if timed_out.load(Ordering::SeqCst) {
453 RoundTripOutcome::Timeout
454 } else {
455 match read_result {
456 Ok(resp) => RoundTripOutcome::Ok(resp),
457 Err(e) => RoundTripOutcome::Err(e),
458 }
459 }
460}
461
462fn spawn_worker(self_path: &Path, token: &[u8; TOKEN_LEN]) -> io::Result<WorkerHandle> {
463 let token_hex = hex::encode(token);

Callers 1

worker_threadFunction · 0.85

Calls 7

storeMethod · 0.80
sendMethod · 0.80
write_messageFunction · 0.70
read_messageFunction · 0.70
flushMethod · 0.45
spawnMethod · 0.45
loadMethod · 0.45

Tested by

no test coverage detected