core: review fixes, init builds the agent, console without a terminal
Activation path (review T6R): a broken plugin or a stray file in /plugins blocks no activation; candidates replace their own entries; discovery has a 10 s deadline and its process is stopped after the answer; an unchanged hash keeps plugin.prev.wasm; activations run in their own task; the watcher is not recursive; no duplicate services; rpc.retired waits for live processes of its version; reads and hashes on blocking threads. Guest I/O and per-call paths: a stdin write error is EOF, stdout and stderr are drained before the status; stderr is lossy UTF-8 and goes to the journal from its own reader; compiled components are freed; shutdown starts no new processes; lock queues drop stopping processes; compile before the admission gate; CommandPre per component; core.unlock in order; null errors are successes; the journal thread survives SQLite errors. sausage init builds the agent with cargo.build; sausage without a command creates ./world. The console reads plain lines without a terminal. PROTOCOL.md and DECISIONS.md describe the decided rules. The flake tests the whole workspace and checks the packaged cargo.build.
This commit is contained in:
@@ -41,13 +41,40 @@ fn main() {
|
||||
"serve" | "hold" => {
|
||||
send(json!({"jsonrpc":"2.0","id":request["id"],"result":{"version":version}}));
|
||||
if request["method"] == "serve" {
|
||||
let exclusive = format!("/run/{plugin}-service");
|
||||
if std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&exclusive)
|
||||
.is_err()
|
||||
{
|
||||
std::fs::write(format!("/state/{plugin}/duplicate-service"), "duplicate")
|
||||
.unwrap();
|
||||
}
|
||||
std::fs::write(format!("/state/{plugin}/service"), version).unwrap();
|
||||
}
|
||||
for _ in lines {}
|
||||
if request["method"] == "serve" {
|
||||
let _ = std::fs::remove_file(format!("/run/{plugin}-service"));
|
||||
}
|
||||
std::fs::write(format!("/state/{plugin}/ended-{version}"), "ended").unwrap();
|
||||
return;
|
||||
}
|
||||
_ => json!({"version":version}),
|
||||
};
|
||||
send(json!({"jsonrpc":"2.0","id":request["id"],"result":result}));
|
||||
if request["method"] == "rpc.discover" && result["x-test-linger"] == true {
|
||||
if result["x-test-stubborn"] == true {
|
||||
while !std::path::Path::new(&format!("/state/{plugin}/discovery-exit")).exists() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
}
|
||||
} else {
|
||||
for _ in lines {}
|
||||
}
|
||||
std::fs::write(
|
||||
format!("/state/{plugin}/discovery-ended-{version}"),
|
||||
"ended",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,9 +292,59 @@ fn locked_write(lines: &mut Lines) -> Value {
|
||||
assert_eq!(std::fs::read_to_string("/state/loop").unwrap(), "new");
|
||||
json!({})
|
||||
}
|
||||
fn unlock_answer(request: &Value, lines: &mut Lines) {
|
||||
lock(&json!({"path":"ordered-unlock"}), lines);
|
||||
let unlock = json!({"jsonrpc":"2.0","method":"core.unlock","params":{"path":"ordered-unlock"}});
|
||||
let answer =
|
||||
json!({"jsonrpc":"2.0","id":request["id"],"result":std::env::var("SAUSAGE_PID").unwrap()});
|
||||
let mut stdout = io::stdout().lock();
|
||||
writeln!(stdout, "{unlock}\n{answer}").unwrap();
|
||||
stdout.flush().unwrap();
|
||||
drop(stdout);
|
||||
for _ in lines {}
|
||||
}
|
||||
fn notification_child(lines: &mut Lines) -> Value {
|
||||
send(json!({"jsonrpc":"2.0","method":"probe.orphan_notify","params":{}}));
|
||||
let pid = loop {
|
||||
if let Ok(pid) = std::fs::read_to_string("/state/orphan") {
|
||||
break pid;
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
};
|
||||
send(json!({"jsonrpc":"2.0","id":7,"method":"core.stop","params":{"pid":pid}}));
|
||||
let mut count = 0;
|
||||
loop {
|
||||
let response = receive(lines);
|
||||
if response["method"] == "core.notify" {
|
||||
count += 1;
|
||||
}
|
||||
if response["id"] == 7 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
json!(count)
|
||||
}
|
||||
fn dispatch(request: &Value, lines: &mut Lines) -> Option<Value> {
|
||||
let params = &request["params"];
|
||||
let result = match request["method"].as_str().unwrap() {
|
||||
"invalid_stderr" => {
|
||||
io::stderr().write_all(&[255, 10]).unwrap();
|
||||
eprintln!("after invalid UTF-8");
|
||||
json!(std::env::var("SAUSAGE_PID").unwrap())
|
||||
}
|
||||
"unlock_answer" => {
|
||||
unlock_answer(request, lines);
|
||||
return None;
|
||||
}
|
||||
"null_error" => {
|
||||
send(json!({"jsonrpc":"2.0","id":request["id"],"error":null,"result":42}));
|
||||
return None;
|
||||
}
|
||||
"notification_child" => notification_child(lines),
|
||||
"orphan_notify" => {
|
||||
std::fs::write("/state/orphan", std::env::var("SAUSAGE_PID").unwrap()).unwrap();
|
||||
notify()
|
||||
}
|
||||
"self_serve" => {
|
||||
std::fs::write("/self/endpoint", "endpoint").unwrap();
|
||||
serve(request, lines);
|
||||
@@ -359,6 +409,21 @@ fn dispatch(request: &Value, lines: &mut Lines) -> Option<Value> {
|
||||
Some(result)
|
||||
}
|
||||
fn main() {
|
||||
if let Ok(mode) = std::fs::read_to_string("/state/early") {
|
||||
if mode == "flood" {
|
||||
for index in 0..5000 {
|
||||
eprintln!("flood {index} {}", "x".repeat(128));
|
||||
}
|
||||
} else {
|
||||
eprintln!("last early log");
|
||||
if mode == "answer" {
|
||||
send(
|
||||
json!({"jsonrpc":"2.0","id":std::env::var("SAUSAGE_PID").unwrap(),"result":"early"}),
|
||||
);
|
||||
}
|
||||
std::process::exit(23);
|
||||
}
|
||||
}
|
||||
let mut lines = io::BufReader::new(io::stdin()).lines();
|
||||
let request = receive(&mut lines);
|
||||
if let Some(result) = dispatch(&request, &mut lines)
|
||||
|
||||
Reference in New Issue
Block a user