blob: 33c4f9150b9a5886d7c330fd6cd0ba5f37b72c32 [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_functions).
-behaviour(gen_server).
%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------
-export([childspec/4]).
-export([start_link/4, reload/1]).
-export([wait/1, stop/1]).
%% ------------------------------------------------------------------
%% gen_server Function Exports
%% ------------------------------------------------------------------
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {
provider, service_id, modules, hash, handle,
initialized = false, pending = []}).
%% ------------------------------------------------------------------
%% API Function Definitions
%% ------------------------------------------------------------------
childspec(Id, App, Key, Module) ->
{
Id,
{?MODULE, start_link, [
App,
{epi_key, Key},
{modules, [Module]},
[]
]},
permanent,
5000,
worker,
[Module]
}.
start_link(ProviderApp, {epi_key, ServiceId}, {modules, Modules}, Options) ->
maybe_start_keeper(ServiceId),
gen_server:start_link(
?MODULE, [ProviderApp, ServiceId, Modules, Options], []).
reload(Server) ->
gen_server:call(Server, reload).
wait(Server) ->
gen_server:call(Server, wait).
stop(Server) ->
catch gen_server:call(Server, stop).
%% ------------------------------------------------------------------
%% gen_server Function Definitions
%% ------------------------------------------------------------------
init([Provider, ServiceId, Modules, _Options]) ->
gen_server:cast(self(), init),
{ok, #state{
provider = Provider,
modules = Modules,
service_id = ServiceId,
handle = couch_epi_functions_gen:get_handle(ServiceId)}}.
handle_call(wait, _From, #state{initialized = true} = State) ->
{reply, ok, State};
handle_call(wait, From, #state{pending = Pending} = State) ->
{noreply, State#state{pending = [From | Pending]}};
handle_call(reload, _From, State) ->
{Res, NewState} = reload_if_updated(State),
{reply, Res, NewState};
handle_call(stop, _From, State) ->
{stop, normal, State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(init, #state{pending = Pending} = State) ->
{_, NewState} = reload_if_updated(State),
[gen_server:reply(Client, ok) || Client <- Pending],
{noreply, NewState#state{initialized = true, pending = []}};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, State) ->
safe_remove(State),
ok.
code_change(_OldVsn, State, _Extra) ->
{_, NewState} = reload_if_updated(State),
{ok, NewState}.
%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------
reload_if_updated(#state{handle = Module} = State) ->
case couch_epi_util:module_exists(Module) of
true ->
do_reload_if_updated(State);
false ->
{ok, State}
end.
do_reload_if_updated(#state{hash = OldHash, modules = Modules} = State) ->
case couch_epi_functions_gen:hash(Modules) of
OldHash ->
{ok, State};
Hash ->
safe_add(Hash, State)
end.
safe_add(Hash, #state{modules = OldModules} = State) ->
#state{
handle = Handle,
provider = Provider,
modules = Modules,
service_id = ServiceId} = State,
try
ok = couch_epi_functions_gen:add(Handle, Provider, Modules),
couch_epi_server:notify(
Provider, ServiceId, {modules, OldModules}, {modules, Modules}),
{ok, State#state{hash = Hash}}
catch Class:Reason ->
{{Class, Reason}, State}
end.
safe_remove(#state{} = State) ->
#state{
handle = Handle,
provider = Provider,
modules = Modules,
service_id = ServiceId} = State,
try
ok = couch_epi_functions_gen:remove(Handle, Provider, Modules),
couch_epi_server:notify(
Provider, ServiceId, {modules, Modules}, {modules, []}),
{ok, State#state{modules = []}}
catch Class:Reason ->
{{Class, Reason}, State}
end.
maybe_start_keeper(ServiceId) ->
Handle = couch_epi_functions_gen:get_handle(ServiceId),
couch_epi_module_keeper:maybe_start_keeper(couch_epi_functions_gen, Handle).