[erlang-questions] RFC: On `inet_tcp_dist` and `erl_epmd` interaction

Ciprian Dorin Craciun ciprian.craciun@REDACTED
Mon Oct 24 15:27:20 CEST 2011


== Summary ==

    I've found out that it is "theoretically" possible to override the
behavior of the default `erl_epmd` module with a custom, but
"compatible" module, without touching the `kernel` application (only
through configuration directives). I've labeled this method as
"theoretical" because the way in which the modules `erl_epmd` and
`inet_tcp_dist` (or any of the `inet_*_dist` family) interact makes
them inseparable.

    I'm writing this email as I want to help in enabling the
overriding of the default `erl_epmd` module in a correct, simple, and
the least intrusive method possible. (By "I want to help" I mean I am
offering to discuss, write, document and test the code.)


== Problem description  ==

    As stated, there is a function `net_kernel:epmd_module`, which
conforming to the (source code) documentation should (quote): "return
module_name of erl_epmd or similar gen_server_module".
        https://github.com/erlang/otp/blob/OTP_R14B04/lib/kernel/src/net_kernel.erl#L1283

    Unfortunately its only usage is in `erl_distribution.erl` to start
the `gen_server` process.
        https://github.com/erlang/otp/blob/OTP_R14B04/lib/kernel/src/erl_distribution.erl#L39

    All the other important modules `inet_*_dist`, `net_adm` directly
use the module `erl_epmd`, without the `net_kernel` indirection.
        https://github.com/erlang/otp/blob/OTP_R14B04/lib/kernel/src/inet_tcp_dist.erl#L70
        https://github.com/erlang/otp/blob/OTP_R14B04/lib/kernel/src/inet_tcp_dist.erl#L254

    As a result it is impossible to actually replace the way in which
`inet_*_dist` modules resolve the transport layer address (more
exactly the port) of the other nodes.


== Problem analysis ==

    I think there are possible purposes of the `net_kernel:epmd_module`:
    a) to give the name of a module which should export a `start_link`
function, which in turn spawns a process, registering under the name
`erl_epmd` and responding to `erl_epmd` messages in a proper manner
(thus implementing the "internal" `erl_epmd` protocol); (and as a
backend, maybe the UDP EPMD protocol;)
    b) or to give the name of a module which should export the
`register_node/2`, `port_please/2`, `names/0`, and `names/1` functions
which should act according to the specs in `erl_epmd` (thus
implementing the `erl_epmd` "interface" / behavior);

    As such there is a decision between "implementing a message
protocol" or "implementing an interface". I.e.:
    * in the first case (implementing the `erl_epmd` internal
protocol) the overriding module receives messages, and responds to
them in a proper manner; but the "clients" still use the `erl_epmd`
module as a frontend (which in turn sends messages to the named
`erl_epmd` process);
    * in the second case *all* clients should use the overriding
module (via `net_kernel:epmd_module`), and this one in its turn is
free to implement the "interface" functions as it sees fit as long as
it doesn't break the spec;

    Now the way in which `net_kernel:epmd_module` is used (only once
to start the server) and the fact that all `inet_*_dist` modules use
directly the `erl_epmd` module, suggests that the initial plan was to
go with solution a) -- i.e. the overriding module should register a
process under the well established name, and it should respond to
messages. (This is also suggested by the documentation quote: "or
similar gen_server_module".)

    Unfortunately the way in which `erl_epmd` module is implemented
suggests method b). Actually it is even worse:
    * half of the functionality is implemented by delegating work to a
`gen_server` process, see `register_node` function:
        https://github.com/erlang/otp/blob/OTP_R14B04/lib/kernel/src/erl_epmd.erl#L108
    * and half is implemented by directly executing the code in the
"client" process, see `port_please` and `names` functions, which in
turn call `get_port` and `get_names`:
        https://github.com/erlang/otp/blob/OTP_R14B04/lib/kernel/src/erl_epmd.erl#L292
        https://github.com/erlang/otp/blob/OTP_R14B04/lib/kernel/src/erl_epmd.erl#L418


== Solution ==

    Now by me, method a) (as presented above, i.e. implementing the
internal `erl_epmd` protocol by a named process) is the one most
"in-line" with OTP principles. (But even b) could work.)

    Thus in order to touch as little as possible the existing code, I
would propose to:
    * update `erl_epmd` module, so that all the "public" functions
(i.e. `port_please`, `names`, etc.) in fact send a message through
`gen_server:call` to that process registered under the `erl_epmd` name
(as `register_node` does);
    * the default implementation in `erl_epmd` in `handle_call`,
spawns a new process where it calls the internal `get_port` or
`get_names` and replies to the original call via `gen_server:reply`;
(to keep the concurrency model as is now, without serializing
requests);


== Conclusion ==

    For me -- and the project I'm involved in -- it is really
imperative to be able to replace the way in which ports are resolved.
I could do this by branching OTP, and maintaining a set of patches.
But I would prefer (and I think it could benefit others too) to "fix"
the current situation.

    As stated in the summary, I'm offering to write the patch and test
it. But before I come up with a patch, I want to ask for feedback as
maybe I've missed something. Therefore any feedback is very important
to me.

    Thanks for the time (as the email is quite long) :)
    Ciprian.


    P.S.: The reason I want to replace the current `erl_epmd` module I
can describe in a different thread. (There are actually two different
but related reasons, one not being directly tied to this problem, but
both are related to the `-no_epmd` option, which I've tried to discuss
in a previous thread.)



More information about the erlang-questions mailing list