Reentrant gen_server

Ulf Wiger (AL/EAB) <>
Tue May 31 14:50:57 CEST 2005


Why do you want to make the gen_server reentrant
in the first place?

You can implement objects in different ways in 
Erlang, but which way to go depends on what type
of object you're dealing with.

One of the main benefits you get from using a server
process is _exactly_ the behaviour you're currently 
trying to get around -- namely that it serializes calls,
and thereby solves many concurrency problems.

Another way to implement objects is to provide a module
with a set of APIs that operate on an abstract data type.
The 'queue' module is a good example of this. Such a 
module is reentrant by nature. The thing that makes this
safe and efficient is that there is no shared data.

Shared data can be nicely abstracted using processes,
but then those processes should be designed so that they
serialize accesses, rather than being reentrant.

Regards,
Uffe

> -----Original Message-----
> From: 
> [mailto:]On Behalf Of Héctor Rivas
> Gándara
> Sent: den 31 maj 2005 13:57
> To: ''
> Subject: Reentrant gen_server
> 
> 
> Hi,
> 
> I'm developting a behaviour that implements objects as 
> process. It is  
> based on gen_server, but I want to hide the gen_server API.
> 
> This object will be eventually called reentrat: the object calls a  
> function passing the This reference as parameter and the 
> funcion calls  
> the object. The problem is that the object is a gen_server so 
> it do not  
> process incoming messages if its handling a call.
> 
> So: I need to build a server that can be reentrat, I mean, a 
> gen_server  
> that will call himself using gen_server:call.
> 
> I could use the {noreply, ...} gen_server:reply() solution, 
> but I think  
> that is too complex and ugly take care of call the gen_server:reply.  
> And the worst, sometimes the reentrant call perform changes in the  
> state of the object that are needed before send the reply.
> 
> So I propose this solution:
> 	- Spawn a caller process that really executes the call
> 	- Spawn a fake server proccess that will run the server 
> loop using  
> gen_server:enter_loop(),
> 	- The server process will redirect all incoming 
> messages to fake  
> server until the caller process does not return.
> 
> Suggestion? ideas? posible problems?
> 
> You can test it with the following code:
> 
> 1> recurrent:start().
> {ok,<0.31.0>}
> 2> recurrent:do_something(recurrent).
> Received: {something,1,2,3}, From: {<0.29.0>,#Ref<0.0.0.33>}, State:  
> {recurrent,
>                                                               
>             
>   1,
>                                                               
>             
>   2}
> ok
> 3> recurrent:do_something_concurrent(recurrent).
> entering loop <0.34.0> <0.35.0>
> Doing something... calling server...redirecting  
> {'$gen_call',{<0.34.0>,#Ref<0.0.0.41>},{otherthing,1,2,3}}
> entering loop <0.34.0> <0.35.0>
> Received: {otherthing,1,2,3}, From: 
> {<0.34.0>,#Ref<0.0.0.41>}, State:  
> {recurrent,
>                                                               
>             
>    1,
>                                                               
>             
>    2}
> server called.
> ok
> 4> recurrent:do_something(recurrent).
> Received: {something,1,2,3}, From: {<0.29.0>,#Ref<0.0.0.50>}, State:  
> {recurrent,
>                                                               
>             
>   1,
>                                                               
>             
>   2}
> ok
> 5> recurrent:do_something(recurrent).
> Received: {something,1,2,3}, From: {<0.29.0>,#Ref<0.0.0.55>}, State:  
> {recurrent,
>                                                               
>             
>   1,
>                                                               
>             
>   2}
> ok
> 6>
> 
> Code:
> 
> ----- 8< ------- 8< ------- 8< ------- 8< ------- 8< ------- 
> 8< -------  
> 8< ------- 8< ------- 8< -------
> 
> -module(recurrent).
> -behaviour(gen_server).
> 
> %~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> ~~~~~~~~~~ 
> ~~~~~~~
> % genserver exports
> -export([init/1, handle_call/3, handle_cast/2,
> 		 handle_info/2, terminate/2, code_change/3]).
> 
> % Public exports
> -export([start/0, do_something/1, do_otherthing/1,  
> do_something_concurrent/1, a_function_with_callback/1]).
> 
> -record(?MODULE, {
> 			value1 = 1,
> 			value2 = 2
> 			}).
> 
> start() ->
> 	gen_server:start({local, ?MODULE}, ?MODULE, [], []).
> 
> do_something_concurrent(Server) ->
> 	gen_server:call(Server, {something_concurrent, 1, 2, 3}, 1000).
> do_something(Server) ->
> 	gen_server:call(Server, {something, 1, 2, 3}, 1000).
> do_otherthing(Server) ->
> 	gen_server:call(Server, {otherthing, 1, 2, 3}, 1000).
> 
> % This function gets the server pid  and executes something on it...
> a_function_with_callback(Server) ->
> 	io:format("Doing something... calling server...",[]),
> 	% recurrent call
> 	do_otherthing(Server),
> 	io:format("server called.~n", []).
> 
> 
> %~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> ~~~~~~~~~~ 
> ~~~~~~~
> % private API |
> %~~~~~~~~~~~~~'
> 
> % Here is the magic. This function spawns a process which 
> will execute  
> the real call
> noblocking_apply(Module, Function, Arguments, State) ->
> 	ServerPid = self(),
> 	
> 	% Function executed by the process that really will call
> 	ConcurrentCall = fun() ->
> 		% Execute the function
> 		Reply = (catch apply(Module, Function, Arguments)),
> 		% send reply to server pid
> 		ServerPid!{return_concurrent, self(), Reply}
> 	end,
> 	
> 	ConcurrentPid = spawn_link(ConcurrentCall),
> 
> 	% Launch a fake server where redirect all incoming messages
> 	FakeServerPid = proc_lib:spawn_link(gen_server, 
> enter_loop, [?MODULE,  
> [], State]),
> 
> 	% Redirect all incoming messagees
> 	noblocking_loop(ConcurrentPid, FakeServerPid).
> 
> % This loop will redirect all incoming messages until 
> return_concurrent  
> message is received.
> noblocking_loop(ConcurrentPid, FakeServerPid) ->
> 	io:format("entering loop ~p ~p~n", [ConcurrentPid, 
> FakeServerPid]),
> 	receive
> 		{return_concurrent, ConcurrentPid, Reply} ->
> 			{Reply, gen_server:call(FakeServerPid, 
> return_concurrent)};
> 		Other ->
> 			io:format("redirecting ~p~n", [Other]),
> 			FakeServerPid!Other,
> 			noblocking_loop(ConcurrentPid, FakeServerPid)
> 	end.
> 
> 
> 	
> %~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> ~~~~~~~~~~ 
> ~~~~~~~
> % gen_server callbacks |
> %~~~~~~~~~~~~~~~~~~~~~~'
> 
> init(_) ->
> 	{ok, #?MODULE{}}.
> 
> % The concurrent call returned, stop this loop and return state
> handle_call(return_concurrent, _From, State) ->
> 	{stop, normal, State, State};
> 
> handle_call({something_concurrent, _X, _Y, _Z}, _From, State) ->
> 	{Reply, NewState} = noblocking_apply(?MODULE,  
> a_function_with_callback, [self()], State),
> 	{reply, Reply, NewState};
> 
> handle_call(Msg, From, State) ->
> 	io:format("Received: ~p, From: ~p, State: ~p~n", [Msg, 
> From, State]),
> 	{reply, ok, State}.
> 
> handle_cast(_, State) -> {noreply, State}.
> 
> terminate(Reason, _) -> Reason.
> 
> handle_info(_, State) -> {noreply, State}.
> 
> code_change(_, State, _) -> {ok, State}.
> 
> ----- 8< ------- 8< ------- 8< ------- 8< ------- 8< ------- 
> 8< -------  
> 8< ------- 8< ------- 8< -------
> 
> 
> --
> Greets
> Keymon
> 
> 



More information about the erlang-questions mailing list