Spike: fs/upgrade/bench examples, agent resumes interrupted turns, world lock, short trap reasons

This commit is contained in:
2026-10-15 05:00:33 +01:00
parent 83bbbb33a8
commit 0be152dda9
13 changed files with 293 additions and 8 deletions
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "bench"
version = "0.1.0"
edition = "2024"
[dependencies]
serde_json = "1"
+45
View File
@@ -0,0 +1,45 @@
//! {"type":"bench","target":"echo","n":1000}: ping-pong with the target, one
//! message at a time, and report the round-trip times.
use serde_json::{Value, json};
use std::io::{BufRead, Write};
use std::time::Instant;
fn main() {
let mut out = std::io::stdout().lock();
let mut lines = std::io::stdin().lock().lines();
while let Some(Ok(line)) = lines.next() {
let Ok(msg) = serde_json::from_str::<Value>(&line) else { continue };
match msg["body"]["type"].as_str() {
Some("shutdown") => break,
Some("bench") => {
let target = msg["body"]["target"].as_str().unwrap_or("echo").to_string();
let n = msg["body"]["n"].as_u64().unwrap_or(1000);
let mut times = Vec::new();
let started = Instant::now();
for i in 0..n {
let t = Instant::now();
writeln!(out, "{}", json!({"to": target, "id": i, "body": {"type": "ping", "i": i}})).unwrap();
out.flush().unwrap();
// Wait for the reply.
loop {
let Some(Ok(l)) = lines.next() else { return };
let Ok(r) = serde_json::from_str::<Value>(&l) else { continue };
if r["reply_to"].as_str().is_some_and(|s| s.ends_with(&format!("/{i}"))) {
break;
}
}
times.push(t.elapsed().as_micros() as u64);
}
let total = started.elapsed().as_secs_f64();
times.sort();
let p = |q: f64| times[((times.len() as f64 - 1.0) * q) as usize];
let reply = json!({"type": "bench_result", "n": n, "total_s": total,
"msgs_per_s": (n as f64 / total).round(),
"p50_us": p(0.5), "p90_us": p(0.9), "p99_us": p(0.99), "max_us": p(1.0)});
writeln!(out, "{}", json!({"to": msg["from"], "reply_to": msg["id"], "body": reply})).unwrap();
out.flush().unwrap();
}
_ => {}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "fstest"
version = "0.1.0"
edition = "2024"
[dependencies]
serde_json = "1"
+47
View File
@@ -0,0 +1,47 @@
//! Filesystem probe for the spike. Keeps files open across messages.
use serde_json::{Value, json};
use std::collections::HashMap;
use std::io::{BufRead, Write};
fn main() {
let mut open: HashMap<String, std::fs::File> = HashMap::new();
let mut out = std::io::stdout().lock();
for line in std::io::stdin().lock().lines() {
let Ok(line) = line else { break };
let Ok(msg) = serde_json::from_str::<Value>(&line) else { continue };
let b = &msg["body"];
let path = b["path"].as_str().unwrap_or_default().to_string();
let r = |res: std::io::Result<Value>| match res {
Ok(v) => v,
Err(e) => json!({"type": "error", "error": e.to_string()}),
};
let reply = match b["type"].as_str() {
Some("shutdown") => break,
Some("write") => r(std::fs::write(&path, b["text"].as_str().unwrap_or_default()).map(|_| json!({"type": "ok"}))),
Some("open") => r((|| {
let mut f = std::fs::File::create(&path)?;
f.write_all(b["text"].as_str().unwrap_or_default().as_bytes())?;
open.insert(path.clone(), f);
Ok(json!({"type": "ok", "open": true}))
})()),
Some("append") => r((|| {
let mut f = std::fs::OpenOptions::new().append(true).open(&path)?;
f.write_all(b["text"].as_str().unwrap_or_default().as_bytes())?;
open.insert(path.clone(), f);
Ok(json!({"type": "ok", "open": true}))
})()),
Some("close") => json!({"type": "ok", "closed": open.remove(&path).is_some()}),
Some("read") => r(std::fs::read_to_string(&path).map(|t| json!({"type": "content", "text": t}))),
Some("list") => r(std::fs::read_dir(&path).map(|rd| {
let mut names: Vec<String> = rd.filter_map(|e| e.ok()).map(|e| e.file_name().to_string_lossy().to_string()).collect();
names.sort();
json!({"type": "entries", "names": names})
})),
Some("remove") => r(std::fs::remove_file(&path).map(|_| json!({"type": "ok"}))),
Some("crash") => panic!("asked to crash"),
_ => json!({"type": "error", "error": "unknown message"}),
};
writeln!(out, "{}", json!({"to": msg["from"], "reply_to": msg["id"], "body": reply})).unwrap();
out.flush().unwrap();
}
}
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "notes"
version = "0.1.0"
edition = "2024"
[dependencies]
serde_json = "1"
+39
View File
@@ -0,0 +1,39 @@
//! Keeps a list of notes in /data/notes.json and publishes them as
//! /public/notes.txt. Version 2: adds `count` and no longer crashes.
use serde_json::{Value, json};
use std::io::{BufRead, Write};
const DATA: &str = "/data/notes.json";
fn save(notes: &[String]) {
std::fs::write(DATA, serde_json::to_string(notes).unwrap()).unwrap();
std::fs::write("/public/notes.txt", notes.join("\n") + "\n").unwrap();
}
fn main() {
let mut notes: Vec<String> = std::fs::read_to_string(DATA)
.ok()
.and_then(|s| serde_json::from_str(&s).ok())
.unwrap_or_default();
let mut out = std::io::stdout().lock();
for line in std::io::stdin().lock().lines() {
let Ok(line) = line else { break };
let Ok(msg) = serde_json::from_str::<Value>(&line) else { continue };
let body = &msg["body"];
let reply = match body["type"].as_str() {
Some("shutdown") => break,
Some("add") => {
notes.push(body["text"].as_str().unwrap_or_default().to_string());
save(&notes);
json!({"type": "added", "count": notes.len()})
}
Some("list") => json!({"type": "notes", "items": notes}),
Some("count") => json!({"type": "count", "count": notes.len(), "version": 2}),
// Version 2 fixes the crash.
Some("crash") => json!({"type": "not_crashing", "version": 2}),
_ => json!({"type": "error", "reason": "unknown message"}),
};
writeln!(out, "{}", json!({"to": msg["from"], "reply_to": msg["id"], "body": reply})).unwrap();
out.flush().unwrap();
}
}
+8
View File
@@ -0,0 +1,8 @@
{"send": {"type": "add", "text": "test note"}}
{"expect": {"type": "added"}}
{"send": {"type": "list"}}
{"expect": {"type": "notes"}}
{"send": {"type": "count"}}
{"expect": {"type": "count", "version": 2}}
{"send": {"type": "crash"}}
{"expect": {"type": "not_crashing"}}
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "ticker"
version = "0.1.0"
edition = "2024"
[dependencies]
serde_json = "1"
+16
View File
@@ -0,0 +1,16 @@
//! Sends {"type":"add","text":"tick N"} to `notes` every 50 ms, N = 1..=200.
//! Does not read stdin, so the broker kills it at shutdown; that is fine.
use std::io::Write;
fn main() {
let mut out = std::io::stdout().lock();
for n in 1..=200 {
let msg = serde_json::json!({"to": "notes", "body": {"type": "add", "text": format!("tick {n}")}});
writeln!(out, "{msg}").unwrap();
out.flush().unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
}
// Stay alive without doing anything.
loop {
std::thread::sleep(std::time::Duration::from_secs(3600));
}
}