|
| 1 | +defmodule Forge.EPMD do |
| 2 | + @moduledoc false |
| 3 | + |
| 4 | + # From Erlang/OTP 23+ |
| 5 | + @epmd_dist_version 6 |
| 6 | + |
| 7 | + @doc ~S""" |
| 8 | + This is the distribution port of the current node. |
| 9 | +
|
| 10 | + The parent node must be named `expert-manager-*`. |
| 11 | + The child node must be named `expert-project-*`. |
| 12 | +
|
| 13 | + When the parent boots the child, it must pass |
| 14 | + its node name and port as the respective environment |
| 15 | + variables `EXPERT_PARENT_NODE` and `EXPERT_PARENT_PORT`. |
| 16 | +
|
| 17 | + The parent must have this as a child in its supervision tree: |
| 18 | +
|
| 19 | + {Forge.NodePortMapper, []} |
| 20 | +
|
| 21 | + The child, in turn, must have this: |
| 22 | +
|
| 23 | + {Task, &Forge.NodePortMapper.register/0} |
| 24 | +
|
| 25 | + This will register the child within the parent, so they can |
| 26 | + find each other. |
| 27 | +
|
| 28 | + ## Example |
| 29 | +
|
| 30 | + In order to manually simulate the connections, run `elixirc epmd.ex` to compile |
| 31 | + this file and follow the steps below. Notice we call the functions in the |
| 32 | + `Forge.NodePortMapper` module directly, while in practice they will be called |
| 33 | + as part of the app's supervision tree. |
| 34 | +
|
| 35 | + # In one node |
| 36 | + $ iex --erl "-start_epmd false -epmd_module Elixir.Forge.EPMD" --sname expert-manager-foo |
| 37 | + iex(expert_parent_foo@macstudio)> Forge.NodePortMapper.start_link([]) |
| 38 | + iex(expert_parent_foo@macstudio)> Forge.EPMD.dist_port() |
| 39 | + 52914 |
| 40 | +
|
| 41 | + Get the port name from the step above and then, in another terminal, do: |
| 42 | +
|
| 43 | + $ EXPERT_PARENT_NODE=expert_parent_foo@macstudio EXPERT_PARENT_PORT=52914 \ |
| 44 | + iex --erl "-start_epmd false -epmd_module Elixir.Forge.EPMD" --sname expert-project-bar |
| 45 | + iex> Forge.NodePortMapper.register() |
| 46 | +
|
| 47 | + And in another terminal: |
| 48 | +
|
| 49 | + $ EXPERT_PARENT_NODE=expert_parent_foo@macstudio EXPERT_PARENT_PORT=52914 \ |
| 50 | + iex --erl "-start_epmd false -epmd_module Elixir.Forge.EPMD -expert parent_port 52914" --sname expert-project-baz |
| 51 | + iex> Forge.NodePortMapper.register() |
| 52 | +
|
| 53 | + If you try `Node.ping(:expert-project-bar@HOSTNAME)` from the last node, it should work. |
| 54 | + The child nodes will find each other even without EPMD. |
| 55 | + """ |
| 56 | + def dist_port do |
| 57 | + :persistent_term.get(:expert_dist_port, nil) |
| 58 | + end |
| 59 | + |
| 60 | + # EPMD callbacks |
| 61 | + |
| 62 | + def register_node(name, port), do: register_node(name, port, :inet) |
| 63 | + |
| 64 | + def register_node(name, port, family) do |
| 65 | + :persistent_term.put(:expert_dist_port, port) |
| 66 | + |
| 67 | + # We don't care if EPMD is not running |
| 68 | + case :erl_epmd.register_node(name, port, family) do |
| 69 | + {:error, _} -> {:ok, -1} |
| 70 | + {:ok, _} = ok -> ok |
| 71 | + end |
| 72 | + end |
| 73 | + |
| 74 | + def port_please(name, host), do: port_please(name, host, :infinity) |
| 75 | + |
| 76 | + def port_please(~c"expert-manager-" ++ _ = name, host, timeout) do |
| 77 | + if port = System.get_env("EXPERT_PARENT_PORT") do |
| 78 | + {:port, String.to_integer(port), @epmd_dist_version} |
| 79 | + else |
| 80 | + :erl_epmd.port_please(name, host, timeout) |
| 81 | + end |
| 82 | + end |
| 83 | + |
| 84 | + def port_please(~c"expert-project-" ++ _ = name, host, timeout) do |
| 85 | + if port = Forge.NodePortMapper.get_port(List.to_atom(name)) do |
| 86 | + {:port, port, @epmd_dist_version} |
| 87 | + else |
| 88 | + :erl_epmd.port_please(name, host, timeout) |
| 89 | + end |
| 90 | + end |
| 91 | + |
| 92 | + def port_please(name, host, timeout) do |
| 93 | + :erl_epmd.port_please(name, host, timeout) |
| 94 | + end |
| 95 | + |
| 96 | + defdelegate start_link(), to: :erl_epmd |
| 97 | + defdelegate listen_port_please(name, host), to: :erl_epmd |
| 98 | + defdelegate address_please(name, host, family), to: :erl_epmd |
| 99 | + defdelegate names(host_name), to: :erl_epmd |
| 100 | +end |
0 commit comments