Packages
postgrex
0.22.1
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/scram/locked_cache.ex
defmodule Postgrex.SCRAM.LockedCache do
@moduledoc false
# SCRAM authentication requires expensive calculations
# that may be repeated across multiple connections.
# This module provides a cache functionality so that
# those are done only once, even if concurrently.
#
# Since those resources can be created dynamically,
# multiple times, they are stored in ETS instead of
# persistent term.
use GenServer
@name __MODULE__
@timeout :infinity
@doc """
Reads the cache key.
"""
def get(key) do
soft_read(key)
end
@doc """
Reads cache key or executes the given function if not
cached yet.
"""
def run(key, fun) do
try do
hard_read(key)
catch
:error, :badarg ->
case GenServer.call(@name, {:lock, key}, @timeout) do
{:uncached, ref} ->
try do
fun.()
catch
kind, reason ->
GenServer.cast(@name, {:uncached, ref})
:erlang.raise(kind, reason, __STACKTRACE__)
else
result ->
write(key, result)
GenServer.cast(@name, {:cached, ref})
result
end
:cached ->
hard_read(key)
end
end
end
defp init(), do: :ets.new(@name, [:public, :set, :named_table, read_concurrency: true])
defp write(key, value), do: :ets.insert(@name, {key, value})
defp hard_read(key), do: :ets.lookup_element(@name, key, 2)
defp soft_read(key) do
try do
:ets.lookup_element(@name, key, 2)
catch
:error, :badarg -> nil
end
end
## Callbacks
@doc false
def start_link(_opts) do
GenServer.start_link(__MODULE__, :ok, name: @name)
end
@impl true
def init(:ok) do
init()
{:ok, %{keys: %{}, ref_to_key: %{}}}
end
@impl true
def handle_call({:lock, key}, from, state) do
case state.keys do
%{^key => {ref, waiting}} ->
{:noreply, put_in(state.keys[key], {ref, [from | waiting]})}
%{} ->
{:noreply, lock(key, from, [], state)}
end
end
@impl true
def handle_cast({:cached, ref}, state) do
Process.demonitor(ref, [:flush])
{key, state} = pop_in(state.ref_to_key[ref])
{{^ref, waiting}, state} = pop_in(state.keys[key])
for from <- waiting, do: GenServer.reply(from, :cached)
{:noreply, state}
end
@impl true
def handle_cast({:uncached, ref}, state) do
Process.demonitor(ref, [:flush])
{:noreply, unlock(ref, state)}
end
@impl true
def handle_info({:DOWN, ref, _, _, _}, state) do
{:noreply, unlock(ref, state)}
end
defp lock(key, {pid, _} = from, waiting, state) do
ref = Process.monitor(pid)
state = put_in(state.keys[key], {ref, waiting})
state = put_in(state.ref_to_key[ref], key)
GenServer.reply(from, {:uncached, ref})
state
end
defp unlock(ref, state) do
{key, state} = pop_in(state.ref_to_key[ref])
{{^ref, waiting}, state} = pop_in(state.keys[key])
case waiting do
[] -> state
[from | waiting] -> lock(key, from, waiting, state)
end
end
end