erli18n_sup (erli18n v0.6.0)

Copy Markdown View Source

Root supervisor of the erli18n application tree.

What it is and what it solves

This is the library's only supervisor: it is the process started by erli18n_app:start/2 and the root of the OTP tree. Its sole responsibility is to keep alive the single process that underpins the translation runtime:

  • erli18n_server — the worker/writer. It serializes all writes (load/reload/unload of catalogs) through its gen_server and is the ONLY process that mutates persistent_term.

The reader goes through neither this supervisor nor the worker: lookup_* reads straight from persistent_term in the calling process, without blocking. This tree exists only to keep the writer alive, not to mediate the read hot path.

Mental model (why a single worker is enough now)

The catalogs are stored in persistent_term (see erli18n_pt_store), which is owned by the runtime, not by any process. A persistent term is not destroyed when the process that installed it dies. So a crash of the worker loses NOTHING: every loaded catalog survives untouched, and the restarted worker resumes serializing writes against the surviving terms.

This is a structural simplification over the previous ETS design. ETS destroyed a table the instant its owner died, so the old tree needed a dedicated table owner holding the table as its heir, plus a rest_for_one topology with the owner started before the worker, so that a worker crash returned the table to the owner via 'ETS-TRANSFER' instead of wiping every catalog (Finding #10). persistent_term makes that whole subsystem unnecessary: there is no table to own, no heir, no handoff, and therefore no ordering constraint between children. The tree collapses to a single worker under one_for_one.

Fixed configuration

The restart intensity is {intensity => 5, period => 10} (at most 5 restarts in 10 seconds before the supervisor gives up) and is hardcoded by a decision recorded in AMB-002 — it is not configurable via application:get_env/2. The single child is permanent with shutdown => 5000.

When a dev touches this module

Almost never directly. Library consumers call application:ensure_all_started(erli18n), which brings up the application and, through it, this supervisor. Touching things here only makes sense when altering the tree's topology (adding/removing a child, changing strategy or intensity).

Quickstart

1> {ok, _Started} = application:ensure_all_started(erli18n).
{ok,[erli18n]}
2> whereis(erli18n_sup) =/= undefined.
true
3> [Id || {Id, _Pid, _Type, _Mods} <- supervisor:which_children(erli18n_sup)].
[erli18n_server]

The list in _Started may vary: erli18n.app.src declares telemetry in optional_applications, so if the optional app is present and has not yet been started, it shows up alongside (e.g.: {ok, [telemetry, erli18n]}). kernel and stdlib are already up and never enter that list. That is why the example matches {ok, _Started} instead of comparing the output literally.

Key functions

Summary

Functions

The supervisor:init/1 callback — defines the shape of the supervision tree.

Starts the root supervisor, registered locally as erli18n_sup.

Functions

init/1

The supervisor:init/1 callback — defines the shape of the supervision tree.

Called once by start_link/0 (via supervisor:start_link/3). It receives the [] argument passed in start_link/0 and returns {ok, {SupFlags, ChildSpecs}}. It is purely declarative: it builds maps and has no side effects nor error paths of its own.

SupFlags

  • strategy => one_for_one — a crash of the worker restarts only the worker. There is no owner to keep alive ahead of it and no ordering constraint, because the catalogs live in runtime-owned persistent_term and survive a worker crash untouched.
  • intensity => 5, period => 10 — at most 5 restarts in 10 seconds; on exceeding it, the supervisor gives up and propagates the failure upward. Fixed values (AMB-002), not configurable.

ChildSpecs

A single child: erli18n_serverpermanent, worker, shutdown => 5000. The catalog writer. In its init/1 it has nothing to claim (no ETS table, no handoff): the catalogs are already in persistent_term, so init/1 just returns an empty state.

Return

Always {ok, {SupFlags, ChildSpecs}}. There is no error clause: a malformed ChildSpec would be a programming bug detected by the supervisor while validating the tree, not a runtime failure mode.

Example

1> {ok, {SupFlags, Children}} = erli18n_sup:init([]).
2> maps:get(strategy, SupFlags).
one_for_one
3> [maps:get(id, C) || C <- Children].
[erli18n_server]

See also start_link/0, which installs this tree.

start_link()

Starts the root supervisor, registered locally as erli18n_sup.

The tree's entry point: it is what erli18n_app:start/2 calls. It delegates to supervisor:start_link/3 with {local, ?MODULE}, which makes the supervisor respond to the name erli18n_sup (usable in supervisor:which_children/1, whereis/1, etc.) and invokes init/1 to assemble the child.

Return

  • {ok, Pid} — the supervisor and its erli18n_server child started successfully.
  • {error, {already_started, Pid}} — a process is already registered under erli18n_sup (the application is already up). Starting the application twice via OTP does not reach here; this only shows up in manual calls.
  • {error, {shutdown, _}} — the child failed in its own init/1. The supervisor unwinds what came up and propagates the error.

It crashes (linked to the caller) only if there is a programming error in the construction of the ChildSpecs in init/1 — which, in this module, is static and does not depend on external input.

Example

1> {ok, Pid} = erli18n_sup:start_link().
{ok,<0.215.0>}
2> Pid =:= whereis(erli18n_sup).
true
3> erli18n_sup:start_link().
{error,{already_started,<0.215.0>}}

The third call demonstrates the failure mode described above: with the supervisor already registered under erli18n_sup, a second manual start_link/0 returns {error, {already_started, Pid}} with the Pid of the existing process — without tearing down or restarting anything.

See also init/1 for the definition of the tree this function installs.