James Harton
a381ca4b34
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: https://code.harton.nz/james/wayfarer/pulls/7 Co-authored-by: James Harton <james@harton.nz> Co-committed-by: James Harton <james@harton.nz>
61 lines
1.3 KiB
Elixir
61 lines
1.3 KiB
Elixir
defmodule Support.PortTracker do
|
|
@moduledoc """
|
|
Generates port numbers which aren't currently being used by any processes.
|
|
"""
|
|
|
|
use GenServer
|
|
require Logger
|
|
|
|
defmacro __using__(_) do
|
|
quote do
|
|
defp random_port(opts \\ []) do
|
|
unquote(__MODULE__).allocate(opts)
|
|
end
|
|
end
|
|
end
|
|
|
|
@doc false
|
|
@spec start_link(any) :: GenServer.on_start()
|
|
def start_link(arg), do: GenServer.start_link(__MODULE__, arg, name: __MODULE__)
|
|
|
|
@doc false
|
|
@impl true
|
|
def init(_) do
|
|
table = :ets.new(__MODULE__, [:public, :named_table, :set])
|
|
{:ok, table}
|
|
end
|
|
|
|
@doc false
|
|
@impl true
|
|
def handle_cast({:monitor, pid}, table) do
|
|
Process.monitor(pid)
|
|
{:noreply, table}
|
|
end
|
|
|
|
@doc false
|
|
@impl true
|
|
def handle_info({:DOWN, _, :process, pid, _}, table) do
|
|
:ets.match_delete(table, {:_, pid})
|
|
{:noreply, table}
|
|
end
|
|
|
|
@doc """
|
|
Allocate an unused random port between `min_port` and `max_port`.
|
|
"""
|
|
def allocate(opts) do
|
|
port = random_port(opts)
|
|
|
|
if :ets.insert_new(__MODULE__, {port, self()}) do
|
|
GenServer.cast(__MODULE__, {:monitor, self()})
|
|
port
|
|
else
|
|
allocate(opts)
|
|
end
|
|
end
|
|
|
|
defp random_port(opts) do
|
|
max_port = Keyword.get(opts, :max_port, 0xFFFF)
|
|
min_port = Keyword.get(opts, :min_port, 20_000)
|
|
:rand.uniform(max_port - min_port) + min_port
|
|
end
|
|
end
|