62 lines
1.3 KiB
Elixir
62 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
|