Crashes in Periodic after a code reload

I am using https://hexdocs.pm/parent/Periodic.html#content for periodic jobs.

I have created a file with such functions as

defmodule EvercamMedia.Weekly do
  def child_spec([args]) do
    Periodic.child_spec(
      id: "#{:rand.uniform()}",
      every: :timer.minutes(1),
      when: fn ->
        match?("Sunday", DateTime.utc_now() |> Calendar.strftime("%A")) &&
          match?(%Time{hour: 0, minute: 0}, Time.utc_now()) &&
          Application.get_env(:evercam_media, :start_periodic_jobs)
      end,
      run: args
    )
  end
end

defmodule EvercamMedia.Daily do
  def child_spec([args]) do
    Periodic.child_spec(
      id: "#{:rand.uniform()}",
      every: :timer.minutes(1),
      when: fn ->
        match?(%Time{hour: 0, minute: 0}, Time.utc_now()) &&
          Application.get_env(:evercam_media, :start_periodic_jobs)
      end,
      run: args
    )
  end
end

defmodule EvercamMedia.Hourly do
  def child_spec([args, hour]) do
    Periodic.child_spec(
      id: "#{:rand.uniform()}",
      every: :timer.hours(hour),
      when: fn -> Application.get_env(:evercam_media, :start_periodic_jobs) end,
      run: args
    )
  end
end

defmodule EvercamMedia.ByXMinutes do
  def child_spec([args, minutes]) do
    Periodic.child_spec(
      id: "#{:rand.uniform()}",
      every: :timer.minutes(minutes),
      when: fn -> Application.get_env(:evercam_media, :start_periodic_jobs) end,
      run: args
    )
  end
end

and I am calling them in my lib/evercam_media.ex as

defmodule EvercamMedia do
  use Application
  require Logger
  alias EvercamMedia.ProductAnalytics.Views

  # See http://elixir-lang.org/docs/stable/elixir/Application.html
  # for more information on OTP Applications
  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    children = [
      {Evercam.Repo, []},
      {Evercam.SnapshotRepo, []},
      {Evercam.AnalyticsRepo, []},
      {Evercam.ProductAnalyticsRepo, []},
      EvercamMediaWeb.Telemetry,
      {Phoenix.PubSub, name: EvercamMedia.PubSub},
      {EvercamMedia.Weekly, [&EvercamMedia.Raid.smart_notifications/0]},
      {EvercamMedia.Daily, [&EvercamMedia.Util.kill_all_ffmpegs/0]},
      {EvercamMedia.Daily, [&EvercamMedia.Raid.raid_notifications/0]},
      {EvercamMedia.Daily, [&EvercamMedia.CollectBatteryReadings.delete_battery_readings/0]},
      {EvercamMedia.Daily,
       [fn -> Views.refresh("events_daily") end]},
      {EvercamMedia.Daily,
       [fn -> Views.refresh("events_weekly") end]},
      {EvercamMedia.Daily,
       [fn -> Views.refresh("events_monthly") end]},
      {EvercamMedia.Hourly,
       [fn -> Views.refresh("events_hourly") end, 1]},
      {EvercamMedia.Hourly, [&EvercamMedia.ShareRequestReminder.check_share_requests/0, 1]},
      {EvercamMedia.Hourly,
       [&EvercamMedia.OfflinePeriodicReminder.offline_cameras_reminder/0, 1]},
      {EvercamMedia.ByXMinutes, [fn -> EvercamMedia.Util.check_camera_streams(0) end, 1]},
      {EvercamMedia.ByXMinutes, [fn -> EvercamMedia.Util.check_camera_streams(30_000) end, 1]},
      {EvercamMedia.ByXMinutes, [&EvercamMedia.CollectBatteryReadings.get_all_batteries/0, 5]},
      :hackney_pool.child_spec(:snapshot_pool, timeout: 50_000, max_connections: 10_000),
      :hackney_pool.child_spec(:seaweedfs_upload_pool, timeout: 50_000, max_connections: 10_000),
      :hackney_pool.child_spec(:seaweedfs_download_pool, timeout: 50_000, max_connections: 10_000)
    ]
  .......
end

everything works fine but when I do a hot code upgrade,

I am getting this crash error

** (BadFunctionError) function #Function<0.75327981/0 in EvercamMedia.Weekly> is invalid, likely because it points to an old version of the code
(parent 0.11.2) lib/periodic.ex:399: Periodic.job_guard_satisfied?/1
(parent 0.11.2) lib/periodic.ex:394: Periodic.handle_tick/2
(parent 0.11.2) lib/periodic.ex:344: Periodic.handle_info/2
(stdlib 3.13) gen_server.erl:680: :gen_server.try_dispatch/4
(stdlib 3.13) gen_server.erl:756: :gen_server.handle_msg/6
(stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3

How I can fix this and what I am doing wrong in calling those functions?

This boils down to the same problem as described in Agent docs. The fix would be to provide an MFA (module-function-arguments triplet) instead of an anonymous function.

For example, suppose we have the following periodic childspec:

{
  Periodic,
  run: fn -> IO.puts("hello") end,
  ...
}

The same job can be expressed via MFA as:

{
  Periodic,
  run: {IO, :puts, ["hello"]},
  ...
}

The {IO, :puts, ["hello"]} is an MFA triplet, where the first element is the module name, the second element is the function name, and the third element is the list of arguments passed to the function. Note that this will only work if the function is public (i.e. exported).

So in your particular case you’d need to do something like:

defmodule EvercamMedia.Weekly do
  def child_spec(run) do
    Periodic.child_spec(
      run: run,
      ...
    )
  end

  ...
end

And then when starting the periodic job scheduler provide the following spec:

{EvercamMedia.Weekly, {EvercamMedia.Raid, :smart_notifications, []}}

As an aside, instead of id: "#{:rand.uniform()}", I propose passing id: System.unique_integer() or id: make_ref().

1 Like