sausage core part 1: a process per call, routing, tree, stop and kill, locks, journal, boot

Checkpoint. Removes the old host. A quality rework follows (modules,
journal writer thread, hash cache, finer tests).
This commit is contained in:
2026-10-23 00:12:02 +01:00
parent 0a479b5800
commit 4882edf243
23 changed files with 1650 additions and 5377 deletions
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "probe"
version = "0.1.0"
edition = "2024"
[workspace]
[dependencies]
serde_json = "1"
+47
View File
@@ -0,0 +1,47 @@
//! A raw JSON-RPC guest for host integration tests.
use std::io::{self,BufRead,Write};
use serde_json::{Value,json};
fn send(v:Value) { let mut out=io::stdout().lock();writeln!(out,"\n{v}").unwrap();out.flush().unwrap(); }
fn main() {
let mut lines=io::stdin().lock().lines();
let request:Value=serde_json::from_str(&lines.next().unwrap().unwrap()).unwrap();
let id=request.get("id").cloned();let p=&request["params"];
let result=match request["method"].as_str().unwrap() {
"echo"|"rpc.discover"=>p.clone(),
"env"=>json!({"pid":std::env::var("SAUSAGE_PID").unwrap(),"plugin":std::env::var("SAUSAGE_PLUGIN").unwrap(),"parent":std::env::var("SAUSAGE_PARENT").unwrap()}),
"multicall"=>{
send(json!({"jsonrpc":"2.0","id":1,"method":"probe.echo","params":{"n":1}}));
send(json!({"jsonrpc":"2.0","id":"1","method":"probe.echo","params":{"n":2}}));
let mut seen=Vec::new();while seen.len()<2 {let r:Value=serde_json::from_str(&lines.next().unwrap().unwrap()).unwrap();if r["id"]==1 {assert_eq!(r["result"]["n"],1);} else {assert_eq!(r["id"],"1");assert_eq!(r["result"]["n"],2);}seen.push(r["id"].clone());}json!({"count":seen.len()})
},
"notification"=>{
send(json!({"jsonrpc":"2.0","method":"probe.write","params":p}));
for _ in 0..200 {if std::fs::exists(p["path"].as_str().unwrap()).unwrap() {break;}std::thread::sleep(std::time::Duration::from_millis(10));}json!({})
},
"unknownid"=>{send(json!({"jsonrpc":"2.0","id":"late","result":7}));return;},
"request_id"=>request["id"].clone(),
"wait_eof"=>{for _ in lines {} std::fs::write("/state/stopped","yes").unwrap(); return;},
"busy"=>loop {std::hint::spin_loop();},
"sleep"=>{std::thread::sleep(std::time::Duration::from_millis(p["ms"].as_u64().unwrap_or(100)));json!({})},
"exit"=>{eprintln!("last line");return;},
"junk"=>{io::stdout().write_all(&[255,10]).unwrap();println!("junk\n{{}}\n[]");eprintln!("stderr line");json!({})},
"notify"=>{send(json!({"jsonrpc":"2.0","method":"core.notify","params":{"name":"progress","params":{"n":1}}}));std::thread::sleep(std::time::Duration::from_millis(30));json!({})},
"serve"=>{send(json!({"jsonrpc":"2.0","id":id,"result":std::env::var("SAUSAGE_PID").unwrap()}));for _ in lines {} return;},
"network"=>{
use std::io::Read;
let mut socket=std::net::TcpStream::connect(p["tcp"].as_str().unwrap()).unwrap();
let mut text=String::new();socket.read_to_string(&mut text).unwrap();
let udp=std::net::UdpSocket::bind("127.0.0.1:0").unwrap();udp.send_to(b"udp",p["udp"].as_str().unwrap()).unwrap();json!(text)
},
"read"=>json!(std::fs::read_to_string(p["path"].as_str().unwrap()).unwrap()),
"write"=>{std::fs::write(p["path"].as_str().unwrap(),p["text"].as_str().unwrap()).unwrap();json!({})},
"call"|"lock"=>{
let method=if request["method"]=="lock" {"core.lock"} else {p["method"].as_str().unwrap()};
let params=if request["method"]=="lock" {p.clone()} else {p["params"].clone()};
send(json!({"jsonrpc":"2.0","id":{"nested":7},"method":method,"params":params}));
loop {let response:Value=serde_json::from_str(&lines.next().unwrap().unwrap()).unwrap();if response["id"]==json!({"nested":7}) {break response["result"].clone();}}
},
_=>{send(json!({"jsonrpc":"2.0","id":id,"error":{"code":-32601,"message":"method not found"}}));return;}
};
if let Some(id)=id {send(json!({"jsonrpc":"2.0","id":id,"result":result}));}
}