block/buzz · error

count, entry too old

Error message

count, entry too old

What it means

This is `.expect("count, entry too old")` on `count_events_routed` after the fence entry was force-opened with an Instant 10s in the past, making it older than the 5s budget; routing must fail closed to the writer. The expect panics if that writer-served count itself returns Err. It exists to prove the over-budget fallback path still executes a valid query.

Source

Thrown at crates/buzz-db/src/runtime/tests.rs:1649

    db.set_replica_read_max_age_for_tests(Some(std::time::Duration::from_secs(5)));
    let n = db
        .count_events_routed("test_count", &q)
        .await
        .expect("count, gate on");
    assert_eq!(n, 1, "budget set must count on the replica (bounded)");

    // Entry older than the budget ⇒ bounded fails ⇒ writer. Covered
    // would still hold here (upper <= wall) — proving count never
    // consults it.
    db.fence().close();
    db.fence().force_open_for_tests_at(
        chrono::Utc::now(),
        std::time::Instant::now() - std::time::Duration::from_secs(10),
    );
    let n = db
        .count_events_routed("test_count", &q)
        .await
        .expect("count, entry too old");
    assert_eq!(
        n, 2,
        "an over-budget entry must fail the count closed to the writer, \
             even when the covered arm would admit the shape"
    );

    drop_scratch_db(&admin, replica, &rname).await;
    drop_scratch_db(&admin, writer, &wname).await;
}

/// Routed relay-membership check: budget unset ⇒ writer; budget set +
/// fresh proved entry ⇒ replica (bounded arm); over-budget entry ⇒
/// writer. Divergent membership rows prove which pool answered.
#[tokio::test]
#[ignore = "requires Postgres"]
async fn is_relay_member_is_bounded_routed_and_fails_closed() {
    let admin = PgPool::connect(&admin_url().await)
        .await

View on GitHub (pinned to dad5a33865)

Solutions

  1. Check the writer pool is still alive after the earlier arms; increase idle timeout or acquire timeout on the pool.
  2. Verify the routing code fails closed (returns writer result) rather than Err for over-budget fence entries.
  3. Print the inner sqlx error to distinguish a routing bug from an infra failure.
  4. Ensure `force_open_for_tests_at` was called with a past Instant as in the test, not the future.
  5. Re-create scratch DBs fresh; leftover state from prior runs can corrupt seed assumptions.

Example fix

// before
let n = db.count_events_routed("test_count", &q).await.expect("count, entry too old");
// after
let n = db.count_events_routed("test_count", &q).await
    .unwrap_or_else(|e| panic!("count (entry too old / fail-closed writer) failed: {e}"));
Defensive patterns

Strategy: fallback

Validate before calling

// Ensure fence state matches the intended over-budget scenario
db.fence().close();
db.fence().force_open_for_tests_at(chrono::Utc::now(),
    std::time::Instant::now() - std::time::Duration::from_secs(10));

Type guard

fn entry_is_stale(opened_at: std::time::Instant, budget: std::time::Duration) -> bool {
    opened_at.elapsed() > budget
}

Try / catch

let n = db.count_events_routed("test_count", &q).await
    .unwrap_or_else(|e| panic!("fail-closed count failed: {e}"));

Prevention

When it happens

Trigger: Calling `count_events_routed` after `fence().close()` + `force_open_for_tests_at(now, now-10s)` when the writer pool errors (connection lost, schema missing), or when the fence/ring state manipulation itself leaves routing in an error state instead of failing closed.

Common situations: Writer pool idle-timed out during the earlier replica arms of the test; scratch writer DB dropped; a routing-code regression where over-budget entries produce Err instead of falling back to the writer (which is exactly what this test is designed to catch).

Related errors


AI-assisted analysis of block/buzz@dad5a33865 (2026-08-30). Data as JSON: /api/errors/1a62622cdbb16b69. Report an issue: GitHub.