Packages
postgrex
0.16.4
1.0.0-rc.1
retired
1.0.0-rc.0
retired
0.22.3
0.22.2
0.22.1
0.22.0
0.21.1
0.21.0
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.18.0
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.13
0.15.12
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.13.0-rc.0
0.12.2
0.12.1
0.12.0
0.11.2
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.2
PostgreSQL driver for Elixir
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/postgrex/type_server.ex
defmodule Postgrex.TypeServer do
@moduledoc false
use GenServer, restart: :temporary
defstruct [:types, :connections, :lock, :waiting]
@timeout 60_000
@doc """
Starts a type server.
"""
@spec start_link({module, pid, keyword}) :: GenServer.on_start()
def start_link({module, starter, opts}) do
GenServer.start_link(__MODULE__, {module, starter}, opts)
end
@doc """
Fetches a lock for the given type server.
We attempt to achieve a lock on the type server for updating the entries.
If another process got the lock we wait for it to finish.
"""
@spec fetch(pid) ::
{:lock, reference, Postgrex.Types.state()} | :noproc | :error
def fetch(server) do
try do
GenServer.call(server, :fetch, @timeout)
catch
# module timed out, pretend it did not exist.
:exit, {:normal, _} -> :noproc
:exit, {:noproc, _} -> :noproc
end
end
@doc """
Update the type server using the given reference and configuration.
"""
@spec update(pid, reference, [Postgrex.TypeInfo.t()]) :: :ok
def update(server, ref, [_ | _] = type_infos) do
GenServer.call(server, {:update, ref, type_infos}, @timeout)
end
def update(server, ref, []) do
done(server, ref)
end
@doc """
Unlocks the given reference for a given module if no update.
"""
@spec done(pid, reference) :: :ok
def done(server, ref) do
GenServer.cast(server, {:done, ref})
end
## Callbacks
def init({module, starter}) do
_ = Process.flag(:trap_exit, true)
Process.link(starter)
state = %__MODULE__{
types: Postgrex.Types.new(module),
connections: MapSet.new([starter]),
waiting: :queue.new()
}
{:ok, state}
end
def handle_call(:fetch, from, %{lock: nil} = state) do
lock(state, from)
end
def handle_call(:fetch, from, %{lock: ref} = state) when is_reference(ref) do
wait(state, from)
end
def handle_call({:update, ref, type_infos}, from, %{lock: ref} = state)
when is_reference(ref) do
associate(state, type_infos, from)
end
def handle_cast({:done, ref}, %{lock: ref} = state) when is_reference(ref) do
Process.demonitor(ref, [:flush])
next(state)
end
def handle_info({:DOWN, ref, _, _, _}, %{lock: ref} = state)
when is_reference(ref) do
next(state)
end
def handle_info({:DOWN, ref, _, _, _}, state) do
down(state, ref)
end
def handle_info({:EXIT, pid, _}, state) do
exit(state, pid)
end
def handle_info(:timeout, state) do
{:stop, :normal, state}
end
## Helpers
defp lock(%{connections: connections, types: types} = state, {pid, _}) do
Process.link(pid)
mref = Process.monitor(pid)
state = %{state | lock: mref, connections: MapSet.put(connections, pid)}
{:reply, {:lock, mref, types}, state}
end
defp wait(state, {pid, _} = from) do
%{connections: connections, waiting: waiting} = state
Process.link(pid)
mref = Process.monitor(pid)
state = %{
state
| connections: MapSet.put(connections, pid),
waiting: :queue.in({mref, from}, waiting)
}
{:noreply, state}
end
defp associate(%{types: types, lock: ref} = state, type_infos, from) do
Postgrex.Types.associate_type_infos(type_infos, types)
Process.demonitor(ref, [:flush])
GenServer.reply(from, :go)
next(state)
end
defp next(%{types: types, waiting: waiting} = state) do
case :queue.out(waiting) do
{{:value, {mref, from}}, waiting} ->
GenServer.reply(from, {:lock, mref, types})
{:noreply, %{state | lock: mref, waiting: waiting}}
{:empty, waiting} ->
check_processes(%{state | lock: nil, waiting: waiting})
end
end
defp down(%{waiting: waiting} = state, ref) do
check_processes(%{state | waiting: :queue.filter(fn {mref, _} -> mref != ref end, waiting)})
end
defp exit(%{connections: connections} = state, pid) do
check_processes(%{state | connections: MapSet.delete(connections, pid)})
end
defp check_processes(%{lock: ref} = state) when is_reference(ref) do
{:noreply, state}
end
defp check_processes(%{connections: connections} = state) do
case MapSet.size(connections) do
0 ->
timeout = Application.fetch_env!(:postgrex, :type_server_reap_after)
{:noreply, state, timeout}
_ ->
{:noreply, state}
end
end
end