PushX.Instance.Loader (PushX v0.15.0)

Copy Markdown View Source

Starts named instances on boot from your own source of truth.

PushX.Instance instances live in memory only (see PushX.Instance — "Lifecycle"), so after a node restart your application has to start them again. Put the loader in your supervision tree after whatever it reads from (typically your Repo) and give it a function that returns the instances to start:

# application.ex
children = [
  MyApp.Repo,
  {PushX.Instance.Loader, instances: &MyApp.Push.tenant_instances/0},
  MyAppWeb.Endpoint
]

# MyApp.Push
def tenant_instances do
  for tenant <- Tenants.with_push_credentials() do
    {:"tenant_#{tenant.id}_apns", :apns,
     key_id: tenant.apns_key_id,
     team_id: tenant.apns_team_id,
     private_key: tenant.apns_private_key,
     mode: :prod}
  end
end

The loader runs synchronously during boot: start_link/1 calls load/1, logs the outcome, and returns :ignore (it keeps no process around), so by the time the next child starts the instances exist. Each instance is started with PushX.Instance.start/3; one tenant's bad credentials are logged and skipped rather than stopping the application — pass on_error: :raise if you would rather fail the boot.

Options

  • :instances — (required) a list of {name, provider, config} tuples, or a 0-arity function / {module, function, args} that returns one. Called at load time, after earlier children (your Repo) are up.
  • :on_error:log (default) logs each failure and continues; :raise raises after attempting all instances, with every failure in the message (the supervisor then fails to start — use this when a missing tenant must stop the deploy).

Later provisioning (a new tenant signs up) is just PushX.Instance.start/3 from your own code; the loader is for boot. To re-run it at any time call load/1 — instances that are already running are reported as such, not restarted.

Summary

Functions

Starts every instance returned by :instances and reports what happened.

Runs load/1 synchronously and returns :ignore (no process is kept). Raises if on_error: :raise and any instance failed to start.

Types

result()

@type result() :: %{
  started: [atom()],
  already_running: [atom()],
  failed: [{atom(), term()}]
}

spec()

@type spec() :: {atom(), :apns | :fcm | :webpush, keyword()}

Functions

load(opts)

@spec load(keyword()) :: result()

Starts every instance returned by :instances and reports what happened.

Instances that are already running count as :already_running (so re-running the loader is safe); any other PushX.Instance.start/3 error lands in :failed with its reason and is logged at error level.

start_link(opts)

@spec start_link(keyword()) :: :ignore

Runs load/1 synchronously and returns :ignore (no process is kept). Raises if on_error: :raise and any instance failed to start.