% Copyright (C) 2003-2019 Olivier Boudeville % % This file is part of the Ceylan-Myriad library. % % This library is free software: you can redistribute it and/or modify % it under the terms of the GNU Lesser General Public License or % the GNU General Public License, as they are published by the Free Software % Foundation, either version 3 of these Licenses, or (at your option) % any later version. % You can also redistribute it and/or modify it under the terms of the % Mozilla Public License, version 1.1 or later. % % This library is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU Lesser General Public License and the GNU General Public License % for more details. % % You should have received a copy of the GNU Lesser General Public % License, of the GNU General Public License and of the Mozilla Public License % along with this library. % If not, see and % . % % Author: Olivier Boudeville [olivier (dot) boudeville (at) esperide (dot) com] % Creation date: July 1, 2007. % Gathering of various facilities about naming services (local and global). % % See naming_utils_test.erl for the corresponding test. % -module(naming_utils). % Registration functions. -export([ register_as/2, register_as/3, register_or_return_registered/2, unregister/2, get_registered_pid_for/1, get_registered_pid_for/2, get_locally_registered_pid_for/2, get_registered_names/1, is_registered/1, is_registered/2, wait_for_registration_of/2, wait_for_global_registration_of/1, wait_for_global_registration_of/2, wait_for_local_registration_of/1, wait_for_local_registration_of/2, wait_for_remote_local_registrations_of/2, display_registered/0 ]). % Necessarily an atom: -type registration_name() :: atom(). -type registration_scope() :: 'global_only' | 'local_only' | 'local_and_global' | 'none'. -type look_up_scope() :: 'global' | 'local' | 'local_and_global' | 'local_otherwise_global'. -export_type([ registration_name/0, registration_scope/0, look_up_scope/0 ]). % Registration functions. % % Note that: % - only local processes can be registered locally % - a given PID cannot be registered globally under more than one name % - if a registered process terminates (for any of the two scopes), its name is % automatically unregistered (see % http://erlang.org/doc/reference_manual/processes.htmlhttp://erlang.org/doc/man/global.html) % Registers the current process under specified name, which must be an atom. % % Declaration is register_as( Name, RegistrationScope ) with RegistrationScope % in 'local_only', 'global_only', 'local_and_global', 'none' depending on what % kind of registration is requested. % % Throws an exception on failure (ex: if that name is already registered). % -spec register_as( registration_name(), registration_scope() ) -> void(). register_as( Name, RegistrationScope ) -> register_as( self(), Name, RegistrationScope ). % Registers specified (local) PID under specified name, which must be an atom. % % Declaration is: register_as( Pid, Name, RegistrationScope ) with % RegistrationScope in 'local_only', 'global_only', 'local_and_global', 'none', % depending on what kind of registration is requested. % % Throws an exception on failure. % -spec register_as( pid(), registration_name(), registration_scope() ) -> void(). register_as( Pid, Name, local_only ) when is_atom( Name ) -> %trace_utils:debug_fmt( "register_as: local_only, " % "with PID=~w and Name='~p'.", [ Pid, Name ] ), try erlang:register( Name, Pid ) of true -> ok catch error:badarg -> case is_registered( Name, local ) of % No more information obtained: not_registered -> throw( { local_registration_failed, Name, { error, badarg } } ); Pid -> throw( { local_registration_failed, Name, already_registered, Pid } ); RegPid -> throw( { local_registration_failed, Name, already_registered, { Pid, RegPid } } ) end; ExceptionType:Exception -> throw( { local_registration_failed, Name, { ExceptionType, Exception } } ) end; register_as( Pid, Name, global_only ) when is_atom( Name ) -> %trace_utils:debug_fmt( "register_as: global_only, with PID=~w " % "and Name='~p'.", [ Pid, Name ] ), case global:register_name( Name, Pid ) of yes -> ok; no -> throw( { global_registration_failed, Name } ) end; register_as( Pid, Name, local_and_global ) when is_atom( Name ) -> register_as( Pid, Name, local_only ), register_as( Pid, Name, global_only ); register_as( _Pid, _Name, none ) -> ok. % Registers specified PID under specified name (which must be an atom) and scope % (only local_only and global_only registration scopes permitted), and returns % 'registered', or returns the PID of any process already registered. % % This is an atomic operation, which is not meant to fail. % % Allows for example a series of non-synchronised processes to all attempt to % register: the first will succeed, all the others will get its PID, none will % fail. % -spec register_or_return_registered( registration_name(), 'global_only' | 'local_only' ) -> 'registered' | pid(). register_or_return_registered( Name, Scope ) when is_atom( Name ) -> % Minor annoyance: we ensured that looking up a process relied generally on % a different atom than registering it (ex: 'global' vs 'global_only'). % % Here, we expect the user to specify a registration atom; we need to % convert it for look-up purposes: % LookUpScope = registration_to_look_up_scope( Scope ), case is_registered( Name, LookUpScope ) of not_registered -> try register_as( Name, Scope ), registered catch throw:_ -> % Another process must have registered in-between, let's % restart: % % (a small random waiting could be added here) % register_or_return_registered( Name, Scope ) end; Pid -> Pid end. % Unregisters specified name from specified registry. % % Throws an exception in case of failure. % % Note: when a process terminates, it unregisters its name (if any) % automatically from all scopes. % -spec unregister( registration_name(), registration_scope() ) -> void(). unregister( Name, local_only ) -> try erlang:unregister( Name ) of true -> ok catch ExceptionType:Exception -> throw( { local_unregistration_failed, Name, { ExceptionType, Exception } } ) end; unregister( Name, global_only ) -> % Documentation says it returns "void" (actually 'ok'): try global:unregister_name( Name ) catch ExceptionType:Exception -> throw( { global_unregistration_failed, Name, { ExceptionType, Exception } } ) end; unregister( Name, local_and_global ) -> unregister( Name, local_only ), unregister( Name, global_only ); unregister( _Name, none ) -> ok. % Returns the PID that should be already registered, as specified name. % % Local registering will be requested first, if not found global one will be % tried. % % No specific waiting for registration will be performed, see % wait_for_*_registration_of instead. % -spec get_registered_pid_for( registration_name() ) -> pid(). get_registered_pid_for( Name ) -> get_registered_pid_for( Name, _RegistrationScope=local_otherwise_global ). -spec get_registered_pid_for( registration_name(), look_up_scope() ) -> pid(). get_registered_pid_for( Name, _RegistrationScope=local_otherwise_global ) -> try get_registered_pid_for( Name, local ) catch { not_registered_locally, _Name } -> try get_registered_pid_for( Name, global ) catch { not_registered_globally, Name } -> throw( { neither_registered_locally_nor_globally, Name } ) end end; get_registered_pid_for( Name, _RegistrationScope=local ) -> case erlang:whereis( Name ) of undefined -> throw( { not_registered_locally, Name } ); Pid -> Pid end; get_registered_pid_for( Name, _RegistrationScope=global ) -> case global:whereis_name( Name ) of undefined -> throw( { not_registered_globally, Name } ); Pid -> Pid end; % So that the atom used for registration can be used for look-up as well, % notably in static methods (see the registration_scope defines). % get_registered_pid_for( Name, _RegistrationScope=local_and_global ) -> get_registered_pid_for( Name, local_otherwise_global ). % Returns the PID of the process corresponding to the specified local name on % specified node: that process is expected to be locally registered on that % specified node. % % Throws an exception on failure. % -spec get_locally_registered_pid_for( registration_name(), net_utils:atom_node_name() ) -> pid(). get_locally_registered_pid_for( Name, TargetNode ) -> case rpc:call( TargetNode, _Mod=erlang, _Fun=whereis, _Args=[ Name ] ) of { badrpc, Reason } -> throw( { not_registered_locally, Name, TargetNode, Reason } ); Res -> Res end. % Returns a list of the names of the registered processes, for specified look-up % scope. % -spec get_registered_names( look_up_scope() ) -> [ registration_name() ]. get_registered_names( _LookUpScope=global ) -> global:registered_names(); get_registered_names( _LookUpScope=local ) -> erlang:registered(). % Tells whether specified name is registered in the specified local/global % context: if no, returns the 'not_registered' atom, otherwise returns the % corresponding PID. % % Local registering will be requested first, if not found global one will be % tried. % % No specific waiting for registration will be performed, see % wait_for_*_registration_of instead. % -spec is_registered( registration_name() ) -> pid() | 'not_registered'. is_registered( Name ) -> is_registered( Name, _RegistrationScope=local_otherwise_global ). -spec is_registered( registration_name(), look_up_scope() ) -> pid() | 'not_registered'. is_registered( Name, _LookUpScope=global ) -> case global:whereis_name( Name ) of undefined -> not_registered ; Pid -> Pid end; is_registered( Name, _LookUpScope=local ) -> case erlang:whereis( Name ) of undefined -> not_registered; Pid -> Pid end; % Returns a PID iff both local and global look-ups returns a PID, and the same % one. % is_registered( Name, _LookUpScope=local_and_global ) -> case is_registered( Name, local ) of not_registered -> not_registered; Pid -> case is_registered( Name, global ) of % Already bound! Pid -> Pid; not_registered -> not_registered end end; is_registered( Name, _LookUpScope=local_otherwise_global ) -> case is_registered( Name, local ) of not_registered -> is_registered( Name, global ); Pid -> Pid end; % Normally, 'local_only', 'global_only' and 'none' should only be specified for % registration (not for looking-up); nevertheless the following clauses allow to % use the same parameter for reading as for registration, even if we do not know % which. % % So that the atom used for registration can be used for look-up as well, % notably in static methods (see the registration_scope defines). % is_registered( Name, _LookUpScope=local_only ) -> is_registered( Name, local ); is_registered( Name, _LookUpScope=global_only ) -> is_registered( Name, global ). % Waits (up to a few seconds) until specified name is registered, within % specified scope. % % Returns the resolved PID, or throws an exception. % -spec wait_for_registration_of( registration_name(), look_up_scope() ) -> pid(). wait_for_registration_of( Name, _LookUpScope=global_only ) -> wait_for_global_registration_of( Name ); wait_for_registration_of( Name, _LookUpScope=local_only ) -> wait_for_local_registration_of( Name ). % (other scopes not managed) % Waits (up to 10 seconds) until specified name is globally registered. % % Returns the resolved PID, or throws % { global_registration_waiting_timeout, Name }. % -spec wait_for_global_registration_of( registration_name() ) -> pid(). wait_for_global_registration_of( Name ) -> wait_for_global_registration_of( Name, _Seconds=10 ). wait_for_global_registration_of( Name, _Seconds=0 ) -> throw( { global_registration_waiting_timeout, Name } ); wait_for_global_registration_of( Name, SecondsToWait ) -> case global:whereis_name( Name ) of undefined -> timer:sleep( 1000 ), wait_for_global_registration_of( Name, SecondsToWait-1 ); Pid -> Pid end. % Waits (up to 5 seconds) until specified name is locally registered. % % Returns the resolved PID, or throws {local_registration_waiting_timeout,Name}. % -spec wait_for_local_registration_of( registration_name() ) -> pid() | port(). wait_for_local_registration_of( Name ) -> wait_for_local_registration_of( Name , _Seconds=5 ). wait_for_local_registration_of( Name, _Seconds=0 ) -> throw( { local_registration_waiting_timeout, Name } ); wait_for_local_registration_of( Name, SecondsToWait ) -> case erlang:whereis( Name ) of undefined -> timer:sleep( 1000 ), wait_for_local_registration_of( Name, SecondsToWait-1 ); Pid -> Pid end. % Waits for specified name RegisteredName (an atom) to be locally registered on % all specified nodes before returning. % % A time-out is triggered if the waited duration exceeds 10 seconds. % -spec wait_for_remote_local_registrations_of( registration_name(), [ net_utils:atom_node_name() ] ) -> void(). wait_for_remote_local_registrations_of( RegisteredName, Nodes ) -> % Up to 10 seconds, 0.5 seconds of waiting between two, thus 20 attempts: RemainingAttempts = round( 10 / 0.5 ), wait_for_remote_local_registrations_of( RegisteredName, Nodes, RemainingAttempts ). % Helper function. wait_for_remote_local_registrations_of( RegisteredName, Nodes, _RemainingAttempts=0 ) -> throw( { time_out_while_waiting_remote_local_registration, RegisteredName, Nodes } ); wait_for_remote_local_registrations_of( RegisteredName, Nodes, RemainingAttempts ) -> { ResList, BadNodes } = rpc:multicall( Nodes, erlang, whereis, [ RegisteredName ], _Timeout=2000 ), case BadNodes of [] -> ok; _ -> throw( { bad_nodes_while_waiting_remote_local_registration, RegisteredName, BadNodes } ) end, case lists:member( undefined, ResList ) of true -> % Happens regularly on some settings: %trace_utils:debug_fmt( "wait_for_remote_local_registrations_of: " % "for ~p, retry needed.", [ Nodes ] ), % At least one node not ready (we do not know which one), waiting a % bit for it: % timer:sleep( 500 ), wait_for_remote_local_registrations_of( RegisteredName, Nodes, RemainingAttempts - 1 ); false -> ok end. % Displays registered processes. -spec display_registered() -> void(). display_registered() -> io:format( "On a total of ~B existing processes on node '~s':~n", [ length( processes() ), node() ] ), case global:registered_names() of [] -> io:format( " - no process is globally-registered~n" ); Globals -> io:format( " - ~B processes are globally-registered:~n~p~n", [ length( Globals ), Globals ] ) end, case registered() of [] -> io:format( " - no process is locally-registered~n" ); Locals -> io:format( " - ~B processes are locally-registered:~n~p~n", [ length( Locals ), Locals ] ) end. % Converts a registration scope into a look-up one. % % Note: only legit for a subset of the registration scopes, otherwise a case % clause is triggered. % % (helper) % -spec registration_to_look_up_scope( registration_scope() ) -> look_up_scope(). registration_to_look_up_scope( _Scope=global_only ) -> global; registration_to_look_up_scope( _Scope=local_only ) -> local; registration_to_look_up_scope( _Scope=local_and_global ) -> local_and_global.