review: a memory write wakes the Honcho worker
A write, an undo or an accepted write decided into the ledger waited out the worker's 30 s idle sleep. Mirror::journalled now says whether it added a line, and memoryd wakes the worker when it did. The Wake lock is a leaf, taken under the index lock as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
//! A memory write wakes the Honcho worker (M5c spec, section 4: a write reaches Honcho "at once";
|
||||
//! review of M5c-1, finding 3): with the worker asleep for its 30 s idle wait, the memory tool's
|
||||
//! conclusion and the owner's undo's deletion both reach the fake Honcho within 5 s.
|
||||
|
||||
#[path = "support/honcho.rs"]
|
||||
mod honcho;
|
||||
#[path = "support/tmp.rs"]
|
||||
mod tmp;
|
||||
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use bxcore::{
|
||||
CallId, DataClass, Envelope, MemoryAdmin, MemoryCall, MemoryCommand, Message, SessionId,
|
||||
ToolRequest, read_frame, write_frame,
|
||||
};
|
||||
use honcho::FakeHoncho;
|
||||
use memoryd::config::Config;
|
||||
use memoryd::honcho::worker::Tuning;
|
||||
use memoryd::serve;
|
||||
use tmp::TempDir;
|
||||
|
||||
fn ask(socket: &Path, msg: Message) -> Message {
|
||||
let mut stream = UnixStream::connect(socket).unwrap();
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_secs(10)))
|
||||
.unwrap();
|
||||
let env = Envelope {
|
||||
v: 1,
|
||||
id: 1,
|
||||
r#final: true,
|
||||
msg,
|
||||
};
|
||||
write_frame(&mut stream, &env).unwrap();
|
||||
read_frame(&mut stream).unwrap().msg
|
||||
}
|
||||
|
||||
/// How many requests of `method` to a path ending `suffix` the fake has had.
|
||||
fn count(fake: &FakeHoncho, method: &str, suffix: &str) -> usize {
|
||||
fake.requests()
|
||||
.iter()
|
||||
.filter(|r| {
|
||||
r.method == method && r.path.contains("/conclusions") && r.path.ends_with(suffix)
|
||||
})
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Waits up to 5 s for `done`; true if it came.
|
||||
fn within_5s(done: impl Fn() -> bool) -> bool {
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
while Instant::now() < deadline {
|
||||
if done() {
|
||||
return true;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
done()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_memory_write_and_an_undo_wake_the_worker() {
|
||||
// The worker `serve` starts waits this long when idle: far past every deadline here.
|
||||
assert!(Tuning::DEFAULT.idle >= Duration::from_secs(30));
|
||||
let fake = FakeHoncho::start();
|
||||
let dir = TempDir::new("wake-serve");
|
||||
let soul = dir.write("etc/soul.md", "You are Boxmaker.\n");
|
||||
let text = format!(
|
||||
"[paths]\nhome = \"{}\"\n[memory]\nsoul_starter = \"{}\"\n[honcho]\nurl = \"{}\"\nagent = \"boxmaker0\"\nowner = \"kyle\"\ntrusted = \"boxmaker0-trusted\"\nuntrusted = \"boxmaker0-untrusted\"\nfleet = \"fleet\"\n",
|
||||
dir.path().display(),
|
||||
soul.display(),
|
||||
fake.url
|
||||
);
|
||||
let config = Config::parse(&text).unwrap();
|
||||
let lines = Arc::new(Mutex::new(Vec::new()));
|
||||
let sink = Arc::clone(&lines);
|
||||
let log: serve::Log = Arc::new(move |l: &str| sink.lock().unwrap().push(l.to_string()));
|
||||
let running = serve::start(config.clone(), log).unwrap();
|
||||
std::thread::spawn(move || running.run());
|
||||
// Let the worker find nothing to send and fall asleep.
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
|
||||
let call = Message::MemoryCall(MemoryCall {
|
||||
request: ToolRequest {
|
||||
session: SessionId::new("mm-a").unwrap(),
|
||||
call: CallId(1),
|
||||
tool: "memory".to_string(),
|
||||
arguments: r#"{"action":"add","file":"user","text":"Prefers UTC."}"#.to_string(),
|
||||
},
|
||||
taint: DataClass::Private,
|
||||
untrusted: false,
|
||||
});
|
||||
assert!(matches!(
|
||||
ask(&config.broker_socket(), call),
|
||||
Message::ToolResponse(_)
|
||||
));
|
||||
assert!(
|
||||
within_5s(|| count(&fake, "POST", "/conclusions") == 1),
|
||||
"the conclusion waited: {:?} {:?}",
|
||||
fake.requests(),
|
||||
lines.lock().unwrap()
|
||||
);
|
||||
std::thread::sleep(Duration::from_millis(200));
|
||||
|
||||
let log = ask(
|
||||
&config.admin_socket(),
|
||||
Message::MemoryAdmin(MemoryAdmin {
|
||||
command: MemoryCommand::Log,
|
||||
arg: String::new(),
|
||||
}),
|
||||
);
|
||||
let Message::MemoryAdminReply(reply) = log else {
|
||||
panic!("{log:?}")
|
||||
};
|
||||
let id = reply.text.split(" ").next().unwrap().to_string();
|
||||
let undone = ask(
|
||||
&config.admin_socket(),
|
||||
Message::MemoryAdmin(MemoryAdmin {
|
||||
command: MemoryCommand::Undo,
|
||||
arg: id,
|
||||
}),
|
||||
);
|
||||
assert!(matches!(undone, Message::MemoryAdminReply(_)), "{undone:?}");
|
||||
assert!(
|
||||
within_5s(|| count(&fake, "DELETE", "") == 1),
|
||||
"the deletion waited: {:?} {:?}",
|
||||
fake.requests(),
|
||||
lines.lock().unwrap()
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user