Current section
Files
Jump to
Current section
Files
src/hecate_plugin_scheduler.erl
%%%-------------------------------------------------------------------
%%% @doc Cron-like recurring task scheduler for plugins.
%%%
%%% Plugins can schedule periodic tasks that run in their own process.
%%% Tasks are lightweight — each is a spawned process with a timer.
%%%
%%% Usage:
%%% %% Run every 5 minutes
%%% hecate_plugin_scheduler:every(5 * 60 * 1000, fun cleanup_expired/0).
%%%
%%% %% Run every hour with a name (for cancellation)
%%% Ref = hecate_plugin_scheduler:every(3600000, fun sync_remote/0),
%%% hecate_plugin_scheduler:cancel(Ref).
%%% @end
%%%-------------------------------------------------------------------
-module(hecate_plugin_scheduler).
-export([every/2, every/3, cancel/1]).
%% @doc Schedule a function to run every IntervalMs milliseconds.
%% Returns a reference that can be used to cancel.
-spec every(IntervalMs :: pos_integer(), Fun :: fun(() -> term())) ->
reference().
every(IntervalMs, Fun) ->
every(IntervalMs, Fun, #{}).
%% @doc Schedule with options.
%% Options:
%% - initial_delay: milliseconds before first run (default: IntervalMs)
%% - name: atom for logging (default: anonymous)
-spec every(pos_integer(), fun(() -> term()), map()) -> reference().
every(IntervalMs, Fun, Opts) ->
Ref = make_ref(),
InitialDelay = maps:get(initial_delay, Opts, IntervalMs),
Name = maps:get(name, Opts, anonymous),
Pid = spawn_link(fun() -> schedule_loop(Ref, IntervalMs, Fun, Name) end),
erlang:send_after(InitialDelay, Pid, {run, Ref}),
Ref.
%% @doc Cancel a scheduled task.
-spec cancel(reference()) -> ok.
cancel(Ref) ->
put({scheduler_cancel, Ref}, true),
ok.
%%--------------------------------------------------------------------
%% Internal
%%--------------------------------------------------------------------
schedule_loop(Ref, IntervalMs, Fun, Name) ->
receive
{run, Ref} ->
case get({scheduler_cancel, Ref}) of
true ->
ok;
_ ->
try Fun()
catch
Class:Reason:Stack ->
logger:error("[scheduler] Task ~p failed: ~p:~p~n~p",
[Name, Class, Reason, Stack])
end,
erlang:send_after(IntervalMs, self(), {run, Ref}),
schedule_loop(Ref, IntervalMs, Fun, Name)
end
end.