Current section
Files
Jump to
Current section
Files
src/plugins/adk_plugin_runtime_sup.erl
%% @doc Dynamic supervisor for stateful plugin instances.
%%
%% The application starts a registered instance for the convenience APIs.
%% start_link/0 remains available for isolated owners and tests that need a
%% private runtime.
-module(adk_plugin_runtime_sup).
-behaviour(supervisor).
-export([child_spec/0, start_link/0, start_link_registered/0,
start_instance/1, start_instance/2,
stop_instance/1, stop_instance/2]).
-export([init/1]).
-define(SERVER, ?MODULE).
-spec child_spec() -> supervisor:child_spec().
child_spec() ->
#{id => ?MODULE,
start => {?MODULE, start_link_registered, []},
restart => permanent,
shutdown => infinity,
type => supervisor,
modules => [?MODULE]}.
-spec start_link() -> supervisor:startlink_ret().
start_link() -> supervisor:start_link(?MODULE, []).
-spec start_link_registered() -> supervisor:startlink_ret().
start_link_registered() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
-spec start_instance(map()) -> {ok, pid()} | {error, term()}.
start_instance(Spec) -> start_instance(?SERVER, Spec).
-spec start_instance(supervisor:sup_ref(), map()) ->
{ok, pid()} | {error, term()}.
start_instance(Supervisor, Spec) when is_map(Spec) ->
%% An instance is addressed by its PID by both the stateful adapter and the
%% caller. Restarting it behind that identity would silently reset policy
%% state while leaving every retained PID stale. Instances are therefore
%% explicitly temporary: failure makes the identity unavailable and the
%% owner must start and distribute a new instance deliberately.
ChildSpec = #{id => make_ref(),
start => {adk_plugin_instance, start_link, [Spec]},
restart => temporary,
shutdown => 5000,
type => worker,
modules => [adk_plugin_instance]},
case supervisor:start_child(Supervisor, ChildSpec) of
{error, {Reason, Child}}
when is_tuple(Child), tuple_size(Child) > 0,
element(1, Child) =:= child ->
%% OTP includes the complete child start term in this error. That
%% term contains plugin config and must not escape this boundary.
{error, Reason};
Result -> Result
end;
start_instance(_Supervisor, _Spec) -> {error, invalid_plugin_instance_spec}.
-spec stop_instance(pid()) -> ok | {error, term()}.
stop_instance(Pid) -> stop_instance(?SERVER, Pid).
-spec stop_instance(supervisor:sup_ref(), pid()) -> ok | {error, term()}.
stop_instance(Supervisor, Pid) when is_pid(Pid) ->
case [Id || {Id, Child, _Type, _Modules} <-
supervisor:which_children(Supervisor),
Child =:= Pid] of
[Id] ->
case supervisor:terminate_child(Supervisor, Id) of
ok ->
%% Temporary child specs are deleted atomically by OTP
%% during termination.
case supervisor:delete_child(Supervisor, Id) of
ok -> ok;
{error, not_found} -> ok;
{error, _} = DeleteError -> DeleteError
end;
{error, _} = Error -> Error
end;
[] -> {error, not_found}
end;
stop_instance(_Supervisor, _Pid) -> {error, invalid_plugin_instance}.
init([]) ->
{ok, {#{strategy => one_for_one,
intensity => 5,
period => 10}, []}}.