erli18n_sup (erli18n v0.7.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)

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.

Because nothing owns the catalogs, the tree needs no table owner, no heir, and no handoff, and therefore no ordering constraint between children: a single worker under one_for_one is enough.

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 — 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, 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.