I have a LiveView web app and every night outside working hours (ideally at 12PM each night) I want to perform a single action (run a piece of code)
I have a GenServer that starts when the Phoenix App starts. The problem is that the phoenix server will be started at different times during the day and I do not know what time frame to set the scheduler.
To solve the problem (using my current code) I can perform the following:
When the user starts the phoenix app, capture the current day as a date and store it in state. Run the Genserver every hour to check if the day has changed. If the day changed run the code and store the new day in state.
Repeat.
This would work fine but the problem is I don't understand GenServers well enough to store and compare values as described. I figure I might need an agent to pass data around but I'm kind of just guessing.
I read this answer at it seems really clean but I do not know how to integrate the module: https://stackoverflow.com/a/32086769/1152980
My code is below and is based on this: https://stackoverflow.com/a/32097971/1152980
defmodule App.Periodically do
use GenServer
def start_link(_opts) do
GenServer.start_link(__MODULE__, DateTime)
end
def init(state) do
schedule_work(state)
{:ok, state}
end
def handle_info(:work, state) do
IO.inspect state.utc_now
IO.inspect "_____________NEW"
IO.inspect DateTime.utc_now
# When phoenix app starts - store current date
# When Genserver runs.....
# run conditional on stored date with current date
# If they are different THEN RUN CODE
# If they are the same - do nothing
IO.inspect state
schedule_work(state)
{:noreply, state}
end
defp schedule_work(state) do
IO.inspect "test"
Process.send_after(self(), :work, 10000 ) # 1 sec for testing will change to 1 hour
end
end
The easiest solution here would probably be to leverage an existing package such as quantum which implements a configurable recurring process that seems like it would fit the use-cases you have described. Just adjust the example
Heartbeatmodule for your own module that you wish to call, e.g.Would translate to calling
MyApp.SomeModule.some_function_name_as_an_atom("positional", "arguments", "to", "the", "given", "function")every day at midnight.If you want to write your own
GenServerfor this, you have to handle a couple edge-cases: how to handle the server restarting multiple times in a day, how to handle the case that it starts EXACTLY at midnight, something with timezones, and probably a couple others that @Aleksei would think about.Adjusting code from an article I wrote, you might wind up with a module something like this:
It accepts configuration parameters to define the
mod,fun, andargsto call periodically. For simplicity, the state could more or less be ignored and you could just hardcode a single function to be called. However, for better usability, you can imagine adding in an option (in thestate) that would dictate how frequently to run the task, but this would require you to refactor the examplems_til_midnight/0function into something more flexible. You can see the trickiest part of this is simply the math to calculate how many milliseconds until midnight, so I've left that verbose.