peer(3erl)
πΈ peer(3erl) πΈ Erlang Module Definition
okay bestie, this is the peer module page β Erlang module definition and all β and we're gonna get into it π
π NAME π
peer β Start and control linked Erlang nodes. periodt.
π¦ DESCRIPTION π¦
Okay so real talk: this module gives you functions for starting linked Erlang nodes. The node that spawns the new nodes is called the origin, and the shiny new nodes are called peer nodes, or just peers. A peer node automatically terminates when it loses the control connection to the origin. That connection could be an Erlang distribution connection, or an alternative β TCP or standard I/O. The alternative connection gives you a way to execute remote procedure calls even when Erlang Distribution is not available, which is literally how you can test the distribution itself πβ¨
Peer node terminal input/output is relayed through the origin. If a standard I/O alternative connection is requested, console output also goes via the origin, allowing debugging of node startup and boot script execution (see -init_debug). File I/O is not redirected, contrary to slave(3erl) behaviour. Not me pointing that out but yes, important.
The peer node can start on the same or a different host (via ssh) or in a separate container (for example Docker). When the peer starts on the same host as the origin, it inherits the current directory and environment variables from the origin. cute.
π Note π
This module is designed to facilitate multi-node testing with Common Test. Use the ?CT_PEER() macro to start a linked peer node according to Common Test conventions: crash dumps written to specific location, node name prefixed with module name, calling function, and origin OS process ID. Use random_name/1 to create sufficiently unique node names if you need more control π
A peer node started without alternative connection behaves similarly to slave(3erl). When an alternative connection is requested, the behaviour is similar to test_server:start_node(Name, peer, Args).
π EXAMPLE π
The following example implements a test suite starting extra Erlang nodes. It employs a number of techniques to speed up testing and reliably shut down peer nodes:
- peers start linked to test runner process. If the test case fails, the peer node is stopped automatically, leaving no rogue nodes running in the background
- arguments used to start the peer are saved in the control process state for manual analysis. If the test case fails, the CRASH REPORT contains these arguments
- multiple test cases can run concurrently speeding up overall testing process, peer node names are unique even when there are multiple instances of the same test suite running in parallel
-module(my_SUITE).
-behaviour(ct_suite).
-export([all/0, groups/0]).
-export([basic/1, args/1, named/1, restart_node/1, multi_node/1]).
-include_lib("common_test/include/ct.hrl").
groups() ->
[{quick, [parallel],
[basic, args, named, restart_node, multi_node]}].
all() ->
[{group, quick}].
basic(Config) when is_list(Config) ->
{ok, Peer, _Node} = ?CT_PEER(),
peer:stop(Peer).
args(Config) when is_list(Config) ->
%% specify additional arguments to the new node
{ok, Peer, _Node} = ?CT_PEER(["-emu_flavor", "smp"]),
peer:stop(Peer).
named(Config) when is_list(Config) ->
%% pass test case name down to function starting nodes
Peer = start_node_impl(named_test),
peer:stop(Peer).
start_node_impl(ActualTestCase) ->
{ok, Peer, Node} = ?CT_PEER(#{name => ?CT_PEER_NAME(ActualTestCase)}),
%% extra setup needed for multiple test cases
ok = rpc:call(Node, application, set_env, [kernel, key, value]),
Peer.
restart_node(Config) when is_list(Config) ->
Name = ?CT_PEER_NAME(),
{ok, Peer, Node} = ?CT_PEER(#{name => Name}),
peer:stop(Peer),
%% restart the node with the same name as before
{ok, Peer2, Node} = ?CT_PEER(#{name => Name, args => ["+fnl"]}),
peer:stop(Peer2).
The next example demonstrates how to start multiple nodes concurrently:
multi_node(Config) when is_list(Config) ->
Peers = [?CT_PEER(#{wait_boot => {self(), tag}})
|| _ <- lists:seq(1, 4)],
%% wait for all nodes to complete boot process, get their names:
_Nodes = [receive {tag, {started, Node, Peer}} -> Node end
|| {ok, Peer} <- Peers],
[peer:stop(Peer) || {ok, Peer} <- Peers].
Start a peer on a different host. Requires ssh key-based authentication set up, allowing "another_host" connection without password prompt.
Ssh = os:find_executable("ssh"),
peer:start_link(#{exec => {Ssh, ["another_host", "erl"]},
connection => standard_io}),
The following Common Test case demonstrates Docker integration, starting two containers with hostnames "one" and "two". In this example Erlang nodes running inside containers form an Erlang cluster.
docker(Config) when is_list(Config) ->
Docker = os:find_executable("docker"),
PrivDir = proplists:get_value(priv_dir, Config),
build_release(PrivDir),
build_image(PrivDir),
%% start two Docker containers
{ok, Peer, Node} = peer:start_link(#{name => lambda,
connection => standard_io,
exec => {Docker, ["run", "-h", "one", "-i", "lambda"]}}),
{ok, Peer2, Node2} = peer:start_link(#{name => lambda,
connection => standard_io,
exec => {Docker, ["run", "-h", "two", "-i", "lambda"]}}),
%% find IP address of the second node using alternative connection RPC
{ok, Ips} = peer:call(Peer2, inet, getifaddrs, []),
{"eth0", Eth0} = lists:keyfind("eth0", 1, Ips),
{addr, Ip} = lists:keyfind(addr, 1, Eth0),
%% make first node to discover second one
ok = peer:call(Peer, inet_db, set_lookup, [[file]]),
ok = peer:call(Peer, inet_db, add_host, [Ip, ["two"]]),
%% join a cluster
true = peer:call(Peer, net_kernel, connect_node, [Node2]),
%% verify that second peer node has only the first node visible
[Node] = peer:call(Peer2, erlang, nodes, []),
%% stop peers, causing containers to also stop
peer:stop(Peer2),
peer:stop(Peer).
build_release(Dir) ->
%% load sasl.app file, otherwise application:get_key will fail
application:load(sasl),
%% create *.rel - release file
RelFile = filename:join(Dir, "lambda.rel"),
Release = {release, {"lambda", "1.0.0"},
{erts, erlang:system_info(version)},
[{App, begin {ok, Vsn} = application:get_key(App, vsn), Vsn end}
|| App <- [kernel, stdlib, sasl]]},
ok = file:write_file(RelFile, list_to_binary(lists:flatten(
io_lib:format("~tp.", [Release])))),
RelFileNoExt = filename:join(Dir, "lambda"),
%% create boot script
{ok, systools_make, []} = systools:make_script(RelFileNoExt,
[silent, {outdir, Dir}]),
%% package release into *.tar.gz
ok = systools:make_tar(RelFileNoExt, [{erts, code:root_dir()}]).
build_image(Dir) ->
%% Create Dockerfile example, working only for Ubuntu 20.04
%% Expose port 4445, and make Erlang distribution to listen
%% on this port, and connect to it without EPMD
%% Set cookie on both nodes to be the same.
BuildScript = filename:join(Dir, "Dockerfile"),
Dockerfile =
"FROM ubuntu:20.04 as runner\n"
"EXPOSE 4445\n"
"WORKDIR /opt/lambda\n"
"COPY lambda.tar.gz /tmp\n"
"RUN tar -zxvf /tmp/lambda.tar.gz -C /opt/lambda\n"
"ENTRYPOINT [\"/opt/lambda/erts-" ++ erlang:system_info(version) ++
"/bin/dyn_erl\", \"-boot\", \"/opt/lambda/releases/1.0.0/start\","
" \"-kernel\", \"inet_dist_listen_min\", \"4445\","
" \"-erl_epmd_port\", \"4445\","
" \"-setcookie\", \"secret\"]\n",
ok = file:write_file(BuildScript, Dockerfile),
os:cmd("docker build -t lambda " ++ Dir).
π DATA TYPES π
okay bestie, this is the part where we meet the actual building blocks. every single one of these matters, so no skimming, i'm watching π
π server_ref() = pid()
this bad boy identifies the controlling process of a peer node. that's it, that's the whole vibe β a pid() pointing at whoever's running the show.
π¦ start_options()
start_options() =
#{name => atom() | string(),
longnames => boolean(),
host => string(),
peer_down => stop | continue | crash,
exec => exec(),
connection => connection(),
args => [string()],
env => [{string(), string()}],
wait_boot => wait_boot(),
shutdown =>
close | halt |
{halt, disconnect_timeout()} |
disconnect_timeout()}
these are the options you can pass when you spin up a peer node through start/1 and start_link/0,1. iykyk. let's go through them one by one because each one is lowkey a whole personality:
name:β the node name, aka the part before the"@". and listen: whennameis not specified buthostis, peer follows compatibility behaviour and uses the origin node name. so it's not out here inventing names, it's being polite.host:β enforces a specific host name. use this when you wanna override the default and start"node@localhost"instead of"node@realhostname". main character energy for your hostname, honestly.longnames:β use long names to start a node. the default gets taken from the origin vianet_kernel:longnames(). and if the origin isn't distributed? short names is the default, periodt.peer_down:β this one defines what the peer control process does when the control connection gets closed from the peer node side (think: the peer crashes or dumps core π). set it tostop(that's the default) and a lost control connection makes the control process exit normally. set it tocontinueand the control process keeps running. set it tocrashand the controlling process exits abnormally. dramatic, but correct.exec:β an alternative mechanism to start peer nodes with β like, say,sshinstead of the defaultbash. icon.connection:β alternative connection specification. see theconnectiondatatype below, bestie, it's coming.args:β extra command line arguments appended to the"erl"command. and here's the important bit: arguments are passed as is β no escaping or quoting is needed or accepted. so don't try to be cute with quotes, fr fr.env:β a list of environment variables with their values. this list gets applied to a locally started executable. if you need to change the environment of the remote peer, adjustargsto contain-env ENV_KEY ENV_VALUE. that's the move π«Άwait_boot:β specifies thestart/start_linktimeout. see thewait_bootdatatype.shutdown:β specifies the peer node stopping behaviour. seestop().
πΈ peer_state() = booting | running | {down, Reason :: term()}
the peer node state. either it's booting, it's running, or it's {down, Reason :: term()} and we're all pretending we're fine π
β¨ connection()
connection() =
0..65535 | {inet:ip_address(), 0..65535} | standard_io
an alternative connection between the origin and the peer. and here's the tea: when the connection closes, the peer node terminates automatically. if the peer_down startup flag is set to crash, the controlling process on the origin node exits with the corresponding reason β effectively giving you a two-way link. cute, symmetrical, slay.
when connection is set to a port number, the origin starts listening on the requested TCP port, and the peer node connects to that port. when it's set to an {IP, Port} tuple, the origin listens only on the specified IP. and the port number can be 0 for automatic selection, which is very "let the universe decide" of you π§
using the standard_io alternative connection starts the peer attached to the origin (other connections use the -detached flag to erl). in this mode peer and origin communicate via stdin/stdout. cozy.
π exec() = file:name() | {file:name(), [string()]}
overrides the executable used to start peer nodes with. by default it's the path to "erl", taken from init:get_argument(progname). and if progname is not known? peer makes its best guess given the current ERTS version. it's doing its best, okay π₯Ί
when a tuple is passed, the first element is the path to the executable, and the second element is prepended to the final command line. this is how you start peers on a remote host or inside a Docker container. see the examples above.
this option is genuinely useful for testing backwards compatibility with previous releases installed at specific paths, or when the Erlang installation location is missing from the PATH. no cap, very handy.
π wait_boot() = timeout() | {pid(), Tag :: term()} | false
specifies the start/start_link timeout in milliseconds. can be set to false, which lets the peer start asynchronously β very "i'll get there when i get there". and if {Pid, Tag} is specified instead of a timeout, the peer will send Tag to the requested process. iykyk, that's a whole notification system.
π disconnect_timeout() = 1000..4294967295 | infinity
the disconnect timeout. see stop(). yes, the range is that specific, and yes, infinity is an option if you're feeling infinite π«
π EXPORTS π
π« call/4 & call/5 π«
call(Dest :: server_ref(),
Module :: module(),
Function :: atom(),
Args :: [term()]) ->
Result :: term()
call(Dest :: server_ref(),
Module :: module(),
Function :: atom(),
Args :: [term()],
Timeout :: timeout()) ->
Result :: term()
Okay so this one uses the alternative connection to evaluate apply(Module, Function, Args) on the peer node, and hands you back the matching value Result. π
Timeout is an integer meaning the timeout in milliseconds, or the atom infinity, which literally means the operation never times out β living its best eternal life, no cap. π«Ά
Heads up bestie: when an alternative connection isn't requested, this function will raise an exit signal with thenoconnectionreason. Lowkey not the vibe. Use theerpcmodule to communicate over Erlang distribution instead. β¨
π¦ cast/4 π¦
cast(Dest :: server_ref(),
Module :: module(),
Function :: atom(),
Args :: [term()]) ->
ok
Uses the alternative connection to evaluate apply(Module, Function, Args) on the peer node β but like, no response is ever delivered to the calling process. Fire and forget, main character energy, you say your thing and walk away. π
peer:cast/4 fails silently when the alternative connection isn't configured (rude, but okay π). Use the erpc module to communicate over Erlang distribution.
π send/3 π
send(Dest :: server_ref(),
To :: pid() | atom(),
Message :: term()) ->
ok
Uses the alternative connection to send Message to a process on the peer node. Silently fails if there's no alternative connection configured β again, it will not tell you, it will not text you back. π The process can be referenced either by process ID or by registered name.
π₯ get_state/1 π₯
get_state(Dest :: server_ref()) -> peer_state()
Returns the peer node state. The initial state is booting β the node stays in that state until the boot script is complete, and then it progresses to running. If the node stops, gracefully or not (no judgement π), the state changes to down.
πΈ random_name/0 πΈ
random_name() -> string()
The same as random_name(peer). Short queen. π
π random_name/1 π
random_name(Prefix :: string() | atom()) -> string()
Creates a sufficiently unique node name for the current host β combining a prefix, a unique number, and the current OS process ID. Iconic little recipe tbh. β¨
Note: use the?CT_PEER(["erl_arg1"])macro provided by Common Test β-include_lib("common_test/include/ct.hrl")β for convenience. It starts a new peer using Erlang distribution as the control channel, supplies the calling module's code path to the peer, and uses the calling function name for the name prefix. Literally does everything for you bestie π«Ά
β start/1 β
start(Options :: start_options()) ->
{ok, pid()} | {ok, pid(), node()} | {error, Reason}
Types:
Reason = term()
Starts a peer node with the specified start_options(). Returns the controlling process and the full peer node name β unless wait_boot isn't requested and the host name isn't known in advance. Then you get what you get. π
π start_link/0 π
start_link() -> {ok, pid(), node()} | {error, Reason :: term()}
The same as start_link(#{name => random_name()}). Default behaviour, still slaying. π
π§ start_link/1 π§
start_link(Options :: start_options()) ->
{ok, pid()} | {ok, pid(), node()} | {error, Reason}
Types:
Reason = term()
Starts a peer node in the same way as start/1, except the peer node is linked to the currently executing process. So if that process terminates, the peer node also terminates β ride or die, linked for life, literally cannot be separated. ππ«Ά
Accepts start_options(). Returns the controlling process and the full peer node name, unless wait_boot isn't requested and the host name isn't known in advance.
Also: when thestandard_ioalternative connection is requested, andwait_bootis not set tofalse, a failed peer boot sequence causes the caller to exit with the{boot_failed, {exit_status, ExitCode}}reason. Not a drill, you're going down with the ship. π
π stop/1 π
stop(Dest :: server_ref()) -> ok
Types:
disconnect_timeout() = 1000..4294967295 | infinity
Stops a peer node. How the node is stopped depends on the shutdown option passed when starting the peer node. Currently the following shutdown options are supported:
haltβ this is the default shutdown behavior. It behaves as shutdown option{halt, DefaultTimeout}whereDefaultTimeoutcurrently equals5000.{halt, Timeout :: disconnect_timeout()}β triggers a call toerlang:halt()on the peer node and then waits for the Erlang distribution connection to the peer node to be taken down. If this connection has not been taken down afterTimeoutmilliseconds, it will forcefully be taken down bypeer:stop/1. See the warning below for more info about this. π¬Timeout :: disconnect_timeout()β triggers a call toinit:stop()on the peer node and then waits for the Erlang distribution connection to the peer node to be taken down. If this connection has not been taken down afterTimeoutmilliseconds, it will forcefully be taken down bypeer:stop/1. Again, see the warning below. π«closeβ close the control connection to the peer node and return. This is the fastest way for the caller ofpeer:stop/1to stop a peer node.- Note that if the Erlang distribution connection is not used as control connection, it might not have been taken down when
peer:stop/1returns. Also note that the warning below applies when the Erlang distribution connection is used as control connection. β¨
- Note that if the Erlang distribution connection is not used as control connection, it might not have been taken down when
Warning: in the cases where the Erlang distribution connection is taken down bypeer:stop/1, other code that's independent of the peer code might react to the connection loss before the peer node is stopped, which might cause undesirable effects. For example,globalmight trigger even more Erlang distribution connections to other nodes to be taken down. The potential undesirable effects are, however, not limited to this. It is hard to say what the effects will be, since these effects can be caused by any code with links or monitors to something on the origin node, or code monitoring the connection to the origin node. So like... be aware. This is the π part of the page, fr fr.
okay bestie that's the whole page ππ this one was written by Maxim Fedorov from WhatsApp Inc., it's stdlib version 4.3.1.3, and the page is peer(3erl) β go forth and peer responsibly, mwah π
β¨
π¦ where this page comes from
- ubuntu noble: erlang-manpages 1:25.3.2.8+dfsg-1ubuntu4 Β· Apache-2.0
- ubuntu noble: erlang-manpages 1:25.3.2.8+dfsg-1ubuntu4.8 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms