Packages
appsignal
1.10.1
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.0
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.3
2.15.2
2.15.1
2.15.0
2.14.1
2.14.0
2.13.4
2.13.3
2.13.2
2.13.1
2.13.0
2.12.3
2.12.2
2.12.1
2.12.0
2.11.0
2.10.2
2.10.1
2.10.0
2.9.2
2.9.1
2.9.0
2.8.3
2.8.2
2.8.1
2.8.0
2.7.13
2.7.12
2.7.11
2.7.10
2.7.9
2.7.8
2.7.7
2.7.6
2.7.5
2.7.4
2.7.3
2.7.2
2.7.1
2.7.0
2.7.0-beta.1
2.6.1
2.6.0
2.5.3
2.5.2
2.5.1
2.5.0
2.4.3
2.4.2
2.4.1
2.4.0
2.3.1
2.3.0
2.2.19
2.2.18
2.2.17
2.2.16
2.2.15
2.2.14
2.2.13
2.2.12
retired
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.1.15
2.1.14
2.1.13
2.1.12
2.1.11
2.1.10
2.1.9
2.1.8
2.1.7
2.1.7-beta.1
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.0.8
2.0.7
2.0.6
2.0.6-beta.1
2.0.5
retired
2.0.4
retired
2.0.3
retired
2.0.2
retired
2.0.1
retired
2.0.0
retired
2.0.0-beta.11
2.0.0-beta.10
2.0.0-beta.9
2.0.0-beta.8
2.0.0-beta.7
2.0.0-beta.6
2.0.0-beta.5
2.0.0-beta.4
2.0.0-beta.3
2.0.0-beta.2
2.0.0-beta.1
2.0.0-alpha.7
2.0.0-alpha.6
2.0.0-alpha.5
2.0.0-alpha.4
2.0.0-alpha.3
2.0.0-alpha.2
2.0.0-alpha.1
1.13.5
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.13.0-beta.2
1.13.0-beta.1
1.12.1
1.12.0
1.12.0-beta.3
1.12.0-beta.2
1.12.0-beta.1
1.11.8
1.11.7
1.11.6
1.11.5
1.11.4
1.11.3
1.11.2
1.11.1
1.11.0
1.11.0-beta.1
1.10.13
1.10.12
1.10.11
1.10.10
1.10.9
1.10.8
1.10.7
1.10.6
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.4
1.9.3
1.9.2
1.9.1
1.9.1-beta.1
1.9.0
1.9.0-beta.1
1.9.0-alpha.1
1.8.2
1.8.1
1.8.1-beta.2
1.8.1-beta.1
1.8.0
1.7.2
1.7.1
1.7.0
1.7.0-beta.2
1.7.0-beta.1
1.7.0-alpha.4
1.7.0-alpha.3
1.7.0-alpha.2
1.7.0-alpha.1
1.6.7
1.6.6
1.6.6-beta.2
1.6.6-beta.1
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.6.0-beta.1
1.6.0-alpha.1
1.5.1
1.5.0
1.5.0-beta.2
1.5.0-beta.1
1.4.11-beta.1
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.4.0-alpha.1
1.3.6
1.3.6-beta.1
1.3.5
1.3.4
1.3.4-beta.1
1.3.3
1.3.2
1.3.1
1.3.0
1.3.0-beta.3
1.3.0-beta.2
1.3.0-beta.1
1.2.3
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
1.0.0-rc.1
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.2
0.9.1
0.9.0
0.8.0
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.0
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
Collects error and performance data from your Elixir applications and sends it to AppSignal
Current section
Files
Jump to
Current section
Files
lib/appsignal/transaction/registry.ex
defmodule Appsignal.TransactionRegistry do
@moduledoc """
Internal module which keeps a registry of the transaction handles
linked to their originating process.
This is used on various places to link a calling process to its transaction.
For instance, the `Appsignal.ErrorHandler` module uses it to be able to
complete the transaction in case the originating process crashed.
The transactions are stored in an ETS table (with
`{:write_concurrency, true}`, so no bottleneck is created); and the
originating process is monitored to clean up the ETS table when the
process has finished.
"""
use GenServer
require Logger
@table :"$appsignal_transaction_registry"
alias Appsignal.Transaction
@spec start_link :: {:ok, pid}
def start_link do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
@doc """
Register the current process as the owner of the given transaction.
"""
@spec register(Transaction.t()) :: :ok
def register(transaction) do
pid = self()
if registry_alive?() do
monitor_reference = GenServer.call(__MODULE__, {:monitor, pid})
true = :ets.insert(@table, {pid, transaction, monitor_reference})
:ok
else
Logger.debug("AppSignal was not started, skipping transaction registration.")
nil
end
end
@doc """
Given a process ID, return its associated transaction.
"""
@spec lookup(pid) :: Transaction.t() | nil
def lookup(pid) do
case registry_alive?() && :ets.lookup(@table, pid) do
[{^pid, %Transaction{} = transaction, _}] ->
transaction
[{^pid, %Transaction{} = transaction}] ->
transaction
false ->
Logger.debug("AppSignal was not started, skipping transaction lookup.")
nil
_ ->
nil
end
end
@spec lookup(pid, boolean) :: Transaction.t() | nil | :removed
@doc false
def lookup(pid, return_removed) do
IO.warn(
"Appsignal.TransactionRegistry.lookup/2 is deprecated. Use Appsignal.TransactionRegistry.lookup/1 instead"
)
case registry_alive?() && :ets.lookup(@table, pid) do
[{^pid, :removed}] ->
case return_removed do
false -> nil
true -> :removed
end
[{^pid, transaction, _}] ->
transaction
[{^pid, transaction}] ->
transaction
false ->
Logger.debug("AppSignal was not started, skipping transaction lookup.")
nil
[] ->
nil
end
end
@doc """
Unregister the current process as the owner of the given transaction.
"""
@spec remove_transaction(Transaction.t()) :: :ok | {:error, :not_found} | {:error, :no_registry}
def remove_transaction(%Transaction{} = transaction) do
if registry_alive?() do
GenServer.cast(__MODULE__, {:demonitor, transaction})
GenServer.call(__MODULE__, {:remove, transaction})
else
{:error, :no_registry}
end
end
@doc """
Ignore a process in the error handler.
"""
@spec ignore(pid()) :: :ok
def ignore(pid) do
if registry_alive?() do
:ets.insert(@table, {pid, :ignore})
:ok
else
{:error, :no_registry}
end
end
@doc """
Check if a progress is ignored.
"""
@spec ignored?(pid()) :: boolean()
def ignored?(pid) do
case registry_alive?() && :ets.lookup(@table, pid) do
[{^pid, :ignore}] -> true
_ -> false
end
end
defmodule State do
@moduledoc false
defstruct table: nil
end
def init([]) do
table =
:ets.new(@table, [:set, :named_table, {:keypos, 1}, :public, {:write_concurrency, true}])
{:ok, %State{table: table}}
end
def handle_call({:remove, transaction}, _from, state) do
reply =
case pids_and_monitor_references(transaction) do
[[_pid, _reference] | _] = pids_and_refs ->
delete(pids_and_refs)
[[_pid] | _] = pids ->
delete(pids)
[] ->
{:error, :not_found}
end
{:reply, reply, state}
end
def handle_call({:monitor, pid}, _from, state) do
monitor_reference = Process.monitor(pid)
{:reply, monitor_reference, state}
end
def handle_cast({:demonitor, %Transaction{} = transaction}, state) do
transaction
|> pids_and_monitor_references()
|> demonitor
{:noreply, state}
end
def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do
# we give the error handler some time to process the error report
Process.send_after(self(), {:delete, pid}, 5000)
{:noreply, state}
end
def handle_info({:delete, pid}, state) do
:ets.delete(@table, pid)
{:noreply, state}
end
def handle_info(_msg, state) do
{:noreply, state}
end
defp delete([[pid, _] | tail]) do
:ets.delete(@table, pid)
delete(tail)
end
defp delete([[pid] | tail]) do
:ets.delete(@table, pid)
delete(tail)
end
defp delete([]), do: :ok
defp demonitor([[_, reference] | tail]) do
Process.demonitor(reference)
demonitor(tail)
end
defp demonitor([_ | tail]), do: demonitor(tail)
defp demonitor([]), do: :ok
defp registry_alive? do
pid = Process.whereis(__MODULE__)
!is_nil(pid) && Process.alive?(pid)
end
defp pids_and_monitor_references(transaction) do
:ets.match(@table, {:"$1", transaction, :"$2"}) ++ :ets.match(@table, {:"$1", transaction})
end
end