axelson
Is it possible to start a Phoenix Server manually?
I know that you can tell the phoenix server to start manually? I know you can start the server automatically with mix phx.start or by setting server: true in your Endpoint configuration, but I don’t see a way to start it manually after the Endpoint is already started.
My use-case is that if a given port is already taken, I want to start on the next available port. I suppose one workaround is to set server: true but then don’t start the endpoint in the initial supervision tree and use a DynamicSupervisor or similar to start the endpoint manually. Tried this and it doesn’t work
If I change the port than the server doesn’t start. Is this just unsupported in Phoenix?
Marked As Solved
axelson
Okay, here is what I ended up with:
Instead of adding MyApp.Endpoint directly to my supervision tree I am adding this LsppWeb.PhoenixPortSupervisor that uses DynamicSupervisor to start up my Phoenix Endpoint, incrementing the port if it fails to startup initially.
defmodule LsppWeb.PhoenixPortSupervisor do
@moduledoc """
Starts up Phoenix, but controls the port with application configuration,
increments the port until Phoenix starts up successfully.
"""
use GenServer
def start_link(_, name \\ __MODULE__) do
GenServer.start_link(__MODULE__, nil, name: name)
end
def init(_) do
initial_port = initial_port()
start_phoenix(initial_port, initial_port)
end
defp start_phoenix(initial_port, port) when port - initial_port < 15 do
set_port(port)
DynamicSupervisor.start_child(LsppWeb.DynamicSupervisor, LsppWebWeb.Endpoint)
|> interpret_results()
|> case do
{:ok, _} -> {:ok, []}
{:error, :eaddrinuse} -> start_phoenix(initial_port, port + 1)
end
end
defp start_phoenix(_, _) do
{:error, :ports_exhausted}
end
def get_port, do: Application.get_env(:lspp_web, :phoenix_port)
defp set_port(port) do
Application.put_env(:lspp_web, :phoenix_port, port)
end
defp interpret_results({:ok, pid}), do: {:ok, pid}
defp interpret_results({:error, error}) do
case error do
{:shutdown,
{:failed_to_start_child, {:ranch_listener_sup, LsppWebWeb.Endpoint.HTTP},
{:shutdown,
{:failed_to_start_child, :ranch_acceptors_sup,
{:listen_error, LsppWebWeb.Endpoint.HTTP, :eaddrinuse}}}}} ->
{:error, :eaddrinuse}
end
end
defp initial_port do
cond do
port = System.get_env("PORT") ->
String.to_integer(port)
config = Application.get_env(:lspp_web, LsppWebWeb.Endpoint) ->
get_in(config, [:http, :port])
true ->
raise "Port not set"
end
end
end
Then in LsppWeb.Endpoint I define an init/2 callback:
def init(supervisor, config) do
port = LsppWeb.PhoenixPortSupervisor.get_port()
config = put_in(config[:http], [:inet6, {:port, port}])
{:ok, config}
end
Any feedback or critique is welcome. The ugliest part of the code right now is how I shuttle the new port through the application environment. I suppose since LsppWeb.PhoenixPortSupervisor is a GenServer maybe that could be handled with a GenServer.call, but then I would need another GenServer/process since currently LsppWeb.PhoenixPortSupervisor doesn’t finish it’s startup until my LsppWeb.Endpoint has finished.
Also Liked
axelson
It looks like I’ll have to use the init/2 callback on an Endpoint: Phoenix.Endpoint — Phoenix v1.8.8
Although I’m not sure how I’ll handle the restart and increment logic with that yet
idi527
You can probably also use :gen_tcp.listen/2 to check if the port is taken, and only after that start the phoenix app. It would probably simplify the logic a bit (no need for dynamic supervisors, just a function that runs before the application starts).
def find_port(port) do
case :gen_tcp.listen(port, []) do
{:ok, socket} ->
:gen_tcp.close(socket)
port
{:error, :eaddrinuse} ->
find_port(port + 1)
end
end
idi527
![]()
Might be not what you want, but just in case, you can try setting the port to 0 in your config, then cowboy would ask ranch would ask gen tcp would ask OS for a random available port (gen_tcp — OTP 29.0.2 (kernel 11.0.2)).
If Port == 0, the underlying OS assigns an available port number, useinet:port/1 to retrieve it.
You can then fetch the port by using something like :ranch.get_port(YourAppWeb.Endpoint.HTTP). If you are not sure what module name plug decided to use for you cowboy server, try runing :ets.i(:ranch_server) in iex.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








