Cyclium.WorkClaims behaviour (Cyclium v0.1.15)

Copy Markdown View Source

Behaviour for lease-based distributed work claiming.

When configured, ensures at-most-once execution of episodes across clustered nodes. Each episode's dedupe_key is claimed before execution — only the node holding an active lease runs the work.

Configuration

# Use the built-in Ecto-based implementation:
config :cyclium, work_claims: Cyclium.WorkClaims.EctoClaims

# Or a SQL Server-optimized adapter in consuming apps:
config :cyclium, work_claims: MyApp.WorkClaims.SqlServer

# Or omit entirely — no claiming, fully backwards compatible

Gate functions

All integration points use gate_* functions which return {:ok, :passthrough} when no implementation is configured. This makes work claims opt-in with zero impact on existing deployments.

Summary

Callbacks

Returns true if owner_node still holds the claim on dedupe_key at the given fence (ownership generation). Optional — adapters that don't implement it fall back to an owner-only ownership check via renew/3.

Functions

Attempt to acquire a lease on dedupe_key. Returns {:ok, :passthrough} if work claims are not configured.

Mark work as completed. No-op if unconfigured.

Mark work as failed. No-op if unconfigured.

Returns true if owner_node still holds the claim at fence.

Lists up to limit claims whose lease has elapsed (still :claimed, past lease_until) — read-only, does not transition them. Returns {:ok, []} when unconfigured. See Cyclium.WorkClaims.EctoClaims.reclaim_expired/1.

Renew the lease for dedupe_key. No-op if unconfigured.

Types

acquire_result()

@type acquire_result() :: {:ok, Cyclium.Schemas.WorkClaim.t()} | {:error, :busy}

owner_result()

@type owner_result() :: :ok | {:error, :not_owner}

Callbacks

acquire(dedupe_key, owner_node, opts)

@callback acquire(dedupe_key :: String.t(), owner_node :: String.t(), opts :: keyword()) ::
  acquire_result()

complete(dedupe_key, owner_node)

@callback complete(dedupe_key :: String.t(), owner_node :: String.t()) :: owner_result()

fail(dedupe_key, owner_node, error_detail)

@callback fail(dedupe_key :: String.t(), owner_node :: String.t(), error_detail :: map()) ::
  owner_result()

owns?(dedupe_key, owner_node, fence)

(optional)
@callback owns?(dedupe_key :: String.t(), owner_node :: String.t(), fence :: integer()) ::
  boolean()

Returns true if owner_node still holds the claim on dedupe_key at the given fence (ownership generation). Optional — adapters that don't implement it fall back to an owner-only ownership check via renew/3.

reclaim_expired(limit)

@callback reclaim_expired(limit :: pos_integer()) ::
  {:ok, [Cyclium.Schemas.WorkClaim.t()]}

renew(dedupe_key, owner_node, lease_seconds)

@callback renew(
  dedupe_key :: String.t(),
  owner_node :: String.t(),
  lease_seconds :: pos_integer()
) :: owner_result()

Functions

gate_acquire(dedupe_key, owner_node, opts \\ [])

Attempt to acquire a lease on dedupe_key. Returns {:ok, :passthrough} if work claims are not configured.

gate_complete(dedupe_key, owner_node)

Mark work as completed. No-op if unconfigured.

gate_fail(dedupe_key, owner_node, error_detail \\ %{})

Mark work as failed. No-op if unconfigured.

gate_owns?(dedupe_key, owner_node, fence)

Returns true if owner_node still holds the claim at fence.

Returns true (we own) when claims are unconfigured. When the configured adapter implements owns?/3 and a fence is known, delegates to it (fence-aware). Otherwise falls back to an owner-only check via renew/3, preserving protection for adapters that don't implement the fence callback.

gate_reclaim_expired(limit \\ 100)

Lists up to limit claims whose lease has elapsed (still :claimed, past lease_until) — read-only, does not transition them. Returns {:ok, []} when unconfigured. See Cyclium.WorkClaims.EctoClaims.reclaim_expired/1.

gate_renew(dedupe_key, owner_node, lease_seconds)

Renew the lease for dedupe_key. No-op if unconfigured.