Skip to content

Commit ab64607

Browse files
committed
feat: epmdless clustering
1 parent c5ac441 commit ab64607

File tree

23 files changed

+256
-39
lines changed

23 files changed

+256
-39
lines changed

apps/engine/test/test_helper.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
Application.ensure_all_started(:snowflake)
22
Application.ensure_all_started(:refactorex)
3-
{"", 0} = System.cmd("epmd", ~w(-daemon))
3+
44
random_number = :rand.uniform(500)
55

66
with :nonode@nohost <- Node.self() do
77
{:ok, _pid} =
8-
:net_kernel.start(:"testing-#{random_number}@127.0.0.1", %{name_domain: :longnames})
8+
Node.start(:"expert-manager-testing-#{random_number}@127.0.0.1", :longnames)
99
end
1010

1111
Engine.Module.Loader.start_link(nil)

apps/expert/lib/expert/application.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ defmodule Expert.Application do
6565
end
6666

6767
children = [
68+
{Forge.NodePortMapper, []},
6869
document_store_child_spec(),
6970
{DynamicSupervisor, Expert.Project.DynamicSupervisor.options()},
7071
{DynamicSupervisor, name: Expert.DynamicSupervisor},

apps/expert/lib/expert/engine_node.ex

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,49 @@ defmodule Expert.EngineNode do
2828
@dialyzer {:nowarn_function, start: 3}
2929

3030
def start(%__MODULE__{} = state, paths, from) do
31-
this_node = inspect(Node.self())
32-
33-
args = [
34-
"--name",
35-
Project.node_name(state.project),
36-
"--cookie",
37-
state.cookie,
38-
"--no-halt",
39-
"-e",
40-
"Node.connect(#{this_node})"
41-
| path_append_arguments(paths)
42-
]
43-
44-
case Expert.Port.open_elixir(state.project, args: args) do
31+
epmd_module = to_charlist(Forge.EPMD)
32+
33+
case :init.get_argument(:epmd_module) do
34+
{:ok, [[^epmd_module]]} ->
35+
:ok
36+
37+
_ ->
38+
Application.put_env(:kernel, :epmd_module, Forge.EPMD, persistent: true)
39+
40+
# Note: this is a private API
41+
if :net_kernel.epmd_module() != Forge.EPMD do
42+
raise("""
43+
you must set the environment variable ELIXIR_ERL_OPTIONS="-epmd_module #{Forge.EPMD}"
44+
""")
45+
end
46+
end
47+
48+
this_node = to_string(Node.self())
49+
dist_port = Forge.EPMD.dist_port()
50+
51+
args =
52+
[
53+
"--erl",
54+
"-start_epmd false -epmd_module #{Forge.EPMD}",
55+
"--cookie",
56+
state.cookie,
57+
"--no-halt",
58+
"-e",
59+
"""
60+
{:ok, _} = Node.start(:"#{Project.node_name(state.project)}", :longnames)
61+
#{Forge.NodePortMapper}.register()
62+
IO.puts(\"ok\")
63+
"""
64+
| path_append_arguments(paths)
65+
]
66+
67+
env =
68+
[
69+
{"EXPERT_PARENT_NODE", this_node},
70+
{"EXPERT_PARENT_PORT", to_string(dist_port)}
71+
]
72+
73+
case Expert.Port.open_elixir(state.project, args: args, env: env) do
4574
{:error, :no_elixir, message} ->
4675
GenLSP.error(Expert.get_lsp(), message)
4776
Expert.terminate("Failed to find an elixir executable, shutting down", 1)
@@ -64,7 +93,7 @@ defmodule Expert.EngineNode do
6493
end
6594

6695
def on_nodeup(%__MODULE__{} = state, node_name) do
67-
if node_name == Project.node_name(state.project) do
96+
if String.starts_with?(to_string(node_name), to_string(Project.node_name(state.project))) do
6897
{pid, _ref} = state.started_by
6998
Process.monitor(pid)
7099
GenServer.reply(state.started_by, :ok)
@@ -117,7 +146,6 @@ defmodule Expert.EngineNode do
117146
use GenServer
118147

119148
def start(project) do
120-
:ok = ensure_epmd_started()
121149
start_net_kernel(project)
122150

123151
node_name = Project.node_name(project)
@@ -134,23 +162,13 @@ defmodule Expert.EngineNode do
134162

135163
defp start_net_kernel(%Project{} = project) do
136164
manager = Project.manager_node_name(project)
137-
:net_kernel.start(manager, %{name_domain: :longnames})
165+
Node.start(manager, :longnames)
138166
end
139167

140168
defp ensure_apps_started(node) do
141169
:rpc.call(node, Engine, :ensure_apps_started, [])
142170
end
143171

144-
defp ensure_epmd_started do
145-
case System.cmd("epmd", ~w(-daemon)) do
146-
{"", 0} ->
147-
:ok
148-
149-
_ ->
150-
{:error, :epmd_failed}
151-
end
152-
end
153-
154172
if Mix.env() == :test do
155173
# In test environment, Expert depends on the Engine app, so we look for it
156174
# in the expert build path.
@@ -294,7 +312,7 @@ defmodule Expert.EngineNode do
294312

295313
@impl true
296314
def handle_call({:start, paths}, from, %State{} = state) do
297-
:ok = :net_kernel.monitor_nodes(true, node_type: :visible)
315+
:ok = :net_kernel.monitor_nodes(true, node_type: :all)
298316
Process.send_after(self(), :maybe_start_timeout, @start_timeout)
299317

300318
case State.start(state, paths, from) do
@@ -365,7 +383,8 @@ defmodule Expert.EngineNode do
365383
end
366384

367385
@impl true
368-
def handle_info({_port, {:data, _message}}, %State{} = state) do
386+
def handle_info({_port, {:data, message}}, %State{} = state) do
387+
Logger.debug("Node port message: #{to_string(message)}")
369388
{:noreply, state}
370389
end
371390

apps/expert/lib/expert/port.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ defmodule Expert.Port do
106106
opts
107107
end
108108

109-
Port.open({:spawn_executable, launcher}, opts)
109+
Port.open({:spawn_executable, launcher}, [:stderr_to_stdout | opts])
110110
end
111111

112112
@doc """

apps/expert/rel/remote.vm.args.eex

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## Customize flags given to the VM: https://www.erlang.org/doc/man/erl.html
2+
## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here
3+
4+
## Increase number of concurrent ports/sockets
5+
##+Q 65536
6+
7+
## Tweak GC to run more often
8+
##-env ERL_FULLSWEEP_AFTER 10
9+
10+
## Enable deployment without epmd
11+
## (requires changing both vm.args and remote.vm.args)
12+
-epmd_module Elixir.XPForge.EPMD
13+
-start_epmd false

apps/expert/rel/vm.args.eex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Customize flags given to the VM: https://www.erlang.org/doc/man/erl.html
2+
## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here
3+
4+
## Increase number of concurrent ports/sockets
5+
##+Q 65536
6+
7+
## Tweak GC to run more often
8+
##-env ERL_FULLSWEEP_AFTER 10
9+
10+
## Enable deployment without epmd
11+
## (requires changing both vm.args and remote.vm.args)
12+
-start_epmd false -epmd_module Elixir.XPForge.EPMD

apps/expert/test/engine/build_test.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ defmodule Engine.BuildTest do
4242
|> Project.workspace_path()
4343
|> File.rm_rf()
4444

45+
{:ok, _} = start_supervised(Forge.NodePortMapper)
4546
{:ok, _} = start_supervised({EngineSupervisor, project})
4647
{:ok, _, _} = EngineNode.start(project)
4748
EngineApi.register_listener(project, self(), [:all])
@@ -650,6 +651,7 @@ defmodule Engine.BuildTest do
650651

651652
describe ".exs files" do
652653
setup do
654+
start_supervised!({Forge.NodePortMapper, []})
653655
start_supervised!(Engine.Dispatch)
654656
start_supervised!(Engine.ModuleMappings)
655657
start_supervised!(Build.CaptureServer)

apps/expert/test/engine/code_intelligence/definition_test.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ defmodule Expert.Engine.CodeIntelligence.DefinitionTest do
4343
end
4444

4545
setup_all do
46+
{:ok, _} = start_supervised({Forge.NodePortMapper, []})
4647
project = project(:navigations)
4748
start_supervised!({Document.Store, derive: [analysis: &Forge.Ast.analyze/1]})
4849
{:ok, _} = start_supervised({EngineSupervisor, project})

apps/expert/test/engine/engine_test.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule EngineTest do
99
import Forge.Test.Fixtures
1010

1111
def start_project(%Project{} = project) do
12+
start_supervised!({Forge.NodePortMapper, []})
1213
start_supervised!({Expert.EngineSupervisor, project})
1314
assert {:ok, _, _} = EngineNode.start(project)
1415
:ok

apps/expert/test/expert/engine_node_test.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ defmodule Expert.EngineNodeTest do
1010

1111
setup do
1212
project = project()
13+
start_supervised!({Forge.NodePortMapper, []})
1314
start_supervised!({EngineSupervisor, project})
1415
{:ok, %{project: project}}
1516
end

0 commit comments

Comments
 (0)