M3b plan: follow-up tasks 14 to 17 for the review's lower findings
14 moves the pipe handling out of container.rs (a pure move, replayed on its own); 15 starts threads with Builder and bounds output collection with a 2 s grace period; 16 escapes container errors in the log and fixes two texts; 17 fixes toolkit's thread start, casts and the egress-proxy form. Each checked against a reference, which is not kept. Tips T24 to T26 from this run. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
//! After a container ends, its output is collected within a grace period, never waited on for
|
||||
//! ever: something outside the container that still holds a pipe must not hold `brokerd` (M3b
|
||||
//! review finding 4). Against a fake `podman` whose shell leaves a background `sleep` holding the
|
||||
//! pipes, which real Podman does not do. Do not edit.
|
||||
|
||||
#[path = "support/build.rs"]
|
||||
mod build;
|
||||
#[path = "support/fake_podman.rs"]
|
||||
mod fake_podman;
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use brokerd::container::{OUTPUT_OPEN, Podman, TIMED_OUT};
|
||||
use brokerd::pipes::GRACE;
|
||||
use brokerd::policy::{Outcome, SessionState, decide};
|
||||
use brokerd::runner::run;
|
||||
use build::{grant, now, read, set};
|
||||
use fake_podman::{Fake, Lines, serial};
|
||||
use proto::{Mode, ToolResponse};
|
||||
|
||||
fn call(fake: &Fake, extra: &str, log: &Lines) -> (ToolResponse, Duration) {
|
||||
let podman = Podman::new(fake.runner(extra), fake.dir.join("egress"), log.sink());
|
||||
let grants = set(vec![grant("notes", "read_file", Mode::Auto).paths(&["/n"])]);
|
||||
let decision = match decide(read("/n/a"), &grants, SessionState::default(), now()) {
|
||||
Outcome::Allowed(d) => d,
|
||||
other => panic!("not allowed: {other:?}"),
|
||||
};
|
||||
let started = Instant::now();
|
||||
let got = run(decision, &podman);
|
||||
(got, started.elapsed())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_grace_period_is_two_seconds() {
|
||||
assert_eq!(GRACE, Duration::from_secs(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_held_open_after_the_container_ended_is_abandoned_after_the_grace_period() {
|
||||
let _s = serial();
|
||||
// The shell exits at once; the background sleep keeps standard output and error open.
|
||||
let fake = Fake::new("grace-open", "cat > /dev/null; printf ok; sleep 6 & exit 0");
|
||||
let log = Lines::default();
|
||||
let (got, took) = call(&fake, "", &log);
|
||||
assert_eq!(
|
||||
got,
|
||||
ToolResponse::Failed {
|
||||
message: OUTPUT_OPEN.to_string()
|
||||
}
|
||||
);
|
||||
assert!(took >= GRACE, "{took:?}");
|
||||
assert!(took < GRACE + Duration::from_secs(2), "{took:?}");
|
||||
assert!(log.all().contains("abandoned"), "{}", log.all());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tool_past_its_limit_is_answered_within_the_grace_period_even_if_its_pipes_stay_open() {
|
||||
let _s = serial();
|
||||
// No `exec`: killing the shell leaves the sleep holding the pipes.
|
||||
let fake = Fake::new("grace-slow", "cat > /dev/null; sleep 6");
|
||||
let log = Lines::default();
|
||||
let (got, took) = call(&fake, "read_file_ms = 300", &log);
|
||||
assert_eq!(
|
||||
got,
|
||||
ToolResponse::Failed {
|
||||
message: TIMED_OUT.to_string()
|
||||
}
|
||||
);
|
||||
assert!(
|
||||
took < Duration::from_millis(300) + GRACE + Duration::from_secs(2),
|
||||
"{took:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_tool_that_ends_normally_is_not_slowed_by_the_grace_period() {
|
||||
let _s = serial();
|
||||
let fake = Fake::new("grace-ok", "cat > /dev/null; printf done; exit 0");
|
||||
let log = Lines::default();
|
||||
let (got, took) = call(&fake, "", &log);
|
||||
assert!(
|
||||
matches!(&got, ToolResponse::Result { content, .. } if content == "done"),
|
||||
"{got:?}"
|
||||
);
|
||||
assert!(took < Duration::from_secs(1), "{took:?}");
|
||||
}
|
||||
Reference in New Issue
Block a user