use std::{io, panic, process::ExitCode};
use monty_proto::{
FrameError, FrameReader, pb,
worker::{Child, EventSink, HandleOutcome, fatal_error_event, protocol_violation},
write_frame,
};
const EX_PROTOCOL: u8 = 76;
pub(crate) fn run() -> ExitCode {
install_panic_hook();
let mut reader = FrameReader::new(io::stdin().lock());
let mut child = Child::default();
let mut sink = StdoutSink;
loop {
match reader.read::<pb::ParentRequest>() {
Ok(Some(request)) => {
let outcome = child.handle(request, &mut sink);
apply_memory_limit(&child);
match outcome {
Ok(HandleOutcome::Continue) => {}
Ok(HandleOutcome::Shutdown) => return ExitCode::SUCCESS,
Ok(HandleOutcome::Fatal) => return ExitCode::from(4),
Err(FrameError::FrameTooLarge { len, max }) => {
fatal(
&child,
&mut sink,
&format!("response frame of {len} bytes exceeds maximum of {max} bytes"),
);
return ExitCode::from(EX_PROTOCOL);
}
Err(_) => return ExitCode::from(3),
}
}
Ok(None) => return ExitCode::SUCCESS,
Err(FrameError::Decode(err)) => {
if sink
.send(&protocol_violation(&format!("malformed request: {err}")))
.is_err()
{
return ExitCode::from(3);
}
}
Err(err) => {
fatal(&child, &mut sink, &format!("malformed request frame: {err}"));
return ExitCode::from(EX_PROTOCOL);
}
}
}
}
fn apply_memory_limit(child: &Child) {
let budget = child.session_budget();
monty_alloc::set_limit(budget.max_memory, budget.type_check)
.expect("monty-runtime must install LimitedAllocator globally");
}
struct StdoutSink;
impl EventSink for StdoutSink {
fn send(&mut self, event: &pb::ChildEvent) -> Result<(), FrameError> {
write_frame(&mut io::stdout(), event)
}
}
fn fatal(child: &Child, sink: &mut impl EventSink, message: &str) {
eprintln!("monty subprocess fatal error: {message}");
let _ = sink.send(&child.fatal_event(message));
}
fn install_panic_hook() {
let default_hook = panic::take_hook();
panic::set_hook(Box::new(move |info| {
let _ = write_frame(
&mut io::stdout(),
&fatal_error_event(&format!("child panicked: {info}")),
);
default_hook(info);
}));
}