diff --git a/apps/explorer/lib/explorer/chain/cache/address_sum.ex b/apps/explorer/lib/explorer/chain/cache/address_sum.ex index 8e2d67f381..694cb892dd 100644 --- a/apps/explorer/lib/explorer/chain/cache/address_sum.ex +++ b/apps/explorer/lib/explorer/chain/cache/address_sum.ex @@ -17,9 +17,10 @@ defmodule Explorer.Chain.Cache.AddressSum do alias Explorer.Etherscan defp handle_fallback(:sum) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, Decimal.new(0)} end @@ -49,7 +50,7 @@ defmodule Explorer.Chain.Cache.AddressSum do # By setting this as a `callback` an async task will be started each time the # `sum` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :sum}), do: get_async_task() + defp async_task_on_deletion({:delete, _, :sum}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil end diff --git a/apps/explorer/lib/explorer/chain/cache/address_sum_minus_burnt.ex b/apps/explorer/lib/explorer/chain/cache/address_sum_minus_burnt.ex index 6e8dfa9289..c1f637ac74 100644 --- a/apps/explorer/lib/explorer/chain/cache/address_sum_minus_burnt.ex +++ b/apps/explorer/lib/explorer/chain/cache/address_sum_minus_burnt.ex @@ -17,9 +17,10 @@ defmodule Explorer.Chain.Cache.AddressSumMinusBurnt do alias Explorer.Chain.Cache.Helper defp handle_fallback(:sum_minus_burnt) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, Decimal.new(0)} end @@ -56,7 +57,7 @@ defmodule Explorer.Chain.Cache.AddressSumMinusBurnt do # By setting this as a `callback` an async task will be started each time the # `sum_minus_burnt` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :sum_minus_burnt}), do: get_async_task() + defp async_task_on_deletion({:delete, _, :sum_minus_burnt}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil end diff --git a/apps/explorer/lib/explorer/chain/cache/block.ex b/apps/explorer/lib/explorer/chain/cache/block.ex index edb97c43d7..68ea799659 100644 --- a/apps/explorer/lib/explorer/chain/cache/block.ex +++ b/apps/explorer/lib/explorer/chain/cache/block.ex @@ -56,9 +56,10 @@ defmodule Explorer.Chain.Cache.Block do end defp handle_fallback(:count) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, nil} end @@ -95,7 +96,7 @@ defmodule Explorer.Chain.Cache.Block do # By setting this as a `callback` an async task will be started each time the # `count` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :count}), do: get_async_task() + defp async_task_on_deletion({:delete, _, :count}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index 12c615dad3..bc9b590538 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -310,9 +310,10 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do defp fast_time_coefficient, do: Application.get_env(:explorer, __MODULE__)[:fast_time_coefficient] defp handle_fallback(:gas_prices) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, get_old_gas_prices()} end @@ -353,7 +354,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do # `gas_prices` expires (unless there is one already running) defp async_task_on_deletion({:delete, _, :gas_prices}) do set_old_gas_prices(get_gas_prices()) - get_async_task() + safe_get_async_task() end defp async_task_on_deletion(_data), do: nil diff --git a/apps/explorer/lib/explorer/chain/cache/gas_usage.ex b/apps/explorer/lib/explorer/chain/cache/gas_usage.ex index 6a632c97dd..ddff60dd41 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_usage.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_usage.ex @@ -37,9 +37,10 @@ defmodule Explorer.Chain.Cache.GasUsage do end defp handle_fallback(:sum) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, nil} end @@ -73,7 +74,7 @@ defmodule Explorer.Chain.Cache.GasUsage do # By setting this as a `callback` an async task will be started each time the # `sum` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :sum}), do: get_async_task() + defp async_task_on_deletion({:delete, _, :sum}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil diff --git a/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex b/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex index 7dccbe06ca..a746664d61 100644 --- a/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex +++ b/apps/explorer/lib/explorer/chain/cache/pending_block_operation.ex @@ -35,9 +35,10 @@ defmodule Explorer.Chain.Cache.PendingBlockOperation do end defp handle_fallback(:count) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, nil} end @@ -67,7 +68,7 @@ defmodule Explorer.Chain.Cache.PendingBlockOperation do # By setting this as a `callback` an async task will be started each time the # `count` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :count}), do: get_async_task() + defp async_task_on_deletion({:delete, _, :count}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil end diff --git a/apps/explorer/lib/explorer/chain/cache/transaction.ex b/apps/explorer/lib/explorer/chain/cache/transaction.ex index 6ac24f537e..969aa53000 100644 --- a/apps/explorer/lib/explorer/chain/cache/transaction.ex +++ b/apps/explorer/lib/explorer/chain/cache/transaction.ex @@ -36,9 +36,10 @@ defmodule Explorer.Chain.Cache.Transaction do end defp handle_fallback(:count) do - # This will get the task PID if one exists and launch a new task if not + # This will get the task PID if one exists, check if it's running and launch + # a new task if task doesn't exist or it's not running. # See next `handle_fallback` definition - get_async_task() + safe_get_async_task() {:return, nil} end @@ -68,7 +69,7 @@ defmodule Explorer.Chain.Cache.Transaction do # By setting this as a `callback` an async task will be started each time the # `count` expires (unless there is one already running) - defp async_task_on_deletion({:delete, _, :count}), do: get_async_task() + defp async_task_on_deletion({:delete, _, :count}), do: safe_get_async_task() defp async_task_on_deletion(_data), do: nil end diff --git a/apps/explorer/lib/explorer/chain/map_cache.ex b/apps/explorer/lib/explorer/chain/map_cache.ex index 6dfbe6b27e..fbe38db6bb 100644 --- a/apps/explorer/lib/explorer/chain/map_cache.ex +++ b/apps/explorer/lib/explorer/chain/map_cache.ex @@ -201,6 +201,35 @@ defmodule Explorer.Chain.MapCache do end end + defp named_functions(:async_task) do + quote do + def get_async_task, do: get(:async_task) + + @doc """ + Checks if the asynchronous task has stalled (has finished, but still in cache), + and if so, creates a new replacement task to continue the operation. + """ + def safe_get_async_task do + case get_async_task() do + pid when is_pid(pid) -> + if Process.alive?(pid) do + pid + else + set_async_task(nil) + get_async_task() + end + + not_pid -> + not_pid + end + end + + def set_async_task(value), do: set(:async_task, value) + + def update_async_task(value), do: update(:async_task, value) + end + end + # sobelow_skip ["DOS"] defp named_functions(key) do quote do