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