This is the main file defining the project. Let’s decrypt it together and remind ourselves what is the meaning of the different values.
-
{application, 'ranch'}-
Thats the name of the OTP application being created.
'ranch'is an atom.
-
-
{description, "Socket acceptor pool for TCP protocols."}-
The description of the application. A string.
-
-
{vsn, "2.0.0-rc.1"}-
The version of the application. A string.
-
-
{modules, ['ranch','ranch_acceptor','ranch_acceptors_sup','ranch_app','ranch_conns_sup','ranch_conns_sup_sup','ranch_crc32c','ranch_embedded_sup','ranch_listener_sup','ranch_protocol','ranch_proxy_header','ranch_server','ranch_server_proxy','ranch_ssl','ranch_sup','ranch_tcp','ranch_transport']}-
"All modules introduced by this application. systools uses this list when generating start scripts and tar files. A module can only be defined in one application."
-
It does correspond to all the
.erlfiles of thesrc/folder.
-
-
{registered, [ranch_sup,ranch_server]}-
The list of processes that will be accessible by their name instead of only by PID (== registered processes, see official Erlang documentation).
-
-
{applications, [kernel,stdlib,ssl]}-
"All applications that must be started before this application is allowed to be started. systools uses this list to generate correct start scripts. Defaults to the empty list, but notice that all applications have dependencies to (at least) Kernel and STDLIB."
-
Here we can note that Ranch needs the ssl external module to be loaded beforehand. Obviously, Ranch does use this security protocol so it makes sense.
-
-
-
{mod, {ranch_app, []}}-
"Specifies the application callback module and a start argument, see application(3). Key mod is necessary for an application implemented as a supervision tree, otherwise the application controller does not know how to start it. mod can be omitted for applications without processes, typically code libraries, for example, STDLIB."
-
That’s interesting! The starting point of the application being always
ranch_app. We’ll keep that in mind and start by this file for our source code anaylisis in the next chapter.
-
-
-
{env, []}-
"Configuration parameters used by the application. The value of a configuration parameter is retrieved by calling application:get_env/1,2. The values in the application resource file can be overridden by values in a configuration file (see config(4)) or by command-line flags (see erts:erl(1))."
-
No default configuration parameter being set here.
-
-
]}.
Source: Official Erlang Documentation
Yep, it has -behaviour(application), which makes sense because it is part of the mod parameter above.
The file is quite small and easy to understand.
-
that’s where profiling logic is in
-
ranch_sup:start_link/0is run on start of the application.
That’s the supervisor for the whole Ranch app. We can now start our supervisor tree:
See LYSEFGG for more info on the Application Master. Let’s dive in ranch_sup to know what’s behind these question marks.
As expected, it implements the supervisor behaviour.
The following code:
start_link() →
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
is standard. No argument will be passed to init/1.
init/1 with an empty argument list will be run in the spawned supervisor process.
We will now study its content.
application:get_env(ranch_sup_intensity) is used to access the ranch_sup_intensity configuration parameter.
Similarly the ranch_sup_period configuration parameter is being accessed.
Then it is returned by the init/1 function as follow:
{ok, {#{intensity ⇒ Intensity, period ⇒ Period}, Procs}}.
-
"To prevent a supervisor from getting into an infinite loop of child process terminations and restarts, a maximum restart intensity is defined using two integer values specified with keys intensity and period in the above map. Assuming the values MaxR for intensity and MaxT for period, then, if more than MaxR restarts occur within MaxT seconds, the supervisor terminates all child processes and then itself. The termination reason for the supervisor itself in that case will be shutdown. intensity defaults to 1 and period defaults to 5."
Interestingly, these configuration parameters are not documented in the official Ranch Documentation. In migration_from_1.5.asciidoc, I quote:
"It is now possible to configure the restart intensity for
ranch_sup using the OTP application environment. This
feature will remain undocumented unless there is popular
demand for it."
test/ranch_ct_hook.erl:-
application:set_env(ranch, ranch_sup_intensity, 10) -
application:set_env(ranch, ranch_sup_period, 1)
The last parameter returned by init/1 is the variable Procs that corresponds to
what’s called ChildSpec in the official Erlang documentation.
-
Procs = [#{id ⇒ ranch_server, start ⇒ {ranch_server, start_link, []}}
which is pretty standard.
ranch_sup is therefore supervising ranch_server, which entry point is start_link/0.
We can update our supervision tree:
It is also worth noticing that an ETS table is being initialized using ets:new/2.
The ETS table is named after ranch_server module so it is expected that this module will be interacting with it.
We will start by ranch_server.
The first thing to notice is that it implements gen_server, as expected by its name.
gen_server keeping? As usual, you need to look for it at the beginning of the file, after the list of export:-
A macro
TABis being defined as?MODULEbut not used throughout the whole file - which is a bit awkard. Sometimes we see the usage of?MODULE, sometimes?TAB. I don’t understand the point of this macro. Maybe there’s a good reason for it that I am missing out. -
The interesting part is the record
statebeing initialized with an empty list.staterecord contains a value of typemonitors()which is itself defined as follows:-
[{{reference(), pid()}, any()}]see official Erlang documentation for explanation of the types
-
start_link/0 without surprises is running gen_server:start_link/4, with standard arguments.
We know that the init/1 function is the starting point of this process. We therefore scroll the file down to see its implementation.
As expected, init/1 returns {ok, State} with State being here
of type state, record defined previously (see above).
The list of all connections and listener supervisors is fetched from the ETS table previously created.
ranch_server starts monitoring them,
and store the returned reference in the state,
together with its corresponding Pid,
the Reference of the supervisor process itself,
and its Id (for connection supervisor).
From that point on, it would be interesting to look for the location where the connection and listener supervisors are added to the ETS table.
To do so, we can search for "conns_sup" or "listener_sup" or "ets:insert" in the project.
ranch_server:handle_call/3:-
State = set_monitored_process({conns_sup, Ref, Id}, Pid, State0)called byranch_server:set_connections_sup/3, which is itself called byranch_conns_sup:init/7 -
State = set_monitored_process({listener_sup, Ref}, Pid, State0)called byranch_server:set_listener_sup/2, which is itself called byranch_listener_sup:init/1
ranch_server:set_monitored_process/3 is simply making ranch_server monitor the connection/listener supervisor,
update the ETS Table accordingly and return the new updated state.
It is called on creation of the supervisors.
-
Why is
erlang:monitor(process, Pid)being called ininit/1? It will stack with another monitoring reference, because the supervisors fetched from the ETS Table are supposed to be already monitored (seeranch_server:set_monitored_process/3)… Why isn’t the ETS Table also storing the MonitorRef? It would avoid having to do that… Or is that the intended behaviour? -
In any case, most likely, the ETS Table will be empty at this point, because
ranch_server:init/1has to have finished running before theranch_servergen_server becomes actually available for users, and before they can eventually callranch_server:handle_call/3! Therefore, the returned initial state will be empty on start ofranch_serverand the code that fetch the ETS Table is irrelevant…
What happens when ranch_server dies and is restarted by ranch_sup?
In this case, there will still be data in the ETS Table, data that will be monitored by…
the ranch_server process that just died! So, basically not monitored at all!
That’s it! That’s the reason why the use of erlang:monitor(process, Pid) in ranch_server:init/1 does not stack with another monitoring.
Its only (and important!) point is in case the process that was previously monitoring died!
ranch_server:get_listener_sup/1 is used in:-
ranch:get_status/1. -
ranch:suspend_listener/1 -
ranch:resume_listener/1 -
ranch:remove_connection/1 -
ranch:info/0andranch:info/1 -
ranch:procs/1
ranch_server:get_connections_sup/1 is used in:-
ranch:get_connections/2 -
ranch:apply_transport_options/2
These functions are the API that’s accessible by the user of the library. They are features. See the official documentation to know more about their usage.
Finally, ranch_server:get_connections_sup/2 is used by ranch_acceptor:start_link/5.
If you remember well the previous chapter, the listener was the one that was creating
the connection supervisor supervisor and the acceptor supervisor. Which means they were basically creating them.
However, it is obviously the acceptor which needs to trigger the connection supervisor to start the connection process.
So the acceptor has to know about a process that’s neither his parent nor his child.
That’s why he needs to get it from an ETS table, that’s handled by ranch_server!
We can now improve our schema:
So, as expected after the study of the documentation,
everytime a connection supervisor or a listener supervisor is started,
it is registered within the ranch_server.
Now we are wondering: when are they started?


