reminder: atomic delivery transaction + per-tick batch cap

This commit is contained in:
damocles 2026-05-17 02:51:05 +02:00
parent e45d161cb8
commit b86c0a2217
2 changed files with 67 additions and 33 deletions

View file

@ -86,6 +86,12 @@ enum Cmd {
Deny { id: i64 },
}
/// Per-tick cap on reminders the scheduler delivers. Anything over this
/// stays due in the table and gets picked up on the next 5s tick — keeps
/// a 10k-deep backlog from flooding the broker (or hogging its mutex) in
/// one shot.
const REMINDER_BATCH_LIMIT: u64 = 100;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
@ -166,36 +172,30 @@ async fn main() -> Result<()> {
// operator-initiated transient state.
crash_watch::spawn(coord.clone());
// Reminder scheduler: checks for due reminders every 5 seconds,
// delivers them as inbox messages from "reminder".
// delivers them atomically (insert inbox + mark sent in one
// sqlite transaction so a transient failure on the second step
// can never produce a duplicate next tick). Per-cycle batch
// limit caps the burst — leftover reminders stay due and get
// picked up on the next tick instead of monopolising the broker
// mutex.
let reminder_coord = coord.clone();
tokio::spawn(async move {
use hive_sh4re::Message;
loop {
// Query all due reminders in a single DB call
match reminder_coord.broker.get_all_due_reminders() {
match reminder_coord
.broker
.get_due_reminders(REMINDER_BATCH_LIMIT)
{
Ok(reminders) => {
for (agent, id, message, _file_path) in reminders {
// Deliver as inbox message from "reminder"
if let Err(e) = reminder_coord.broker.send(&Message {
from: "reminder".to_owned(),
to: agent.clone(),
body: message.clone(),
}) {
if let Err(e) =
reminder_coord.broker.deliver_reminder(id, &agent, &message)
{
tracing::warn!(
reminder_id = id,
%agent,
error = ?e,
"failed to deliver reminder"
);
continue;
}
// Mark as sent
if let Err(e) = reminder_coord.broker.mark_reminder_sent(id) {
tracing::warn!(
reminder_id = id,
error = ?e,
"failed to mark reminder sent"
);
}
}
}