1
votes

I'm trying to create "Cache warmer" to run ONCE at the application startup, by calling genServer module "Cache"

I've created some code: GenServer "Cache Warmer", which is used to handle a single async call on application startup, configured with [restart: :temporary]. The main idea is return {:stop, :normal, state} after cast, to shut down the process

defmodule TlgmBot.Application do
 ...
     def start(_type, _args) do
     ...
       children = [
       ... some stuff ...
        %{
          id: Services.Cache.CacheWarmer,
          start: {Services.Cache.CacheWarmer, :start_link, [restart: :temporary]},
          type: :supervisor
      }

        %{
          id: Services.Cache.Cache,
          start: {Services.Cache.Cache, :start_link, []},
          type: :supervisor
      },
    end
end
defmodule Services.Cache.CacheWarmer do
 use GenServer

  def start_link(_state \\ []) do
    GenServer.start_link(__MODULE__, [:ok], name: __MODULE__)
  end

  def handle_cast({:warm_up_cache}, state) do
    debug "loading users..."
    load_users()
    debug "done."
    load_mfc()

    {:stop, :normal, state}
  end

  defp load_users() do
    result = RedmineApi.request_rm_users()

    case result do
    {:ok, users} -> Cache.save_users(users)
                    {:ok}
    _            -> {:error}
    end
  end
end

And process "Cache warmer" still running again and again

Please, point me to correct way to complete this task or help me figure out what am i doing wrong here.

Maybe i should add couple of lines in application.start() to call cache module here and forget about it?

2
GenServer.handle_continue/2 is surely way to go, but in general the issue is; why would you supervise something that is by no mean intended to be supervised? Spawn a process, unlinked. It’ll die silently as you wish.Aleksei Matiushkin

2 Answers

4
votes

Since your Cache Warmer doesn't use its state or need to exist once it has performed its duty, I would recommend you instead call a function either when starting your application or internally in a handle_continue in your Cache. This would happen post init so as not to block starting other children.

See: GenServer.handle_continue/2.

4
votes

Besides handle_continue/2, posted by @aleksei-matiushkin, you can also have a Task in your supervision tree after the cache:

MyApp.Cache,
{Task, &MyApp.Cache.warmup/0}

A Task is temporary by default, which means it won't be restarted if it crashes. Note though if the cache process crashes, handle_continue/2 will run again once the process restarts. The task won't run again if the supervision strategy is :one_for_one, but it will for :rest_for_one and :one_for_all.

The code you posted has two issues: the cache warmer is starting before the cache, therefore the cast request won't find the cache server. It is also listing the children as type: :supervisor, that should be done only for supervisors (it won't cause an actual bug here but it may cause problem during shutdown, hot code upgrades, etc).