Current section

Files

Jump to
posthog lib posthog feature_flags called_cache.ex
Raw

lib/posthog/feature_flags/called_cache.ex

defmodule PostHog.FeatureFlags.CalledCache do
@moduledoc false
use GenServer
@max_size 50_000
@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts) do
supervisor_name = Keyword.fetch!(opts, :supervisor_name)
GenServer.start_link(__MODULE__, supervisor_name,
name: PostHog.Registry.via(supervisor_name, __MODULE__)
)
end
@impl GenServer
def init(supervisor_name) do
table =
supervisor_name
|> table_name()
|> :ets.new([
:set,
:public,
:named_table,
read_concurrency: true,
write_concurrency: true
])
{:ok, %{table: table}}
end
@spec first_seen?(PostHog.supervisor_name(), PostHog.distinct_id(), String.t(), any()) ::
boolean()
def first_seen?(supervisor_name, distinct_id, flag_key, value) do
key = {to_string(distinct_id), flag_key, value}
table = table_name(supervisor_name)
case :ets.insert_new(table, {key}) do
true ->
rollover_if_full(supervisor_name, table, key)
true
false ->
false
end
rescue
ArgumentError -> true
catch
:exit, _ -> true
end
@impl GenServer
def handle_call({:rollover, key}, _from, %{table: table} = state) do
if over_max_size?(table) do
# Intentionally flush instead of evicting individual entries to keep
# the hot path simple. Previously seen values may emit again after
# the cache rolls over.
:ets.delete_all_objects(table)
:ets.insert(table, {key})
end
{:reply, :ok, state}
end
defp rollover_if_full(supervisor_name, table, key) do
if over_max_size?(table) do
GenServer.call(PostHog.Registry.via(supervisor_name, __MODULE__), {:rollover, key})
end
end
defp over_max_size?(table) do
case :ets.info(table, :size) do
size when is_integer(size) -> size > @max_size
_ -> false
end
end
defp table_name(supervisor_name), do: Module.concat(supervisor_name, CalledCacheTable)
end