fix(core): cron double-fire, materialize warnings, group quota fast path, SSE channel cap
Remediate the MEDIUM backend correctness/perf findings from the 2026-07-11 audit. B1 — a group cron template toggled disabled→enabled no longer re-fires every descendant's cron. Materialization now KEEPS a disabled cron template's copies (syncing `enabled` in place) instead of delete+recreate, preserving `last_fired_at`; the scheduler already skips disabled copies, so keeping them is inert. Queue/email arms still delete-on-disable (freeing the one-consumer slot). B2 — `rematerialize_stateful_templates` warnings are now logged at the app-create/delete and group-reparent chokepoints (were silently dropped); only the `Err` arm was handled before. B3 — the group KV/docs byte-quota check skips the O(rows) `SUM(octet_length(..))` scan when a cheap upper bound (`rows_after * max_value_bytes`) is already under the cap, so the full aggregate only runs near-cap. Every row is validated ≤ max_value_bytes, so the bound is sound (never lets an over-quota write through). B6 — the in-process SSE broadcaster caps live channels per map (`PICLOUD_REALTIME_MAX_CHANNELS`, default 100k); a subscribe that would open a NEW channel past the cap is refused with 503 + `Retry-After: 1` rather than growing the (app,topic)/(group,topic) maps unboundedly. Check+insert is atomic under the map mutex (no TOCTOU); an existing-channel subscribe is exempt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,10 @@
|
||||
//!
|
||||
//! Full-live like `rebuild_route_table`: called at the same chokepoints (apply,
|
||||
//! app create/delete, group reparent). A precise create/delete diff (not
|
||||
//! delete-all-then-recreate) preserves `last_fired_at` on unchanged cron copies.
|
||||
//! delete-all-then-recreate) preserves `last_fired_at` on unchanged cron copies;
|
||||
//! a DISABLED cron template keeps its copies (their `enabled` is synced off)
|
||||
//! rather than deleting them, so a later re-enable resumes from the preserved
|
||||
//! `last_fired_at` instead of firing from a fresh reference.
|
||||
//!
|
||||
//! Returns per-app WARNINGS (e.g. a queue whose consumer slot is already taken)
|
||||
//! — the caller surfaces them; materialization of the other pairs still
|
||||
@@ -86,7 +89,8 @@ pub async fn rematerialize_stateful_templates(pool: &PgPool) -> Result<Vec<Strin
|
||||
SELECT DISTINCT ac.effective_app_id, t.id AS template_id, t.kind, t.shared \
|
||||
FROM app_chain ac \
|
||||
JOIN triggers t ON t.group_id = ac.owner_group \
|
||||
WHERE t.group_id IS NOT NULL AND t.enabled = TRUE AND t.kind = ANY($1)",
|
||||
WHERE t.group_id IS NOT NULL AND t.kind = ANY($1) \
|
||||
AND (t.enabled = TRUE OR t.kind = 'cron')",
|
||||
)
|
||||
.bind(&kinds)
|
||||
.fetch_all(&mut *tx)
|
||||
@@ -135,6 +139,21 @@ pub async fn rematerialize_stateful_templates(pool: &PgPool) -> Result<Vec<Strin
|
||||
}
|
||||
}
|
||||
|
||||
// Sync each CRON copy's `enabled` to its template's, in place. A cron
|
||||
// template's copies are kept across a disable (the `should` query includes
|
||||
// disabled cron templates), so a disable→re-enable toggles this flag rather
|
||||
// than delete+recreate — preserving each copy's per-app `last_fired_at` (and
|
||||
// its `created_at` reference). Without this, a re-enabled cron would reset to
|
||||
// a fresh reference and lose/shift its schedule state. Cron-only: queue/email
|
||||
// keep delete-on-disable so the queue one-consumer slot is freed on disable.
|
||||
sqlx::query(
|
||||
"UPDATE triggers c SET enabled = t.enabled \
|
||||
FROM triggers t \
|
||||
WHERE c.materialized_from = t.id AND t.kind = 'cron' AND c.enabled <> t.enabled",
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(warnings)
|
||||
}
|
||||
@@ -201,7 +220,11 @@ async fn materialize_one(
|
||||
|
||||
match kind {
|
||||
"cron" => {
|
||||
// A fresh copy starts with last_fired_at NULL → fires on next due.
|
||||
// A first-ever copy starts with last_fired_at NULL → its reference
|
||||
// is created_at (now), so it fires at the next scheduled slot, not
|
||||
// immediately. Re-enabling a previously-materialized template does
|
||||
// NOT hit this path — that copy is kept and its `enabled` synced, so
|
||||
// last_fired_at survives (see the enabled-sync UPDATE above).
|
||||
sqlx::query(
|
||||
"INSERT INTO cron_trigger_details (trigger_id, schedule, timezone) \
|
||||
SELECT $1, schedule, timezone FROM cron_trigger_details WHERE trigger_id = $2",
|
||||
|
||||
Reference in New Issue
Block a user