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/once.ex
defmodule Services.Once do
@moduledoc """
This module provides a mechanism to perform actions only once, using a unique
key provided by the caller to determine whether the action has already been
performed this session.
The agent is tree-scoped rather than VM-global: `start_link/0` starts an
unnamed agent and registers it for the current process tree via
`Services.Instance`. Each instance root (a test, a `Fnord.Instance`
checkout, or the escript's main process in production) gets its own notion
of "once", so seen-keys cannot leak between trees sharing a BEAM.
"""
use Agent
@doc """
Starts the agent that keeps track of seen keys and registers it as the
current process tree's instance.
"""
def start_link() do
with {:ok, pid} <- Agent.start_link(fn -> Map.new() end) do
Services.Instance.register(__MODULE__, pid)
{:ok, pid}
end
end
@doc """
True when a Services.Once instance is running in the current process tree.
Callers that may be exercised outside a live session (without the service
roster running) use this to no-op instead of crashing.
"""
@spec running?() :: boolean()
def running?() do
Services.Instance.whereis(__MODULE__) != nil
end
@doc """
Checks if a key has been seen before. If the key has not been seen, it
returns `{:error, :not_seen}`. If the key has been seen, it returns `{:ok,
value}` where `value` is the value associated with the key, or `true` if no
value was specified.
"""
def get(key) do
Agent.get(instance(), fn seen ->
with {:ok, value} <- Map.fetch(seen, key) do
{:ok, value}
else
_ -> {:error, :not_seen}
end
end)
end
@doc """
Marks a key as seen. If the key has not been seen before, it returns `true`
and updates the internal state. If the key has already been seen, it returns
`false` without updating the state.
"""
def set(key, value \\ true) do
Agent.get_and_update(instance(), fn seen ->
if Map.has_key?(seen, key) do
{false, seen}
else
{true, Map.put(seen, key, value)}
end
end)
end
@doc """
Emits a warning (using `UI.warn/1`) if the message has not yet been emitted
during this session.
"""
def warn(msg) do
if set(msg) do
UI.warn(msg)
end
:ok
end
@doc """
Run the given zero-arity function only once per unique key.
Subsequent calls with the same key will be ignored.
"""
@spec run(term(), (-> any())) :: any() | :ignore
def run(key, fun) when is_function(fun, 0) do
case set(key) do
true -> fun.()
false -> :ignore
end
end
defp instance() do
Services.Instance.fetch!(__MODULE__)
end
end