The erli18n library's application callback: the OTP entry point
that brings the process tree up and down.
What it is and what problem it solves
This is the module pointed to by {mod, {erli18n_app, []}} in
erli18n.app.src. When someone runs application:ensure_all_started(erli18n)
(or the release boot), OTP calls start/2 here, and when the application
stops, it calls stop/1. There is nothing erli18n-specific in it beyond
delegating: it exists because the application_controller requires a
callback module, not because there is any i18n logic to run at boot.
Mental model (for the maintainer)
Think of this module as the thinnest possible shell around the supervision tree. All the load-bearing complexity lives one hop ahead:
start/2callserli18n_sup:start_link/0and returns the supervisor's{ok, Pid}raw, unwrapped. ThatPidbecomes the pid of the application that theapplication_controllergoes on to monitor.- The real topology lives in
erli18n_sup: arest_for_onestrategy with two children in load-bearing order —erli18n_table_owner(owner/heir of the ETS table) beforeerli18n_server(worker/writer). A crash of the worker does not take down the owner, so theerli18n_catalogtable and all loaded catalogs survive the restart and are re-handed-over viaETS-TRANSFER. This is a bug fix, not an accidental detail: seeerli18n_supand Finding #10 of the technical review. - This module's own state: zero. It does not touch ETS, does not touch
the process dictionary, and neither reads nor writes
application:get_env/2. Theenvdefaults (emit_lookup_telemetry,memory_warning_threshold,memory_warning_rate_limit_seconds) are declared inerli18n.app.srcand consumed byerli18n_telemetry— never here. - That is why
stop/1ignores theStateand returnsok: there is no resource to release at this level. The ordered shutdown of the children (including theterminate/2of the worker and of the owner) is the responsibility of the supervision tree, triggered by theapplication_controllerafterstop/1returns.
When a dev touches this module
Almost never directly. The normal path is:
- Library consumer: runs
application:ensure_all_started(erli18n)and moves on toerli18n:gettext/3,erli18n_server:ensure_loaded/3, etc. Does not import this module. - Maintainer: touches this only if the shape of the boot changes — for
example, starting to read
Argsfrom{mod, {erli18n_app, Args}}, doing one-shot setup at start, or handlingType(normalvs takeover/failover in a distributed scenario). Today both arguments are ignored on purpose. If you are going to add a new top-level process, the place iserli18n_sup:init/1, not here —start/2should stay a one-liner.
Quickstart
1> {ok, Started} = application:ensure_all_started(erli18n).
{ok,[erli18n]}
2> lists:member(erli18n, Started).
true
3> is_pid(whereis(erli18n_sup)).
true
4> is_pid(whereis(erli18n_server)).
true
5> application:stop(erli18n).
okThe exact list in
{ok, Started}depends on the environment. Sincetelemetryis declared inoptional_applicationsinerli18n.app.src, if it is present but not yet started,ensure_all_started/1brings it up too and includes it in the list (e.g.{ok,[telemetry,erli18n]}). The literal{ok,[erli18n]}above holds whentelemetryis absent or already up; that is why the robust test islists:member(erli18n, Started), not a comparison against the whole list.
Summary
Functions
Callback application:start/2: brings up the root supervision tree.
Callback application:stop/1: post-shutdown cleanup point — a no-op here.
Functions
Callback application:start/2: brings up the root supervision tree.
Delegates raw to erli18n_sup:start_link/0 and returns the supervisor's
{ok, Pid} without any wrapping. That Pid is the pid that the
application_controller adopts as the pid of the erli18n application and
goes on to monitor; when it dies, the application is considered terminated.
Parameters
Type— the start type that OTP passes (normalon a regular boot;{takeover, Node}/{failover, Node}in distributed applications). Ignored: the erli18n boot is identical in any mode, so there is no branching onType.Args— the term from{mod, {erli18n_app, Args}}inerli18n.app.src, today[]. Ignored: no boot configuration is read here (the runtime defaults live inenvand are read byerli18n_telemetry, not by this callback).
Return
{ok, Pid}— on success, passed through directly fromerli18n_sup:start_link/0.- Any
{error, Reason}coming from below propagates intact: this callback has notry/catchand no fallback. The normal case of a child failing at boot is an{error, {shutdown, Reason}}— the form thatsupervisor:start_link/3returns (and which the Return section oferli18n_sup:start_link/0already documents) when a child fails its owninit/1, e.g.erli18n_table_ownerfailing to create the ETS table orerli18n_serverfailing to claim it. That is the term the maintainer will see and match on. An error here makesensure_all_started/1fail and the application does not come up — the correct OTP behavior, with no masking.
Example
1> {ok, Started} = application:ensure_all_started(erli18n).
{ok,[erli18n]}
2> lists:member(erli18n, Started).
true
3> SupPid = whereis(erli18n_sup), is_pid(SupPid).
trueThe literal
{ok,[erli18n]}on line 1 holds whentelemetry(declared inoptional_applications) is absent or already started; if it gets brought up now, it also appears in the list. Hence thelists:member/2instead of comparing the whole list.
Sibling function: stop/1. Topology started: erli18n_sup:init/1.
Callback application:stop/1: post-shutdown cleanup point — a no-op here.
The application_controller calls stop/1 after having already torn
down the supervision tree (terminating the children in reverse start order:
erli18n_server before erli18n_table_owner, each running its own
terminate/2). By the time control reaches here, no resource of this module
is left to release, because it created none — no ETS, no process
dictionary, no ports. That is why the body is just ok.
Parameters
State— the value thatstart/2would have returned as the second element of{ok, Pid, State}. Sincestart/2returns the 2-tuple{ok, Pid}(withoutState), it is theapplication_controllerthat substitutes[]for theStatewhen calling this callback: whenstart/2uses the{ok, Pid}form instead of{ok, Pid, State}, the controller normalizes the missing state to[]. That is why the argument here is[]. Ignored either way: there is no state to undo.
Return
ok— always. This callback has no error path and no crash path: it is total and does not inspect the argument.
Example
1> application:ensure_all_started(erli18n).
{ok,[erli18n]}
2> application:stop(erli18n).
ok
3> whereis(erli18n_sup).
undefinedSibling function: start/2. The shutdown of the children belongs to the
supervision tree: see erli18n_sup.