|
|
@ -1,79 +1,129 @@ |
|
|
|
defmodule Explorer.Chain.BlockCountCache do |
|
|
|
defmodule Explorer.Chain.BlockCountCache do |
|
|
|
@moduledoc """ |
|
|
|
@moduledoc """ |
|
|
|
Cache for count consensus blocks. |
|
|
|
Cache for block count. |
|
|
|
""" |
|
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
require Logger |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use GenServer |
|
|
|
|
|
|
|
|
|
|
|
alias Explorer.Chain |
|
|
|
alias Explorer.Chain |
|
|
|
|
|
|
|
|
|
|
|
@tab :block_count_cache |
|
|
|
|
|
|
|
# 10 minutes |
|
|
|
# 10 minutes |
|
|
|
@cache_period 1_000 * 60 * 10 |
|
|
|
@cache_period 1_000 * 60 * 10 |
|
|
|
@key "count" |
|
|
|
@key "count" |
|
|
|
@opts_key "opts" |
|
|
|
@default_value nil |
|
|
|
|
|
|
|
@name __MODULE__ |
|
|
|
|
|
|
|
|
|
|
|
@spec setup() :: :ok |
|
|
|
def start_link(params) do |
|
|
|
def setup do |
|
|
|
name = params[:name] || @name |
|
|
|
if :ets.whereis(@tab) == :undefined do |
|
|
|
params_with_name = Keyword.put(params, :name, name) |
|
|
|
:ets.new(@tab, [ |
|
|
|
|
|
|
|
:set, |
|
|
|
|
|
|
|
:named_table, |
|
|
|
|
|
|
|
:public, |
|
|
|
|
|
|
|
write_concurrency: true |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setup_opts() |
|
|
|
GenServer.start_link(__MODULE__, params_with_name, name: name) |
|
|
|
update_cache() |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
:ok |
|
|
|
def init(params) do |
|
|
|
|
|
|
|
cache_period = period_from_env_var() || params[:cache_period] || @cache_period |
|
|
|
|
|
|
|
current_value = params[:default_value] || @default_value |
|
|
|
|
|
|
|
name = params[:name] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
init_ets_table(name) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{:ok, {{cache_period, current_value, name}, nil}} |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def count(process_name \\ __MODULE__) do |
|
|
|
|
|
|
|
GenServer.call(process_name, :count) |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
def count do |
|
|
|
def handle_call(:count, _, {{cache_period, default_value, name}, task}) do |
|
|
|
initial_cache = {_count, old_current_time} = cached_values() |
|
|
|
{count, task} = |
|
|
|
|
|
|
|
case cached_values(name) do |
|
|
|
|
|
|
|
nil -> |
|
|
|
|
|
|
|
{default_value, update_cache(task, name)} |
|
|
|
|
|
|
|
|
|
|
|
{count, _current_time} = |
|
|
|
{cached_value, timestamp} -> |
|
|
|
if current_time() - old_current_time > cache_period() do |
|
|
|
task = |
|
|
|
update_cache() |
|
|
|
if current_time() - timestamp > cache_period do |
|
|
|
|
|
|
|
update_cache(task, name) |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
cached_values() |
|
|
|
{cached_value, task} |
|
|
|
else |
|
|
|
|
|
|
|
initial_cache |
|
|
|
|
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
count |
|
|
|
{:reply, count, {{cache_period, default_value, name}, task}} |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_cache(nil, name) do |
|
|
|
|
|
|
|
async_update_cache(name) |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def update_cache(task, _) do |
|
|
|
|
|
|
|
task |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
defp update_cache do |
|
|
|
def handle_cast({:update_cache, value}, {{cache_period, default_value, name}, _}) do |
|
|
|
current_time = current_time() |
|
|
|
current_time = current_time() |
|
|
|
count = count_from_db() |
|
|
|
tuple = {value, current_time} |
|
|
|
tuple = {count, current_time} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:ets.insert(@tab, {@key, tuple}) |
|
|
|
table_name = table_name(name) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:ets.insert(table_name, {@key, tuple}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{:noreply, {{cache_period, default_value, name}, nil}} |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
defp setup_opts do |
|
|
|
def handle_info({:DOWN, _, _, _, _}, {{cache_period, default_value, name}, _}) do |
|
|
|
cache_period = Application.get_env(:explorer, __MODULE__)[:ttl] || @cache_period |
|
|
|
{:noreply, {{cache_period, default_value, name}, nil}} |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
:ets.insert(@tab, {@opts_key, cache_period}) |
|
|
|
def handle_info(_, {{cache_period, default_value, name}, _}) do |
|
|
|
|
|
|
|
{:noreply, {{cache_period, default_value, name}, nil}} |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
defp cached_values do |
|
|
|
# sobelow_skip ["DOS"] |
|
|
|
[{_, cached_values}] = :ets.lookup(@tab, @key) |
|
|
|
defp table_name(name) do |
|
|
|
|
|
|
|
name |
|
|
|
|
|
|
|
|> Atom.to_string() |
|
|
|
|
|
|
|
|> Macro.underscore() |
|
|
|
|
|
|
|
|> String.to_atom() |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
cached_values |
|
|
|
def async_update_cache(name) do |
|
|
|
|
|
|
|
Task.async(fn -> |
|
|
|
|
|
|
|
try do |
|
|
|
|
|
|
|
result = Chain.fetch_count_consensus_block() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GenServer.cast(name, {:update_cache, result}) |
|
|
|
|
|
|
|
rescue |
|
|
|
|
|
|
|
e -> |
|
|
|
|
|
|
|
Logger.debug([ |
|
|
|
|
|
|
|
"Coudn't update block count test #{inspect(e)}" |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
end) |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
defp cache_period do |
|
|
|
defp init_ets_table(name) do |
|
|
|
[{_, cache_period}] = :ets.lookup(@tab, @opts_key) |
|
|
|
table_name = table_name(name) |
|
|
|
|
|
|
|
|
|
|
|
cache_period |
|
|
|
if :ets.whereis(table_name) == :undefined do |
|
|
|
|
|
|
|
:ets.new(table_name, [ |
|
|
|
|
|
|
|
:set, |
|
|
|
|
|
|
|
:named_table, |
|
|
|
|
|
|
|
:public, |
|
|
|
|
|
|
|
write_concurrency: true |
|
|
|
|
|
|
|
]) |
|
|
|
|
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
defp count_from_db do |
|
|
|
defp cached_values(name) do |
|
|
|
Chain.fetch_count_consensus_block() |
|
|
|
table_name = table_name(name) |
|
|
|
rescue |
|
|
|
|
|
|
|
_e -> |
|
|
|
case :ets.lookup(table_name, @key) do |
|
|
|
0 |
|
|
|
[{_, cached_values}] -> cached_values |
|
|
|
|
|
|
|
_ -> nil |
|
|
|
|
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
defp current_time do |
|
|
|
defp current_time do |
|
|
@ -81,4 +131,17 @@ defmodule Explorer.Chain.BlockCountCache do |
|
|
|
|
|
|
|
|
|
|
|
DateTime.to_unix(utc_now, :millisecond) |
|
|
|
DateTime.to_unix(utc_now, :millisecond) |
|
|
|
end |
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
defp period_from_env_var do |
|
|
|
|
|
|
|
case System.get_env("BLOCK_COUNT_CACHE_PERIOD") do |
|
|
|
|
|
|
|
value when is_binary(value) -> |
|
|
|
|
|
|
|
case Integer.parse(value) do |
|
|
|
|
|
|
|
{integer, ""} -> integer * 1_000 |
|
|
|
|
|
|
|
_ -> nil |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_ -> |
|
|
|
|
|
|
|
nil |
|
|
|
|
|
|
|
end |
|
|
|
|
|
|
|
end |
|
|
|
end |
|
|
|
end |
|
|
|