Bloccs.Idempotency (bloccs v0.1.1)

Copy Markdown View Source

Runtime internals

Infrastructure called by compiler-generated pipelines — not part of the stable user API. You drive this through manifests, not by calling it directly; signatures may change between minor versions.

Tracks which idempotency keys a node has already processed, so a node declaring [contract].idempotency = { key = "request_id" } drops duplicate deliveries instead of re-running its effects.

Semantics (v0.4)

A key is reserved on entry via an atomic :ets.insert_new, so two simultaneous first-deliveries of the same key cannot both proceed — exactly one wins the reservation and runs; the other is treated as a duplicate. On successful completion the entry is kept (and its TTL refreshed); on terminal failure it is released, so a genuinely new submission can be reprocessed. Retries bypass reservation (they already own the key from the first attempt).

Entries expire after config :bloccs, :idempotency_ttl_ms (default 1 hour) and are swept periodically — this also frees reservations abandoned by a crashed process. The backing ETS table is public with read+write concurrency.

Summary

Functions

Returns a specification to start this module under a supervisor.

Mark key as processed for scope. No-op if the tracker hasn't started.

Release a previously reserved key (on terminal failure), so a fresh submission can be reprocessed. No-op if the tracker hasn't started.

Atomically reserve key for scope. Returns true if the caller now owns the key (proceed), or false if it was already reserved/completed (treat as a duplicate). Fail-open: returns true if the tracker hasn't started.

Drop all tracked keys. Test-only.

Has key already been reserved or completed for scope (within the TTL window)? Safe to call before the tracker has started — returns false.

Start the tracker (started under the bloccs application supervisor).

Types

scope()

@type scope() :: {atom(), atom()}

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

mark(scope, key)

@spec mark(scope(), term()) :: :ok

Mark key as processed for scope. No-op if the tracker hasn't started.

release(scope, key)

@spec release(scope(), term()) :: :ok

Release a previously reserved key (on terminal failure), so a fresh submission can be reprocessed. No-op if the tracker hasn't started.

reserve(scope, key)

@spec reserve(scope(), term()) :: boolean()

Atomically reserve key for scope. Returns true if the caller now owns the key (proceed), or false if it was already reserved/completed (treat as a duplicate). Fail-open: returns true if the tracker hasn't started.

reset()

@spec reset() :: :ok

Drop all tracked keys. Test-only.

seen?(scope, key)

@spec seen?(scope(), term()) :: boolean()

Has key already been reserved or completed for scope (within the TTL window)? Safe to call before the tracker has started — returns false.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Start the tracker (started under the bloccs application supervisor).