PhoenixKit.Modules.Emails.EventTrackerReconciler (phoenix_kit_emails v0.2.1)

Copy Markdown View Source

Stateless reconcile — the single code path that starts/stops a tracker's self-scheduling Oban chain, enforcing EventTracker.should_run?/1. See spec §4.2/§4.3.

Not a GenServer (decision §9.1): the correctness backbone is the periodic reconcile Cron (EventTrackerReconcileWorker), not a long-lived orchestrator process. reconcile/0/reconcile_tracker/1 are plain functions, safe to call from anywhere (boot, a settings toggle, the Cron tick, a future admin panel) — idempotent and cluster-safe:

  • "ensure exactly one chain" = an Oban unique insert — a genuinely dead chain gets a fresh immediate job (schedule_in: 0); a chain that's already alive (:available/:scheduled/:executing) hits the unique conflict and is left completely untouched, its own scheduled_at unchanged (no replace: — see ensure_chain/1's own comment for why touching it would silently override the operator's own polling interval). Enforced at the DATABASE by Oban's own uniqueness, so two nodes reconciling simultaneously cannot create two chains (spec §8a).
  • "ensure none" = Oban.cancel_all_jobs/1 scoped to available/ scheduled only (never executing — a running cycle is never interrupted; it dies on its own next time it checks should_run?/0 and doesn't self-reschedule, same mechanism SQSPollingJob/ BrevoPollingJob already rely on for the disable path — see #21).

A duplicate/racing insert at worst costs one extra no-op cycle (should_run?/0's per-cycle gate inside the job itself is the real safety net) — never two live chains.

Summary

Functions

Reconcile every registered tracker. Returns a list of {tracker, result} pairs, result being whatever reconcile_tracker/1 returns for that tracker — never raises on a single tracker's failure (one broken tracker must not stop the rest from reconciling).

Reconcile a single tracker against EventTracker.should_run?/1.

Functions

reconcile()

@spec reconcile() :: [{module(), term()}]

Reconcile every registered tracker. Returns a list of {tracker, result} pairs, result being whatever reconcile_tracker/1 returns for that tracker — never raises on a single tracker's failure (one broken tracker must not stop the rest from reconciling).

reconcile_tracker(tracker)

@spec reconcile_tracker(module()) ::
  {:ok, Oban.Job.t() | :not_running} | {:error, term()}

Reconcile a single tracker against EventTracker.should_run?/1.

  • should_run? == true{:ok, %Oban.Job{}} (the chain's current job — the existing one, untouched, if the chain is already alive; a freshly inserted one, running immediately, if it wasn't).
  • should_run? == false{:ok, :not_running} (any queued job for this tracker's worker was cancelled; a still-executing one is left to finish and die on its own).
  • {:error, reason} on an insert/cancel failure — logged, never raised, so a single tracker's transient DB hiccup during a Cron tick doesn't take down the rest.