Packages
fnord
0.9.38
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/services/instance.ex
defmodule Services.Instance do
@moduledoc """
Tree-scoped service registry.
Maps a service module to the pid serving the *current process tree*, using
`Services.Globals`' root resolution for scoping. This is the process
counterpart to tree-scoped config: instead of registering a service under a
VM-global atom name (`name: __MODULE__`), a service registers its pid under
the current tree's root. Two process trees (two tests, or two checked-out
`Fnord.Instance`s) each get their own copy of the service, invisible to one
another. In production there is exactly one tree (rooted at the escript's
main process), so behavior is identical to a named singleton.
Registration entries live in the Globals data table, so they are wiped
automatically when the tree's root process dies.
Resolution relies on `:"$ancestors"`, which only proc_lib-spawned processes
(GenServer, Supervisor, Task, Agent) carry. A raw `spawn`ed process, or a
process whose ancestry chain leads to a dead root, cannot resolve any
instance and `fetch!/1` will raise.
"""
@doc """
Register `pid` as the instance of `service` for the current process tree.
Typically called from the service's own `start_link`, in which case the
registration is performed by the *starter* (the owner process or a
supervisor), scoping the service to the starter's tree. A supervisor
restarting the service re-runs registration and overwrites the entry with
the new pid.
"""
@spec register(module(), pid()) :: :ok
def register(service, pid) do
Services.Globals.put_env(:fnord, {:instance_service, service}, pid)
end
@doc """
Return the pid registered for `service` in the current process tree, or nil
when the service is not running in this tree (or the registered pid has
died).
"""
@spec whereis(module()) :: pid() | nil
def whereis(service) do
case Services.Globals.get_override(:fnord, {:instance_service, service}) do
pid when is_pid(pid) -> alive_or_nil(pid)
_ -> nil
end
end
@doc """
Like `whereis/1`, but raises when the service is not running in the current
tree. Services use this to resolve their own instance in their API
functions; a raise here means the calling process's tree never started the
service, or the caller is not parented under any instance root.
"""
@spec fetch!(module()) :: pid()
def fetch!(service) do
case whereis(service) do
nil ->
raise "#{inspect(service)} is not running in this process tree. " <>
"It is tree-scoped: boot it via Fnord.Instance (or start it " <>
"directly), and call it from a descendant of that tree's root."
pid ->
pid
end
end
@doc """
All services registered in the current process tree, sorted by module
name. Dead pids are excluded. Introspection surface for debugging and
roster assertions in tests.
"""
@spec registered() :: [module()]
def registered() do
:fnord
|> Services.Globals.overrides()
|> Enum.flat_map(fn
{{:instance_service, service}, pid} when is_pid(pid) ->
if Process.alive?(pid) do
[service]
else
[]
end
_other ->
[]
end)
|> Enum.sort()
end
@doc """
`GenServer.call/3` against the tree's instance of `service`. Raises (via
`fetch!/1`) when the service is not running in the caller's tree, matching
the crash semantics of a call to an unregistered name.
"""
@spec call(module(), term(), timeout()) :: term()
def call(service, msg, timeout \\ 5000) do
service
|> fetch!()
|> GenServer.call(msg, timeout)
end
@doc """
`GenServer.cast/2` against the tree's instance of `service`. Silently drops
the message when the service is not running in the caller's tree, matching
the fire-and-forget semantics of a cast to an unregistered name.
"""
@spec cast(module(), term()) :: :ok
def cast(service, msg) do
case whereis(service) do
nil -> :ok
pid -> GenServer.cast(pid, msg)
end
end
defp alive_or_nil(pid) do
if Process.alive?(pid) do
pid
else
nil
end
end
end