%%-------------------------------------------------------------------- %% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2017. All Rights Reserved. %% %% 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. %% %% %CopyrightEnd% %% %% %%----------------------------------------------------------------- %% File: orber_objectkeys.erl %% %% Description: %% This file contains the object keyserver in Orber %% %%----------------------------------------------------------------- -module(orber_objectkeys). -behaviour(gen_server). -include_lib("orber/include/corba.hrl"). %%----------------------------------------------------------------- %% External exports %%----------------------------------------------------------------- -export([start/1, stop/0, stop_all/0, get_pid/1, is_persistent/1, register/2, register/3, delete/1, create_schema/1, check/1, remove_old_keys/0]). %%----------------------------------------------------------------- %% Internal exports %%----------------------------------------------------------------- -export([init/1, terminate/2, install/2, handle_call/3, handle_info/2, code_change/3]). -export([handle_cast/2, dump/0, get_key_from_pid/1, gc/1]). %%----------------------------------------------------------------- %% Mnesia Table definition record %%----------------------------------------------------------------- -record(orber_objkeys, {object_key, pid, persistent=false, timestamp}). %%----------------------------------------------------------------- %% Macros %%----------------------------------------------------------------- -define(dirty_query_context, true). %% This macro returns a read fun suitable for evaluation in a transaction -define(read_function(Objkey), fun() -> mnesia:dirty_read(Objkey) end). %% This macro returns a write fun suitable for evaluation in a transaction -define(write_function(R), fun() -> mnesia:dirty_write(R) end). %% This macro returns a delete fun suitable for evaluation in a transaction -define(delete_function(R), fun() -> mnesia:delete(R) end). %% Use this fun inside a transaction to get a list of all keys. -define(match_function(), fun() -> mnesia:match_object({orber_objkeys, '_', '_','_','_'}) end). -ifdef(dirty_query_context). -define(query_check(Q_res), Q_res). -else. -define(query_check(Q_res), {atomic, Q_res}). -endif. -define(CHECK_EXCEPTION(Res), case Res of {'EXCEPTION', E} -> corba:raise(E); R -> R end). -define(DEBUG_LEVEL, 6). %%----------------------------------------------------------------- %% Debugging function %%----------------------------------------------------------------- dump() -> case catch mnesia:dirty_first('orber_objkeys') of {'EXIT', R} -> io:format("Exited with ~p\n",[R]); Key -> dump_print(Key), dump_loop(Key) end. dump_loop(PreviousKey) -> case catch mnesia:dirty_next('orber_objkeys', PreviousKey) of {'EXIT', R} -> io:format("Exited with ~p\n",[R]); '$end_of_table' -> ok; Key -> dump_print(Key), dump_loop(Key) end. dump_print(Key) -> case catch mnesia:dirty_read({'orber_objkeys', Key}) of {'EXIT', R} -> io:format("Exited with ~p\n",[R]); [X] -> io:format("object_key: ~p, pid: ~p, persistent: ~p, timestamp: ~p\n", [binary_to_term(X#orber_objkeys.object_key), X#orber_objkeys.pid, X#orber_objkeys.persistent, X#orber_objkeys.timestamp]); _ -> ok end. %%----------------------------------------------------------------- %% External interface functions %%----------------------------------------------------------------- start(Opts) -> gen_server:start_link({local, orber_objkeyserver}, orber_objectkeys, Opts, []). stop() -> gen_server:call(orber_objkeyserver, stop, infinity). remove_old_keys() -> %% This function may ONLY be used when restarting a crashed node. %% We must remove all objects started with {global, "name"} otherwise %% we cannot restart the node using the same name. Fun = fun() -> Node = node(), mnesia:write_lock_table(orber_objkeys), Objects = mnesia:match_object(orber_objkeys, mnesia:table_info(orber_objkeys, wild_pattern), read), lists:foreach(fun(Obj) -> case node(Obj#orber_objkeys.pid) of Node -> mnesia:delete({orber_objkeys, Obj#orber_objkeys.object_key}); _-> ok end end, Objects), ok end, write_result(mnesia:transaction(Fun)). stop_and_remove_local(Reason) -> %% This function may ONLY be used when this server terminates with reason %% normal or shutdown. Fun = fun() -> Node = node(), mnesia:write_lock_table(orber_objkeys), Objects = mnesia:match_object(orber_objkeys, mnesia:table_info(orber_objkeys, wild_pattern), read), lists:foreach(fun(Obj) -> case node(Obj#orber_objkeys.pid) of Node -> exit(Obj#orber_objkeys.pid, Reason), mnesia:delete({orber_objkeys, Obj#orber_objkeys.object_key}); _-> ok end end, Objects), ok end, write_result(mnesia:transaction(Fun)). stop_all() -> Fun = ?match_function(), case mnesia:transaction(Fun) of {atomic, Objects} -> lists:foreach(fun(Obj) -> gen_server:call(Obj#orber_objkeys.pid, stop, infinity) end, Objects); R -> R end. get_pid(Objkey) -> case catch ets:lookup_element(orber_objkeys, Objkey, 3) of Pid when is_pid(Pid) -> Pid; dead -> {error, "unable to contact object"}; _ -> %% This call is necessary if a persistent object have died %% and the objectkey server is currently updating the Pid %% to equal 'dead'. Without this case 'OBJECT_NOT_EXIST' %% would be raised which is uncorrect if the object is %% persistent. ?CHECK_EXCEPTION(gen_server:call(orber_objkeyserver, {get_pid, Objkey}, infinity)) end. is_persistent(Pid) when is_pid(Pid) -> case catch get_key_from_pid(Pid) of {'EXCEPTION', _} -> corba:raise(#'OBJECT_NOT_EXIST'{completion_status=?COMPLETED_NO}); Key -> is_persistent(Key) end; is_persistent(Objkey) -> case catch ets:lookup_element(orber_objkeys, Objkey, 4) of {'EXIT', _R} -> corba:raise(#'OBJECT_NOT_EXIST'{completion_status=?COMPLETED_NO}); Boolean -> Boolean end. gc(Sec) when is_integer(Sec) -> Fun = fun() -> mnesia:write_lock_table(orber_objkeys), Objects = mnesia:match_object({orber_objkeys, '_', dead, true,'_'}), lists:foreach(fun(Obj) -> case timetest(Sec, Obj#orber_objkeys.timestamp) of true -> mnesia:delete({orber_objkeys, Obj#orber_objkeys.object_key}); _-> ok end end, Objects), ok end, write_result(mnesia:transaction(Fun)). register(Objkey, Pid) -> 'register'(Objkey, Pid, false). register(Objkey, Pid, Type) when is_pid(Pid) -> ?CHECK_EXCEPTION(gen_server:call(orber_objkeyserver, {register, Objkey, Pid, Type}, infinity)); register(Objkey, Pid, Type) -> orber:dbg("[~p] orber_objectkeys:register(~p, ~p); Not a Pid ~p", [?LINE, Objkey, Type, Pid], ?DEBUG_LEVEL), corba:raise(#'INTERNAL'{completion_status=?COMPLETED_NO}). delete(Objkey) -> ?CHECK_EXCEPTION(gen_server:call(orber_objkeyserver, {delete, Objkey}, infinity)). check(Objkey) -> ?CHECK_EXCEPTION(gen_server:call(orber_objkeyserver, {check, Objkey}, infinity)). %%----------------------------------------------------------------- %% Server functions %%----------------------------------------------------------------- init(_Env) -> case mnesia:wait_for_tables(['orber_objkeys'], infinity) of ok -> process_flag(trap_exit, true), start_gc_timer(orber:objectkeys_gc_time()); StopReason -> {stop, StopReason} end. terminate(shutdown, _State) -> stop_and_remove_local(shutdown), ok; terminate(normal, _State) -> stop_and_remove_local(normal), ok; terminate(_Reason, _State) -> ok. start_gc_timer(infinity) -> {ok, []}; start_gc_timer(Time) -> timer:start(), case timer:send_after(timer:seconds(Time), orber_objkeyserver, {oe_gc, Time}) of {ok, _} -> {ok, []}; StopReason -> {stop, StopReason} end. install(Timeout, Options) -> %% check if there already exists a database. If not, create one. %% DB_initialized = perhaps_create_schema(Nodelist), %% check if mnesia is running. If not, start mnesia. perhaps_start_mnesia(), %% Do we have a complete set of IFR tables? If not, create them. AllTabs = mnesia:system_info(tables), DB_Result = case lists:member(orber_objkeys, AllTabs) of true -> case lists:member({local_content, true}, Options) of true-> mnesia:add_table_copy(orber_objkeys, node(), ram_copies); _ -> mnesia:create_table(orber_objkeys, [{attributes, record_info(fields, orber_objkeys)} |Options]) end; _ -> mnesia:create_table(orber_objkeys, [{attributes, record_info(fields, orber_objkeys)} |Options]) end, Wait = mnesia:wait_for_tables([orber_objkeys], Timeout), %% Check if any error has occurred yet. If there are errors, return them. if DB_Result == {atomic, ok}, Wait == ok -> ok; true -> {error, {DB_Result, Wait}} end. %%----------------------------------------------------------------- %% Func: handle_call/3 %% %% Comment: %% In objectkey gen_server all exceptions are tupples and corba:raise %% may not be used. It is too time consuming to add catches in every %% function before returning. On the client side there is a case which %% maps every tupple on the format {'exception', E} to corba:raise(E). %%----------------------------------------------------------------- handle_call(stop, _From, State) -> {stop, normal, [], State}; handle_call({get, Objkey}, _From, State) -> R = query_result(mnesia:dirty_read({orber_objkeys, Objkey})), {reply, R, State}; handle_call({register, Objkey, Pid, Type}, _From, State) -> _WF = fun() -> case mnesia:wread({orber_objkeys, Objkey}) of [] -> %% No key exists. Ok to register. mnesia:write(#orber_objkeys{object_key=Objkey, pid=Pid, persistent=Type, timestamp=erlang:monotonic_time(seconds)}); [X] when X#orber_objkeys.persistent==true, X#orber_objkeys.pid == dead -> %% A persistent object is being restarted. Update Pid & time. mnesia:write(X#orber_objkeys{pid=Pid, timestamp=erlang:monotonic_time(seconds)}); [X] when is_pid(X#orber_objkeys.pid) -> %% Object exists, i.e., trying to create an object with %% the same name. orber:dbg("[~p] orber_objectkeys:register(~p, ~p); Object already exists.", [?LINE, Objkey, Type], ?DEBUG_LEVEL), {'EXCEPTION', #'BAD_PARAM'{completion_status=?COMPLETED_NO}}; Why -> %% Something else occured. orber:dbg("[~p] orber_objectkeys:register(~p, ~p); error reading from DB(~p)", [?LINE, Objkey, Type, Why], ?DEBUG_LEVEL), {'EXCEPTION', #'INTERNAL'{completion_status=?COMPLETED_NO}} end end, R = write_result(mnesia:transaction(_WF)), if R == ok andalso is_pid(Pid) -> link(Pid); true -> true end, {reply, R, State}; handle_call({delete, Objkey}, _From, State) -> ?query_check(Qres) = mnesia:dirty_read({orber_objkeys, Objkey}), case Qres of [] -> true; [X] when is_pid(X#orber_objkeys.pid) -> unlink(X#orber_objkeys.pid); _ -> true end, _F = ?delete_function({orber_objkeys, Objkey}), R = write_result(mnesia:transaction(_F)), {reply, R, State}; handle_call({get_pid, Objkey}, _From, State) -> _F = fun() -> mnesia:read({orber_objkeys, Objkey}) end, case mnesia:transaction(_F) of {atomic, [X]} when is_pid(X#orber_objkeys.pid) -> {reply, X#orber_objkeys.pid, State}; {atomic, [X]} when X#orber_objkeys.pid == dead -> {reply, {'EXCEPTION', #'TRANSIENT'{completion_status=?COMPLETED_NO}}, State}; _Res -> {reply, {'EXCEPTION', #'OBJECT_NOT_EXIST'{completion_status=?COMPLETED_NO}}, State} end; handle_call({check, {_, 'key', Objkey, _, _, _}}, _From, State) -> ?query_check(Qres) = mnesia:dirty_read({orber_objkeys, Objkey}), case Qres of [_X] -> {reply, 'object_here', State}; _ -> {reply, 'unknown_object', State} end; handle_call({check, {_, 'registered', Objkey, _, _, _}}, _From, State) -> case whereis(Objkey) of undefined -> case catch ets:lookup_element(orber_objkeys, Objkey, 4) of true -> {reply, 'object_here', State}; _-> {reply, 'unknown_object', State} end; _ -> {reply, 'object_here', State} end; handle_call({check, {_, 'pseudo', Module, _, _, _}}, _From, State) -> case code:is_loaded(Module) of false -> {reply, 'unknown_object', State}; _ -> {reply, 'object_here', State} end; handle_call({check, "INIT"}, _From, State) -> {reply, 'object_here', State}; handle_call({check, _}, _From, State) -> {reply, 'unknown_object', State}. handle_info({'EXIT', Pid, Reason}, State) when is_pid(Pid) -> _WF = fun() -> case mnesia:match_object({orber_objkeys, '_', Pid,'_','_'}) of [] -> ok; [X] when X#orber_objkeys.persistent==false -> mnesia:delete({orber_objkeys, X#orber_objkeys.object_key}); [X] when is_pid(X#orber_objkeys.pid) andalso X#orber_objkeys.persistent==true andalso Reason /= normal andalso Reason /= shutdown -> mnesia:write(X#orber_objkeys{pid=dead, timestamp=erlang:monotonic_time(seconds)}); [X] when X#orber_objkeys.persistent==true -> mnesia:delete({orber_objkeys, X#orber_objkeys.object_key}); _-> ok end end, case write_result(mnesia:transaction(_WF)) of ok -> unlink(Pid); _-> true end, {noreply, State}; handle_info({oe_gc, Secs}, State) -> catch gc(Secs), {noreply, State}. code_change(_OldVsn, State, _Extra) -> {ok, State}. %%----------------------------------------------------------------- %% Internal Functions %%----------------------------------------------------------------- timetest(S, TimeStamp) -> TimeStamp+S < erlang:monotonic_time(seconds). get_key_from_pid(Pid) -> case mnesia:dirty_match_object({orber_objkeys, '_', Pid,'_','_'}) of [Keys] -> Keys#orber_objkeys.object_key; _ -> corba:raise(#'OBJECT_NOT_EXIST'{completion_status=?COMPLETED_NO}) end. %remove_keys([], _) -> % ok; %remove_keys([H|T], R) when H#orber_objkeys.persistent==false -> % _F = ?delete_function({orber_objkeys, H#orber_objkeys.object_key}), % write_result(mnesia:transaction(_F)), % remove_keys(T, R). %%----------------------------------------------------------------- %% Check a read transaction query_result(?query_check(Qres)) -> case Qres of [Hres] -> Hres#orber_objkeys.pid; [] -> {'EXCEPTION', #'OBJECT_NOT_EXIST'{completion_status=?COMPLETED_NO}}; Other -> orber:dbg("[~p] orber_objectkeys:query_result(); DB lookup failed(~p)", [?LINE, Other], ?DEBUG_LEVEL), {'EXCEPTION', #'INTERNAL'{completion_status=?COMPLETED_NO}} end. %%----------------------------------------------------------------- %% Check a write transaction write_result({atomic,ok}) -> ok; write_result(Foo) -> orber:dbg("[~p] orber_objectkeys:query_result(); DB write failed(~p)", [?LINE, Foo], ?DEBUG_LEVEL), {'EXCEPTION', #'INTERNAL'{completion_status=?COMPLETED_NO}}. create_schema(Nodes) -> case mnesia:system_info(use_dir) of false -> mnesia:create_schema(Nodes); _ -> ok end. perhaps_start_mnesia() -> case mnesia:system_info(is_running) of no -> mnesia:start(); _ -> ok end. %%------------------------------------------------------------ %% Standard gen_server cast handle %% handle_cast(_, State) -> {noreply, State}.