LemmyNet/lemmy · error

err getting activity: {e:?}

Error message

err getting activity: {e:?}

What it means

get_activity_cached loads a SentActivity by id through a moka cache (try_get_with); when the underlying read fails, the LemmyError (wrapped in Arc) is converted into this anyhow error. Holes in activity serial ids are normal in PostgreSQL, so a missing id surfaces as this error too.

Source

Thrown at crates/apub/send/src/util.rs:193

}

type CachedActivityInfo = Option<Arc<SentActivity>>;
/// activities are immutable so cache does not need to have TTL
/// May return None if the corresponding id does not exist or is a received activity.
/// Holes in serials are expected behaviour in postgresql
/// todo: cache size should probably be configurable / dependent on desired memory usage
pub(crate) async fn get_activity_cached(
  pool: &mut DbPool<'_>,
  activity_id: ActivityId,
) -> Result<CachedActivityInfo> {
  static ACTIVITIES: LazyLock<Cache<ActivityId, CachedActivityInfo>> =
    LazyLock::new(|| Cache::builder().max_capacity(10000).build());
  ACTIVITIES
    .try_get_with(activity_id, async {
      Ok(Some(Arc::new(SentActivity::read(pool, activity_id).await?)))
    })
    .await
    .map_err(|e: Arc<LemmyError>| anyhow::anyhow!("err getting activity: {e:?}"))
}

/// return the most current activity id (with 1 second cache)
pub(crate) async fn get_latest_activity_id(pool: &mut DbPool<'_>) -> Result<Option<ActivityId>> {
  static CACHE: LazyLock<Cache<(), Option<ActivityId>>> = LazyLock::new(|| {
    Cache::builder()
      .time_to_live(*CACHE_DURATION_LATEST_ID)
      .build()
  });
  CACHE
    .try_get_with((), async {
      use lemmy_db_schema_file::schema::sent_activity::dsl::{id, sent_activity};
      let conn = &mut get_conn(pool).await?;
      let latest_id: Option<ActivityId> = sent_activity
        .select(diesel::dsl::max(id))
        .get_result(conn)
        .await?;
      anyhow::Result::<_, anyhow::Error>::Ok(latest_id)

View on GitHub (pinned to 439734dd63)

Solutions

  1. Confirm whether the id is a normal PostgreSQL sequence hole — treat as benign and skip
  2. Check DB health/logs for the underlying query failure
  3. Retry once the database is reachable; the cache will re-fetch
  4. If gaps recur and block the queue, adjust the loop to skip missing ids instead of failing

Example fix

// before
let activity = get_activity_cached(pool, activity_id).await?; // aborts on gap
// after
let activity = match get_activity_cached(pool, activity_id).await {
  Ok(a) => a,
  Err(e) => { warn!("skipping activity {activity_id}: {e:?}"); continue; }
};
Defensive patterns

Strategy: try-catch

Try / catch

match get_activity_cached(pool, id).await {
  Ok(a) => process(a),
  Err(e) => { warn!("activity {id} unavailable (maybe serial gap): {e:?}"); skip(id); }
}

Prevention

When it happens

Trigger: spawn_send_if_needed calls get_activity_cached with an activity_id that does not exist in sent_activities (serial gap) or the DB read fails (connection/query error).

Common situations: PostgreSQL sequence gaps after rollbacks/crashes cause the queue to reference ids that were never committed; transient DB outages during federation; read replicas lagging.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of LemmyNet/lemmy@439734dd63 (2026-09-06). Data as JSON: /api/errors/e192313ad2cf0ac3. Report an issue: GitHub.