blob: 477cbe79eee99b9326df58e0f93ccff6b3620de2 [file] [log] [blame]
% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
% http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.
-module(couch_epi_sup).
%% --------------------
%% Important assumption
%% ====================
%% Keeper and codechange_monitor childspecs rely on undocumented behaviour.
%% According to supervisor docs:
%% ...if the child process is a supervisor, gen_server, or gen_fsm, this
%% should be a list with one element [Module].
%% However it is perfectly fine to have more than one module in the list.
%% Modules property is used to determine if process is suspendable.
%% Only suspendable processes are hot code upgraded, others are killed.
%% The check looks like `lists:member(Module, Modules)`
%% The assumption is that it is indeed underdocumented fact and not
%% an implementation detail.
-behaviour(supervisor).
-include("couch_epi.hrl").
%% API
-export([start_link/0]).
-export([plugin_childspecs/2]).
%% Supervisor callbacks
-export([init/1]).
%% For testing
-export([
plugin_childspecs/3
]).
%% Helper macro for declaring children of supervisor
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
%% ===================================================================
%% API functions
%% ===================================================================
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
plugin_childspecs(Plugin, Children) ->
Plugins = application:get_env(couch_epi, plugins, []),
plugin_childspecs(Plugin, Plugins, Children).
%% ===================================================================
%% Supervisor callbacks
%% ===================================================================
init([]) ->
{ok, { {one_for_one, 5, 10}, keepers()} }.
%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------
keepers() ->
Plugins = application:get_env(couch_epi, plugins, []),
Definitions = couch_epi_plugin:grouped_definitions(Plugins),
Children = keeper_childspecs(Definitions),
remove_duplicates(Children).
plugin_childspecs(Plugin, Plugins, Children) ->
Definitions = couch_epi_plugin:grouped_definitions([Plugin]),
ExtraChildren = couch_epi_plugin:plugin_processes(Plugin, Plugins),
merge(ExtraChildren, Children) ++ childspecs(Definitions).
childspecs(Definitions) ->
lists:map(fun({{Kind, Key}, Defs}) ->
CodeGen = couch_epi_plugin:codegen(Kind),
Handle = CodeGen:get_handle(Key),
Modules = lists:append([modules(Spec) || {_App, Spec} <- Defs]),
Name = service_name(Key) ++ "|" ++ atom_to_list(Kind),
code_monitor(Name, [Handle], [Handle|Modules])
end, Definitions).
%% ------------------------------------------------------------------
%% Helper Function Definitions
%% ------------------------------------------------------------------
remove_duplicates(Definitions) ->
lists:ukeysort(1, Definitions).
keeper_childspecs(Definitions) ->
lists:map(fun({{Kind, Key}, _Specs}) ->
Name = service_name(Key) ++ "|keeper",
CodeGen = couch_epi_plugin:codegen(Kind),
Handle = CodeGen:get_handle(Key),
keeper(Name, [provider_kind(Kind), Key, CodeGen], [Handle])
end, Definitions).
keeper(Name, Args, Modules) ->
{"couch_epi|" ++ Name, {couch_epi_module_keeper, start_link,
Args}, permanent, 5000, worker, Modules}.
code_monitor(Name, Args, Modules0) ->
Modules = [couch_epi_codechange_monitor | Modules0],
{"couch_epi_codechange_monitor|" ++ Name,
{couch_epi_codechange_monitor, start_link, Args}, permanent, 5000, worker, Modules}.
provider_kind(services) -> providers;
provider_kind(data_subscriptions) -> data_providers;
provider_kind(Kind) -> Kind.
service_name({ServiceId, Key}) ->
atom_to_list(ServiceId) ++ ":" ++ atom_to_list(Key);
service_name(ServiceId) ->
atom_to_list(ServiceId).
modules(#couch_epi_spec{kind = providers, value = Module}) ->
[Module];
modules(#couch_epi_spec{kind = services, value = Module}) ->
[Module];
modules(#couch_epi_spec{kind = data_providers, value = Value}) ->
case Value of
{static_module, Module} -> [Module];
{callback_module, Module} -> [Module];
_ -> []
end;
modules(#couch_epi_spec{kind = data_subscriptions, behaviour = Module}) ->
[Module].
merge([], Children) ->
Children;
merge([{Id, _, _, _, _, _} = Spec | Rest], Children) ->
merge(Rest, lists:keystore(Id, 1, Children, Spec));
merge([#{id := Id} = Spec | Rest], Children) ->
Replace = fun(#{id := I}) when I == Id -> Spec; (E) -> E end,
merge(Rest, lists:map(Replace, Children)).