Amarula.RetryCache.ETS (amarula v0.4.0)

View Source

In-memory Amarula.RetryCache adapter — the default.

One :public, named ETS table per connection profile, holding {msg_id, entry}. State is lost on restart, which is acceptable: a retry receipt arrives within seconds of the original send.

Bound

A message only needs to be held for as long as a retry could still arrive for it. The protocol bounds that two ways, and this cache mirrors both (matching the reference implementation):

  • TTL — @ttl_ms (5 min). A retry receipt normally lands within seconds; after a few minutes it won't come (and the message is stale anyway). Entries older than the TTL (by entry.ts) are dropped on the next write.
  • Hard cap — :max_entries (default 512). A safety ceiling on memory; past it, the oldest entries are evicted. Size this to your peak sends per ~5 min so a burst can't push a still-unacked message out before its retry arrives.

If you already persist your sent messages, consider a read-only custom adapter pointed at your own store instead — see Amarula.RetryCache ("Using your own message store").

Ownership

The table is created by ensure_local/2, called from Connection.init, so it is owned by the Connection process and named by profile. Because the table dies with its owner, a Connection crash/restart recreates it empty — so a poisoned entry can never outlive the restart it triggers (no crash-loop on a bad cached value).

It must be :public, not :protected: it is written from two processes — Connection itself (on a retry receipt) and each per-recipient ConversationSender (the Amarula.RetryCache.Step in the send pipe records the sent message there) — while ownership (and thus the restart-clearing lifetime above) stays with Connection.

Options

  • :max_entries — hard cap before oldest-eviction (default 512).

Summary

Functions

Create the profile's table, owned by the calling process (Connection). Called from Connection.init before any reader, so we never create lazily on first use — no create race, no rescue. Idempotent.

Functions

ensure_local(state, profile)

@spec ensure_local(map(), atom() | String.t()) :: :ok

Create the profile's table, owned by the calling process (Connection). Called from Connection.init before any reader, so we never create lazily on first use — no create race, no rescue. Idempotent.