From b0426732cc19598f0c0c310b1e79918252495259 Mon Sep 17 00:00:00 2001 From: Ulf Wiger Date: Mon, 17 Jan 2011 15:47:00 +0100 Subject: Add plugin support for alternative name lookup OTP behaviour instances (gen_server, gen_fsm, gen_event) can currently register themselves either locally or globally, and the behaviour libraries (including gen.erl) support both addressing methods, as well as the normal Pid and {Name, Node}. However, there are alternative registry implementations - e.g. gproc - and one can well imagine other ways of locating a behaviour instance, e.g. on a node connected only via a TCP tunnel, rather than via Distributed Erlang. In all these cases, one needs to write extra code to identify the behaviour instance, even though the instance itself need not be aware of how it is located. This patch introduces a new way of locating a behaviour instance: {via, Module, Name}. Module is expected to export a subset of the functions in global.erl, namely: register_name(Name, Pid) -> yes | no whereis_name(Name) -> pid() | undefined unregister_name(Name) -> ok send(Name, Msg) -> Pid Semantics are expected to be the same as for global.erl This can be used in all places where {global, Name} is accepted. faulty export in gen_fsm_SUITE.erl await process death in dummy_via:reset() fix error in gen_[server|fsm]:enter_loop() fix documentation --- lib/stdlib/doc/src/gen_event.xml | 61 ++++++++++++++++----- lib/stdlib/doc/src/gen_fsm.xml | 55 ++++++++++++------- lib/stdlib/doc/src/gen_server.xml | 39 +++++++++---- lib/stdlib/doc/src/supervisor.xml | 12 +++- lib/stdlib/doc/src/supervisor_bridge.xml | 7 +++ lib/stdlib/src/gen.erl | 22 ++++++-- lib/stdlib/src/gen_event.erl | 10 +++- lib/stdlib/src/gen_fsm.erl | 25 ++++++++- lib/stdlib/src/gen_server.erl | 23 +++++++- lib/stdlib/test/Makefile | 1 + lib/stdlib/test/dummy_via.erl | 94 ++++++++++++++++++++++++++++++++ lib/stdlib/test/gen_event_SUITE.erl | 26 ++++++++- lib/stdlib/test/gen_fsm_SUITE.erl | 71 +++++++++++++++++++++--- lib/stdlib/test/gen_server_SUITE.erl | 62 ++++++++++++++++++++- 14 files changed, 436 insertions(+), 72 deletions(-) create mode 100644 lib/stdlib/test/dummy_via.erl diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml index 24bcb419fe..0f50b37de6 100644 --- a/lib/stdlib/doc/src/gen_event.xml +++ b/lib/stdlib/doc/src/gen_event.xml @@ -120,8 +120,10 @@ gen_event:stop -----> Module:terminate/2 start_link(EventMgrName) -> Result Create a generic event manager process in a supervision tree. - EventMgrName = {local,Name} | {global,Name} + EventMgrName = {local,Name} | {global,GlobalName} + | {via,Module,ViaName}  Name = atom() +  GlobalName = ViaName = term() Result = {ok,Pid} | {error,{already_started,Pid}}  Pid = pid() @@ -132,10 +134,17 @@ gen_event:stop -----> Module:terminate/2 the event manager is linked to the supervisor.

If EventMgrName={local,Name}, the event manager is registered locally as Name using register/2. - If EventMgrName={global,Name}, the event manager is - registered globally as Name using + If EventMgrName={global,GlobalName}, the event manager is + registered globally as GlobalName using global:register_name/2. If no name is provided, - the event manager is not registered.

+ the event manager is not registered. + If EventMgrName={via,Module,ViaName}, the event manager will + register with the registry represented by Module. + The Module callback should export the functions + register_name/2, unregister_name/1, + whereis_name/1 and send/2, which should behave like the + corresponding functions in global. Thus, + {via,global,GlobalName} is a valid reference.

If the event manager is successfully created the function returns {ok,Pid}, where Pid is the pid of the event manager. If there already exists a process with @@ -149,8 +158,10 @@ gen_event:stop -----> Module:terminate/2 start(EventMgrName) -> Result Create a stand-alone event manager process. - EventMgrName = {local,Name} | {global,Name} + EventMgrName = {local,Name} | {global,GlobalName} + | {via,Module,ViaName}  Name = atom() +  GlobalName = ViaName = term() Result = {ok,Pid} | {error,{already_started,Pid}}  Pid = pid() @@ -166,8 +177,10 @@ gen_event:stop -----> Module:terminate/2 add_handler(EventMgrRef, Handler, Args) -> Result Add an event handler to a generic event manager. - EventMgr = Name | {Name,Node} | {global,Name} | pid() + EventMgr = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() +  GlobalName = ViaName = term() Handler = Module | {Module,Id}  Module = atom()  Id = term() @@ -185,8 +198,10 @@ gen_event:stop -----> Module:terminate/2 Name, if the event manager is locally registered, {Name,Node}, if the event manager is locally registered at another node, or - {global,Name}, if the event manager is globally + {global,GlobalName}, if the event manager is globally registered. + {via,Module,ViaName}, if the event manager is registered + through an alternative process registry.

Handler is the name of the callback module Module or a tuple {Module,Id}, where Id is any term. @@ -207,8 +222,10 @@ gen_event:stop -----> Module:terminate/2 add_sup_handler(EventMgrRef, Handler, Args) -> Result Add a supervised event handler to a generic event manager. - EventMgr = Name | {Name,Node} | {global,Name} | pid() + EventMgr = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() +  GlobalName = ViaName = term() Handler = Module | {Module,Id}  Module = atom()  Id = term() @@ -252,8 +269,10 @@ gen_event:stop -----> Module:terminate/2 sync_notify(EventMgrRef, Event) -> ok Notify an event manager about an event. - EventMgrRef = Name | {Name,Node} | {global,Name} | pid() + EventMgrRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() +  GlobalName = ViaName = term() Event = term() @@ -277,8 +296,10 @@ gen_event:stop -----> Module:terminate/2 call(EventMgrRef, Handler, Request, Timeout) -> Result Make a synchronous call to a generic event manager. - EventMgrRef = Name | {Name,Node} | {global,Name} | pid() + EventMgrRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() +  GlobalName = ViaName = term() Handler = Module | {Module,Id}  Module = atom()  Id = term() @@ -317,8 +338,10 @@ gen_event:stop -----> Module:terminate/2 delete_handler(EventMgrRef, Handler, Args) -> Result Delete an event handler from a generic event manager. - EventMgrRef = Name | {Name,Node} | {global,Name} | pid() + EventMgrRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() +  GlobalName = ViaName = term() Handler = Module | {Module,Id}  Module = atom()  Id = term() @@ -345,8 +368,10 @@ gen_event:stop -----> Module:terminate/2 swap_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result Replace an event handler in a generic event manager. - EventMgrRef = Name | {Name,Node} | {global,Name} | pid() + EventMgrRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() +  GlobalName = ViaName = term() Handler1 = Handler2 = Module | {Module,Id}  Module = atom()  Id = term() @@ -389,8 +414,10 @@ gen_event:stop -----> Module:terminate/2 swap_sup_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result Replace an event handler in a generic event manager. - EventMgrRef = Name | {Name,Node} | {global,Name} | pid() + EventMgrRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() +  GlobalName = ViaName = term() Handler1 = Handler 2 = Module | {Module,Id}  Module = atom()  Id = term() @@ -411,8 +438,10 @@ gen_event:stop -----> Module:terminate/2 which_handlers(EventMgrRef) -> [Handler] Return all event handlers installed in a generic event manager. - EventMgrRef = Name | {Name,Node} | {global,Name} | pid() + EventMgrRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() +  GlobalName = ViaName = term() Handler = Module | {Module,Id}  Module = atom()  Id = term() @@ -428,8 +457,10 @@ gen_event:stop -----> Module:terminate/2 stop(EventMgrRef) -> ok Terminate a generic event manager. - EventMgrRef = Name | {Name,Node} | {global,Name} | pid() + EventMgrRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid() Name = Node = atom() + GlobalName = ViaName = term()

Terminates the event manager EventMgrRef. Before diff --git a/lib/stdlib/doc/src/gen_fsm.xml b/lib/stdlib/doc/src/gen_fsm.xml index 421eeb4fd3..73c1911f1e 100644 --- a/lib/stdlib/doc/src/gen_fsm.xml +++ b/lib/stdlib/doc/src/gen_fsm.xml @@ -84,9 +84,10 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 start_link(FsmName, Module, Args, Options) -> Result Create a gen_fsm process in a supervision tree. - FsmName = {local,Name} | {global,GlobalName} + FsmName = {local,Name} | {global,GlobalName} + | {via,Module,ViaName}  Name = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Module = atom() Args = term() Options = [Option] @@ -113,8 +114,16 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 locally as Name using register/2. If FsmName={global,GlobalName}, the gen_fsm is registered globally as GlobalName using - global:register_name/2. If no name is provided, - the gen_fsm is not registered.

+ global:register_name/2. + If EventMgrName={via,Module,ViaName}, the event manager will + register with the registry represented by Module. + The Module callback should export the functions + register_name/2, unregister_name/1, + whereis_name/1 and send/2, which should behave like the + corresponding functions in global. Thus, + {via,global,GlobalName} is a valid reference.

+

If no name is provided, + the gen_fsm is not registered.

Module is the name of the callback module.

Args is an arbitrary term which is passed as the argument to Module:init/1.

@@ -154,9 +163,10 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 start(FsmName, Module, Args, Options) -> Result Create a stand-alone gen_fsm process. - FsmName = {local,Name} | {global,GlobalName} + FsmName = {local,Name} | {global,GlobalName} + | {via,Module,ViaName}  Name = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Module = atom() Args = term() Options = [Option] @@ -180,9 +190,10 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 send_event(FsmRef, Event) -> ok Send an event asynchronously to a generic FSM. - FsmRef = Name | {Name,Node} | {global,GlobalName} | pid() + FsmRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Event = term() @@ -196,9 +207,11 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 the pid, Name, if the gen_fsm is locally registered, {Name,Node}, if the gen_fsm is locally - registered at another node, or - {global,GlobalName}, if the gen_fsm is globally - registered. + registered at another node, or + {global,GlobalName}, if the gen_fsm is globally + registered. + {via,Module,ViaName}, if the event manager is registered + through an alternative process registry.

Event is an arbitrary term which is passed as one of the arguments to Module:StateName/2.

@@ -208,9 +221,10 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 send_all_state_event(FsmRef, Event) -> ok Send an event asynchronously to a generic FSM. - FsmRef = Name | {Name,Node} | {global,GlobalName} | pid() + FsmRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Event = term() @@ -232,9 +246,10 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 sync_send_event(FsmRef, Event, Timeout) -> Reply Send an event synchronously to a generic FSM. - FsmRef = Name | {Name,Node} | {global,GlobalName} | pid() + FsmRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Event = term() Timeout = int()>0 | infinity Reply = term() @@ -264,9 +279,10 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 sync_send_all_state_event(FsmRef, Event, Timeout) -> Reply Send an event synchronously to a generic FSM. - FsmRef = Name | {Name,Node} | {global,GlobalName} | pid() + FsmRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Name = Node = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Event = term() Timeout = int()>0 | infinity Reply = term() @@ -388,9 +404,10 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4     | {log_to_file,FileName} | {install,{Func,FuncState}} StateName = atom() StateData = term() - FsmName = {local,Name} | {global,GlobalName} + FsmName = {local,Name} | {global,GlobalName} + | {via,Module,ViaName}  Name = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Timeout = int() | infinity diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml index 1045766e01..90b77d5cb6 100644 --- a/lib/stdlib/doc/src/gen_server.xml +++ b/lib/stdlib/doc/src/gen_server.xml @@ -83,9 +83,10 @@ gen_server:abcast -----> Module:handle_cast/2 start_link(ServerName, Module, Args, Options) -> Result Create a gen_server process in a supervision tree. - ServerName = {local,Name} | {global,GlobalName} + ServerName = {local,Name} | {global,GlobalName} + | {via,Module,ViaName}  Name = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Module = atom() Args = term() Options = [Option] @@ -111,14 +112,22 @@ gen_server:abcast -----> Module:handle_cast/2 If ServerName={global,GlobalName} the gen_server is registered globally as GlobalName using global:register_name/2. If no name is provided, - the gen_server is not registered.

+ the gen_server is not registered. + If EventMgrName={via,Module,ViaName}, the event manager will + register with the registry represented by Module. + The Module callback should export the functions + register_name/2, unregister_name/1, + whereis_name/1 and send/2, which should behave like the + corresponding functions in global. Thus, + {via,global,GlobalName} is a valid reference.

Module is the name of the callback module.

Args is an arbitrary term which is passed as the argument to Module:init/1.

If the option {timeout,Time} is present, the gen_server is allowed to spend Time milliseconds initializing or it will be terminated and the start function - will return {error,timeout}.

+ will return {error,timeout}. +

If the option {debug,Dbgs} is present, the corresponding sys function will be called for each item in Dbgs. See @@ -151,9 +160,10 @@ gen_server:abcast -----> Module:handle_cast/2 start(ServerName, Module, Args, Options) -> Result Create a stand-alone gen_server process. - ServerName = {local,Name} | {global,GlobalName} + ServerName = {local,Name} | {global,GlobalName} + | {via,Module,ViaName}  Name = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Module = atom() Args = term() Options = [Option] @@ -178,9 +188,10 @@ gen_server:abcast -----> Module:handle_cast/2 call(ServerRef, Request, Timeout) -> Reply Make a synchronous call to a generic server. - ServerRef = Name | {Name,Node} | {global,GlobalName} | pid() + ServerRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Node = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Request = term() Timeout = int()>0 | infinity Reply = term() @@ -198,6 +209,8 @@ gen_server:abcast -----> Module:handle_cast/2 registered at another node, or {global,GlobalName}, if the gen_server is globally registered. + {via,Module,ViaName}, if the gen_server is + registered through an alternative process registry.

Request is an arbitrary term which is passed as one of the arguments to Module:handle_call/3.

@@ -281,9 +294,10 @@ gen_server:abcast -----> Module:handle_cast/2 cast(ServerRef, Request) -> ok Send an asynchronous request to a generic server. - ServerRef = Name | {Name,Node} | {global,GlobalName} | pid() + ServerRef = Name | {Name,Node} | {global,GlobalName} + | {via,Module,ViaName} | pid()  Node = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Request = term() @@ -355,9 +369,10 @@ gen_server:abcast -----> Module:handle_cast/2    Dbg = trace | log | statistics     | {log_to_file,FileName} | {install,{Func,FuncState}} State = term() - ServerName = {local,Name} | {global,GlobalName} + ServerName = {local,Name} | {global,GlobalName} + | {via,Module,ViaName}  Name = atom() -  GlobalName = term() +  GlobalName = ViaName = term() Timeout = int() | infinity
diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml index cddb55e5c5..73df7183e6 100644 --- a/lib/stdlib/doc/src/supervisor.xml +++ b/lib/stdlib/doc/src/supervisor.xml @@ -271,8 +271,14 @@ child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules}

If SupName={local,Name} the supervisor is registered locally as Name using register/2. If SupName={global,Name} the supervisor is registered - globally as Name using global:register_name/2. - If no name is provided, the supervisor is not registered.

+ globally as Name using global:register_name/2. If + SupName={via,Module,Name} the supervisor + is registered as Name using the registry represented by + Module. The Module callback should export the functions + register_name/2, unregister_name/1 and send/2, + which should behave like the corresponding functions in global. + Thus, {via,global,Name} is a valid reference.

+

If no name is provided, the supervisor is not registered.

Module is the name of the callback module.

Args is an arbitrary term which is passed as the argument to Module:init/1.

@@ -315,6 +321,8 @@ child_spec() = {Id,StartFunc,Restart,Shutdown,Type,Modules} registered at another node, or {global,Name}, if the supervisor is globally registered. + {via,Module,Name}, if the supervisor is registered + through an alternative process registry.

ChildSpec should be a valid child specification (unless the supervisor is a simple_one_for_one diff --git a/lib/stdlib/doc/src/supervisor_bridge.xml b/lib/stdlib/doc/src/supervisor_bridge.xml index c1a5e7947f..f6712d6c1d 100644 --- a/lib/stdlib/doc/src/supervisor_bridge.xml +++ b/lib/stdlib/doc/src/supervisor_bridge.xml @@ -63,6 +63,13 @@ If SupBridgeName={global,Name} the supervisor_bridge is registered globally as Name using global:register_name/2. + If SupBridgeName={via,Module,Name} the supervisor_bridge is + registered as Name using a registry represented + by Module. The Module callback should export + the functions register_name/2, unregister_name/1 + and send/2, which should behave like the + corresponding functions in global. Thus, + {via,global,GlobalName} is a valid reference. If no name is provided, the supervisor_bridge is not registered. If there already exists a process with the specified SupBridgeName the function returns diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl index 5d803091b6..42555aedd7 100644 --- a/lib/stdlib/src/gen.erl +++ b/lib/stdlib/src/gen.erl @@ -36,7 +36,7 @@ %%----------------------------------------------------------------- -type linkage() :: 'link' | 'nolink'. --type emgr_name() :: {'local', atom()} | {'global', term()}. +-type emgr_name() :: {'local', atom()} | {'global', term()} | {via, atom(), term()}. -type start_ret() :: {'ok', pid()} | 'ignore' | {'error', term()}. @@ -53,7 +53,7 @@ %% start(GenMod, LinkP, Name, Mod, Args, Options) %% GenMod = atom(), callback module implementing the 'real' fsm %% LinkP = link | nolink -%% Name = {local, atom()} | {global, term()} +%% Name = {local, atom()} | {global, term()} | {via, atom(), term()} %% Args = term(), init arguments (to Mod:init/1) %% Options = [{timeout, Timeout} | {debug, [Flag]} | {spawn_opt, OptionList}] %% Flag = trace | log | {logfile, File} | statistics | debug @@ -158,9 +158,12 @@ call(Name, Label, Request, Timeout) exit(noproc) end; %% Global by name -call({global, _Name}=Process, Label, Request, Timeout) - when Timeout =:= infinity; - is_integer(Timeout), Timeout >= 0 -> +call(Process, Label, Request, Timeout) + when ((tuple_size(Process) == 2 andalso element(1, Process) == global) + orelse + (tuple_size(Process) == 3 andalso element(1, Process) == via)) + andalso + (Timeout =:= infinity orelse (is_integer(Timeout) andalso Timeout >= 0)) -> case where(Process) of Pid when is_pid(Pid) -> Node = node(Pid), @@ -274,6 +277,7 @@ reply({To, Tag}, Reply) -> %%% Misc. functions. %%%----------------------------------------------------------------- where({global, Name}) -> global:whereis_name(Name); +where({via, Module, Name}) -> Module:whereis_name(Name); where({local, Name}) -> whereis(Name). name_register({local, Name} = LN) -> @@ -287,8 +291,16 @@ name_register({global, Name} = GN) -> case global:register_name(Name, self()) of yes -> true; no -> {false, where(GN)} + end; +name_register({via, Module, Name} = GN) -> + case Module:register_name(Name, self()) of + yes -> + true; + no -> + {false, where(GN)} end. + timeout(Options) -> case opt(timeout, Options) of {ok, Time} -> diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl index 9879b76391..343eb7d4e4 100644 --- a/lib/stdlib/src/gen_event.erl +++ b/lib/stdlib/src/gen_event.erl @@ -106,8 +106,10 @@ -type add_handler_ret() :: ok | term() | {'EXIT',term()}. -type del_handler_ret() :: ok | term() | {'EXIT',term()}. --type emgr_name() :: {'local', atom()} | {'global', atom()}. --type emgr_ref() :: atom() | {atom(), atom()} | {'global', atom()} | pid(). +-type emgr_name() :: {'local', atom()} | {'global', atom()} + | {'via', atom(), term()}. +-type emgr_ref() :: atom() | {atom(), atom()} | {'global', atom()} + | {'via', atom(), term()} | pid(). -type start_ret() :: {'ok', pid()} | {'error', term()}. %%--------------------------------------------------------------------------- @@ -142,6 +144,7 @@ init_it(Starter, Parent, Name0, _, _, Options) -> name({local,Name}) -> Name; name({global,Name}) -> Name; +name({via,_, Name}) -> Name; name(Pid) when is_pid(Pid) -> Pid. -spec add_handler(emgr_ref(), handler(), term()) -> term(). @@ -208,6 +211,9 @@ call1(M, Handler, Query, Timeout) -> send({global, Name}, Cmd) -> catch global:send(Name, Cmd), ok; +send({via, Mod, Name}, Cmd) -> + catch Mod:send(Name, Cmd), + ok; send(M, Cmd) -> M ! Cmd, ok. diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl index 57734a075c..8d43846c92 100644 --- a/lib/stdlib/src/gen_fsm.erl +++ b/lib/stdlib/src/gen_fsm.erl @@ -165,7 +165,7 @@ %%% start(Name, Mod, Args, Options) %%% start_link(Mod, Args, Options) %%% start_link(Name, Mod, Args, Options) where: -%%% Name ::= {local, atom()} | {global, atom()} +%%% Name ::= {local, atom()} | {global, atom()} | {via, atom(), term()} %%% Mod ::= atom(), callback module implementing the 'real' fsm %%% Args ::= term(), init arguments (to Mod:init/1) %%% Options ::= [{debug, [Flag]}] @@ -191,6 +191,9 @@ start_link(Name, Mod, Args, Options) -> send_event({global, Name}, Event) -> catch global:send(Name, {'$gen_event', Event}), ok; +send_event({via, Mod, Name}, Event) -> + catch Mod:send(Name, {'$gen_event', Event}), + ok; send_event(Name, Event) -> Name ! {'$gen_event', Event}, ok. @@ -214,6 +217,9 @@ sync_send_event(Name, Event, Timeout) -> send_all_state_event({global, Name}, Event) -> catch global:send(Name, {'$gen_all_state_event', Event}), ok; +send_all_state_event({via, Mod, Name}, Event) -> + catch Mod:send(Name, {'$gen_all_state_event', Event}), + ok; send_all_state_event(Name, Event) -> Name ! {'$gen_all_state_event', Event}, ok. @@ -273,7 +279,10 @@ cancel_timer(Ref) -> enter_loop(Mod, Options, StateName, StateData) -> enter_loop(Mod, Options, StateName, StateData, self(), infinity). -enter_loop(Mod, Options, StateName, StateData, ServerName = {_,_}) -> +enter_loop(Mod, Options, StateName, StateData, {Scope,_} = ServerName) + when Scope == local; Scope == global -> + enter_loop(Mod, Options, StateName, StateData, ServerName,infinity); +enter_loop(Mod, Options, StateName, StateData, {via,_,_} = ServerName) -> enter_loop(Mod, Options, StateName, StateData, ServerName,infinity); enter_loop(Mod, Options, StateName, StateData, Timeout) -> enter_loop(Mod, Options, StateName, StateData, self(), Timeout). @@ -303,6 +312,15 @@ get_proc_name({global, Name}) -> Name; _Pid -> exit(process_not_registered_globally) + end; +get_proc_name({via, Mod, Name}) -> + case Mod:whereis_name(Name) of + undefined -> + exit({process_not_registered_via, Mod}); + Pid when Pid =:= self() -> + Name; + _Pid -> + exit({process_not_registered_via, Mod}) end. get_parent() -> @@ -367,12 +385,15 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> name({local,Name}) -> Name; name({global,Name}) -> Name; +name({via,_, Name}) -> Name; name(Pid) when is_pid(Pid) -> Pid. unregister_name({local,Name}) -> _ = (catch unregister(Name)); unregister_name({global,Name}) -> _ = global:unregister_name(Name); +unregister_name({via, Mod, Name}) -> + _ = Mod:unregister_name(Name); unregister_name(Pid) when is_pid(Pid) -> Pid. diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index 6f075bbe5a..244795df9f 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -142,7 +142,7 @@ %%% start(Name, Mod, Args, Options) %%% start_link(Mod, Args, Options) %%% start_link(Name, Mod, Args, Options) where: -%%% Name ::= {local, atom()} | {global, atom()} +%%% Name ::= {local, atom()} | {global, atom()} | {via, atom(), term()} %%% Mod ::= atom(), callback module implementing the 'real' server %%% Args ::= term(), init arguments (to Mod:init/1) %%% Options ::= [{timeout, Timeout} | {debug, [Flag]}] @@ -194,6 +194,9 @@ call(Name, Request, Timeout) -> cast({global,Name}, Request) -> catch global:send(Name, cast_msg(Request)), ok; +cast({via, Mod, Name}, Request) -> + catch Mod:send(Name, cast_msg(Request)), + ok; cast({Name,Node}=Dest, Request) when is_atom(Name), is_atom(Node) -> do_cast(Dest, Request); cast(Dest, Request) when is_atom(Dest) -> @@ -266,7 +269,11 @@ multi_call(Nodes, Name, Req, Timeout) enter_loop(Mod, Options, State) -> enter_loop(Mod, Options, State, self(), infinity). -enter_loop(Mod, Options, State, ServerName = {_, _}) -> +enter_loop(Mod, Options, State, ServerName = {Scope, _}) + when Scope == local; Scope == local -> + enter_loop(Mod, Options, State, ServerName, infinity); + +enter_loop(Mod, Options, State, ServerName = {via, _, _}) -> enter_loop(Mod, Options, State, ServerName, infinity); enter_loop(Mod, Options, State, Timeout) -> @@ -327,12 +334,15 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> name({local,Name}) -> Name; name({global,Name}) -> Name; +name({via,_, Name}) -> Name; name(Pid) when is_pid(Pid) -> Pid. unregister_name({local,Name}) -> _ = (catch unregister(Name)); unregister_name({global,Name}) -> _ = global:unregister_name(Name); +unregister_name({via, Mod, Name}) -> + _ = Mod:unregister_name(Name); unregister_name(Pid) when is_pid(Pid) -> Pid. @@ -827,6 +837,15 @@ get_proc_name({global, Name}) -> Name; _Pid -> exit(process_not_registered_globally) + end; +get_proc_name({via, Mod, Name}) -> + case Mod:whereis_name(Name) of + undefined -> + exit({process_not_registered_via, Mod}); + Pid when Pid =:= self() -> + Name; + _Pid -> + exit({process_not_registered_via, Mod}) end. get_parent() -> diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile index aa6a660c34..b36265302c 100644 --- a/lib/stdlib/test/Makefile +++ b/lib/stdlib/test/Makefile @@ -20,6 +20,7 @@ MODULES= \ digraph_utils_SUITE \ dummy1_h \ dummy_h \ + dummy_via \ edlin_expand_SUITE \ epp_SUITE \ erl_eval_helper \ diff --git a/lib/stdlib/test/dummy_via.erl b/lib/stdlib/test/dummy_via.erl new file mode 100644 index 0000000000..e405811cbe --- /dev/null +++ b/lib/stdlib/test/dummy_via.erl @@ -0,0 +1,94 @@ +-module(dummy_via). +-export([reset/0, + register_name/2, + whereis_name/1, + unregister_name/1, + send/2]). + + +reset() -> + P = whereis(?MODULE), + catch unlink(P), + Ref = erlang:monitor(process, P), + catch exit(P, kill), + receive {'DOWN',Ref,_,_,_} -> ok end, + Me = self(), + Pid = spawn_link(fun() -> + register(?MODULE, self()), + Me ! {self(), started}, + loop([]) + end), + receive + {Pid, started} -> + Pid + after 10000 -> + exit(timeout) + end. + +register_name(Name, Pid) when is_pid(Pid) -> + call({register_name, Name, Pid}). + +unregister_name(Name) -> + call({unregister_name, Name}). + +whereis_name(Name) -> + call({whereis_name, Name}). + +send(Name, Msg) -> + case whereis_name(Name) of + undefined -> + exit({badarg, {Name, Msg}}); + Pid when is_pid(Pid) -> + Pid ! Msg, + Pid + end. + +call(Req) -> + MRef = erlang:monitor(process, ?MODULE), + ?MODULE ! {self(), MRef, Req}, + receive + {'DOWN', MRef, _, _, _} -> + erlang:error(badarg); + {MRef, badarg} -> + erlang:error(badarg); + {MRef, Reply} -> + Reply + after 5000 -> + erlang:error(timeout) + end. + +loop(Reg) -> + receive + {'DOWN', _, _, P, _} when is_pid(P) -> + loop([X || {_,Pid,_} = X <- Reg, Pid =/= P]); + {From, Ref, Request} when is_pid(From), is_reference(Ref) -> + {Reply, NewReg} = handle_request(Request, Reg), + From ! {Ref, Reply}, + loop(NewReg) + end. + +handle_request({register_name, Name, Pid}, Reg) when is_pid(Pid) -> + case lists:keyfind(Name, 1, Reg) of + false -> + Ref = erlang:monitor(process, Pid), + {yes, [{Name, Pid, Ref}|Reg]}; + _ -> + {no, Reg} + end; +handle_request({whereis_name, Name}, Reg) -> + case lists:keyfind(Name, 1, Reg) of + {_, Pid, _} -> + {Pid, Reg}; + false -> + {undefined, Reg} + end; +handle_request({unregister_name, Name}, Reg) -> + case lists:keyfind(Name, 1, Reg) of + {_, _, Ref} -> + catch erlang:demonitor(Ref); + _ -> + ok + end, + {ok, lists:keydelete(Name, 1, Reg)}; +handle_request(_, Reg) -> + {badarg, Reg}. diff --git a/lib/stdlib/test/gen_event_SUITE.erl b/lib/stdlib/test/gen_event_SUITE.erl index b3a7edc140..5c51e12e35 100644 --- a/lib/stdlib/test/gen_event_SUITE.erl +++ b/lib/stdlib/test/gen_event_SUITE.erl @@ -62,6 +62,8 @@ start(suite) -> []; start(Config) when is_list(Config) -> OldFl = process_flag(trap_exit, true), + ?line dummy_via:reset(), + ?line {ok, Pid0} = gen_event:start(), %anonymous ?line [] = gen_event:which_handlers(Pid0), ?line ok = gen_event:stop(Pid0), @@ -85,6 +87,11 @@ start(Config) when is_list(Config) -> ?line [] = gen_event:which_handlers(Pid4), ?line ok = gen_event:stop({global, my_dummy_name}), + ?line {ok, Pid5} = gen_event:start_link({via, dummy_via, my_dummy_name}), + ?line [] = gen_event:which_handlers({via, dummy_via, my_dummy_name}), + ?line [] = gen_event:which_handlers(Pid5), + ?line ok = gen_event:stop({via, dummy_via, my_dummy_name}), + ?line {ok, _} = gen_event:start_link({local, my_dummy_name}), ?line {error, {already_started, _}} = gen_event:start_link({local, my_dummy_name}), @@ -92,15 +99,28 @@ start(Config) when is_list(Config) -> gen_event:start({local, my_dummy_name}), ?line ok = gen_event:stop(my_dummy_name), - ?line {ok, Pid5} = gen_event:start_link({global, my_dummy_name}), + ?line {ok, Pid6} = gen_event:start_link({global, my_dummy_name}), ?line {error, {already_started, _}} = gen_event:start_link({global, my_dummy_name}), ?line {error, {already_started, _}} = gen_event:start({global, my_dummy_name}), - exit(Pid5, shutdown), + exit(Pid6, shutdown), + receive + {'EXIT', Pid6, shutdown} -> ok + after 10000 -> + ?t:fail(exit_gen_event) + end, + + ?line {ok, Pid7} = gen_event:start_link({via, dummy_via, my_dummy_name}), + ?line {error, {already_started, _}} = + gen_event:start_link({via, dummy_via, my_dummy_name}), + ?line {error, {already_started, _}} = + gen_event:start({via, dummy_via, my_dummy_name}), + + exit(Pid7, shutdown), receive - {'EXIT', Pid5, shutdown} -> ok + {'EXIT', Pid7, shutdown} -> ok after 10000 -> ?t:fail(exit_gen_event) end, diff --git a/lib/stdlib/test/gen_fsm_SUITE.erl b/lib/stdlib/test/gen_fsm_SUITE.erl index d60629d841..bdb4ea65b5 100644 --- a/lib/stdlib/test/gen_fsm_SUITE.erl +++ b/lib/stdlib/test/gen_fsm_SUITE.erl @@ -21,11 +21,11 @@ -include_lib("test_server/include/test_server.hrl"). %% Test cases --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2]). --export([ start1/1, start2/1, start3/1, start4/1 , start5/1, start6/1, - start7/1, start8/1, start9/1, start10/1, start11/1]). +-export([start1/1, start2/1, start3/1, start4/1, start5/1, start6/1, + start7/1, start8/1, start9/1, start10/1, start11/1, start12/1]). -export([ abnormal1/1, abnormal2/1]). @@ -56,14 +56,14 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. -all() -> +all() -> [{group, start}, {group, abnormal}, shutdown, {group, sys}, hibernate, enter_loop]. -groups() -> +groups() -> [{start, [], [start1, start2, start3, start4, start5, start6, start7, - start8, start9, start10, start11]}, + start8, start9, start10, start11, start12]}, {abnormal, [], [abnormal1, abnormal2]}, {sys, [], [sys1, call_format_status, error_format_status]}]. @@ -258,6 +258,25 @@ start11(Config) when is_list(Config) -> test_server:messages_get(), ok. +%% Via register linked +start12(Config) when is_list(Config) -> + ?line dummy_via:reset(), + ?line {ok, Pid} = + gen_fsm:start_link({via, dummy_via, my_fsm}, gen_fsm_SUITE, [], []), + ?line {error, {already_started, Pid}} = + gen_fsm:start_link({via, dummy_via, my_fsm}, gen_fsm_SUITE, [], []), + ?line {error, {already_started, Pid}} = + gen_fsm:start({via, dummy_via, my_fsm}, gen_fsm_SUITE, [], []), + + ?line ok = do_func_test(Pid), + ?line ok = do_sync_func_test(Pid), + ?line ok = do_func_test({via, dummy_via, my_fsm}), + ?line ok = do_sync_func_test({via, dummy_via, my_fsm}), + ?line stop_it({via, dummy_via, my_fsm}), + + test_server:messages_get(), + ok. + %% Check that time outs in calls work abnormal1(suite) -> []; @@ -362,7 +381,25 @@ call_format_status(Config) when is_list(Config) -> ?line Status4 = sys:get_status(GlobalName2), ?line {status, Pid4, _Mod, [_PDict4, running, _, _, Data4]} = Status4, ?line [format_status_called | _] = lists:reverse(Data4), - ?line stop_it(Pid4). + ?line stop_it(Pid4), + + %% check that format_status can handle a name being a term other than a + %% pid or atom + ?line dummy_via:reset(), + ViaName1 = {via, dummy_via, "CallFormatStatus"}, + ?line {ok, Pid5} = gen_fsm:start(ViaName1, gen_fsm_SUITE, [], []), + ?line Status5 = sys:get_status(ViaName1), + ?line {status, Pid5, _Mod, [_PDict5, running, _, _, Data5]} = Status5, + ?line [format_status_called | _] = lists:reverse(Data5), + ?line stop_it(Pid5), + ViaName2 = {via, dummy_via, {name, "term"}}, + ?line {ok, Pid6} = gen_fsm:start(ViaName2, gen_fsm_SUITE, [], []), + ?line Status6 = sys:get_status(ViaName2), + ?line {status, Pid6, _Mod, [_PDict6, running, _, _, Data6]} = Status6, + ?line [format_status_called | _] = lists:reverse(Data6), + ?line stop_it(Pid6). + + error_format_status(Config) when is_list(Config) -> ?line error_logger_forwarder:register(), @@ -520,6 +557,8 @@ enter_loop(doc) -> enter_loop(Config) when is_list(Config) -> OldFlag = process_flag(trap_exit, true), + ?line dummy_via:reset(), + %% Locally registered process + {local, Name} ?line {ok, Pid1a} = proc_lib:start_link(?MODULE, enter_loop, [local, local]), @@ -623,10 +662,22 @@ enter_loop(Config) when is_list(Config) -> {'EXIT', Pid6b, process_not_registered_globally} -> ok after 1000 -> - ?line test_server:fail(gen_server_started) + ?line test_server:fail(gen_fsm_started) end, global:unregister_name(armitage), + dummy_via:register_name(armitage, self()), + ?line {ok, Pid6c} = + proc_lib:start_link(?MODULE, enter_loop, [anon, via]), + receive + {'EXIT', Pid6c, {process_not_registered_via, dummy_via}} -> + ok + after 1000 -> + ?line test_server:fail({gen_fsm_started, process_info(self(), + messages)}) + end, + dummy_via:unregister_name(armitage), + process_flag(trap_exit, OldFlag), ok. @@ -635,6 +686,7 @@ enter_loop(Reg1, Reg2) -> case Reg1 of local -> register(armitage, self()); global -> global:register_name(armitage, self()); + via -> dummy_via:register_name(armitage, self()); anon -> ignore end, proc_lib:init_ack({ok, self()}), @@ -643,6 +695,9 @@ enter_loop(Reg1, Reg2) -> gen_fsm:enter_loop(?MODULE, [], state0, [], {local,armitage}); global -> gen_fsm:enter_loop(?MODULE, [], state0, [], {global,armitage}); + via -> + gen_fsm:enter_loop(?MODULE, [], state0, [], + {via, dummy_via, armitage}); anon -> gen_fsm:enter_loop(?MODULE, [], state0, []) end. diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl index 7fb8d54f2d..cdf15ba017 100644 --- a/lib/stdlib/test/gen_server_SUITE.erl +++ b/lib/stdlib/test/gen_server_SUITE.erl @@ -36,7 +36,7 @@ ]). % spawn export --export([spec_init_local/2, spec_init_global/2, +-export([spec_init_local/2, spec_init_global/2, spec_init_via/2, spec_init_default_timeout/2, spec_init_anonymous/1, spec_init_anonymous_default_timeout/1, spec_init_not_proc_lib/1, cast_fast_messup/0]). @@ -199,6 +199,35 @@ start(Config) when is_list(Config) -> test_server:fail(not_stopped) end, + %% via register + ?line dummy_via:reset(), + ?line {ok, Pid6} = + gen_server:start({via, dummy_via, my_test_name}, + gen_server_SUITE, [], []), + ?line ok = gen_server:call({via, dummy_via, my_test_name}, started_p), + ?line {error, {already_started, Pid6}} = + gen_server:start({via, dummy_via, my_test_name}, + gen_server_SUITE, [], []), + ?line ok = gen_server:call({via, dummy_via, my_test_name}, stop), + test_server:sleep(1), + ?line {'EXIT', {noproc,_}} = (catch gen_server:call(Pid6, started_p, 10)), + + %% via register linked + ?line dummy_via:reset(), + ?line {ok, Pid7} = + gen_server:start_link({via, dummy_via, my_test_name}, + gen_server_SUITE, [], []), + ?line ok = gen_server:call({via, dummy_via, my_test_name}, started_p), + ?line {error, {already_started, Pid7}} = + gen_server:start({via, dummy_via, my_test_name}, + gen_server_SUITE, [], []), + ?line ok = gen_server:call({via, dummy_via, my_test_name}, stop), + ?line receive + {'EXIT', Pid7, stopped} -> + ok + after 5000 -> + test_server:fail(not_stopped) + end, test_server:messages_get(), %% Must wait for all error messages before going to next test. @@ -853,6 +882,8 @@ otp_5854(doc) -> otp_5854(Config) when is_list(Config) -> OldFlag = process_flag(trap_exit, true), + ?line dummy_via:reset(), + %% Make sure gen_server:enter_loop does not accept {local,Name} %% when it's another process than the calling one which is %% registered under that name @@ -881,6 +912,18 @@ otp_5854(Config) when is_list(Config) -> end, global:unregister_name(armitage), + %% (same for {via, Mod, Name}) + dummy_via:register_name(armitage, self()), + ?line {ok, Pid3} = + start_link(spec_init_via, [{not_ok, armitage}, []]), + receive + {'EXIT', Pid3, {process_not_registered_via, dummy_via}} -> + ok + after 1000 -> + ?line test_server:fail(gen_server_started) + end, + dummy_via:unregister_name(armitage), + process_flag(trap_exit, OldFlag), ok. @@ -1060,7 +1103,22 @@ spec_init_global({not_ok, Name}, Options) -> %% Supervised init can occur here ... gen_server:enter_loop(?MODULE, Options, {}, {global, Name}, infinity). -spec_init_default_timeout({ok, Name}, Options) -> +spec_init_via({ok, Name}, Options) -> + process_flag(trap_exit, true), + dummy_via:register_name(Name, self()), + proc_lib:init_ack({ok, self()}), + %% Supervised init can occur here ... + gen_server:enter_loop(?MODULE, Options, {}, + {via, dummy_via, Name}, infinity); + +spec_init_via({not_ok, Name}, Options) -> + process_flag(trap_exit, true), + proc_lib:init_ack({ok, self()}), + %% Supervised init can occur here ... + gen_server:enter_loop(?MODULE, Options, {}, + {via, dummy_via, Name}, infinity). + +spec_init_default_timeout({ok, Name}, Options) -> process_flag(trap_exit, true), register(Name, self()), proc_lib:init_ack({ok, self()}), -- cgit v1.2.3