Reentrant gen_server

Héctor Rivas Gándara keymon@REDACTED
Tue May 31 13:56:37 CEST 2005


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