| Author |
Message |
|
| jim at lutefisk.jetcafe.o |
Posted: Fri Sep 17, 1999 12:03 am |
|
|
|
Guest
|
I'm using using open-source Erlang (erlang_base-47.4.1) on FreeBSD
2.2.8.
I'm writing a server S1 using the gen_server behaviour. One of
the handle_call() clauses makes a "tail call" to server S2 (which
also uses gen_server), i.e.
handle_call(Request, From, State) ->
%% ... some pre-processing goes on ...
Reply = gen_server:call(S2, Arg),
{reply, Reply, NewState}
However, the call to S2 may block for a long period of time, thus
blocking all calls to S1 in the interim.
What would be nice is a "tail call" function in the gen_server
package that would allows you to forward the From "continuation"
to another gen_server module, but returns to its caller immediately.
handle_call(Request, From, State) ->
%% ... the same pre-processing goes on ...
%% This call won't block
case gen_server:tail_call(S2, Arg, From) of
ok ->
{noreply, NewState}; % S2 will reply to our caller
{error, Reason} ->
%% error looking up S2
{reply, {error, SomeReason}, SomeState}
end.
The implementation for the simplest clause of tail_call would be
something like:
tail_call(Pid, Request, From) when pid(Pid), node(Pid) == node() ->
Pid ! {'$gen_call', From, Request},
ok.
[Okay, so it doesn't quite handle all error cases, since the original
gen_server caller will only catch 'EXIT' messages from the Pid of
the server it originally called. I'm sure there are ways to deal
with this.]
I realize that there are other workarounds, including:
- returning appropriate information from S1, and
allowing the original client process to call S2;
- having S1 spawn a new process to make the blocking call;
- explicitly passing the From "continuation" as an
argument to a special method of S2.
However, these workarounds are unappealing. Am I overlooking any
other options or existing infrastructure for accomplishing what I
want?
I await the comments of those wiser in the ways of Erlang than I.
Jim
Post generated using Mail2Forum (http://m2f.sourceforge.net) |
|
|
| Back to top |
|
| cesarini at csd.uu.se |
Posted: Fri Sep 17, 1999 6:41 am |
|
|
|
Guest
|
Hi Jim,
if you save the From argument in your handle_call function, you can
later use it in the gen_server:reply(From, Reply) function call (When S2
asynchronously replies to S1's query). The preferred solution, however
is to pass the From argument to S2 (Using an asynchronous function) so
as to make S2 call the gen_server:reply/2 function.
e.g.
in S1:
handle_call(Request, From, State) ->
%% ... some pre-processing goes on ...
Reply = gen_server:cast(S2, {From,Arg}),
{noreply, NewState}
in S2:
handle_cast({From, Arg}, State) ->
Reply = whatever(State, Arg),
gen_server:reply(From, Reply),
{noreply, State}.
Cheers,
Francesco
Jim Larson wrote:
>
> I'm using using open-source Erlang (erlang_base-47.4.1) on FreeBSD
> 2.2.8.
>
> I'm writing a server S1 using the gen_server behaviour. One of
> the handle_call() clauses makes a "tail call" to server S2 (which
> also uses gen_server), i.e.
>
> handle_call(Request, From, State) ->
> %% ... some pre-processing goes on ...
> Reply = gen_server:call(S2, Arg),
> {reply, Reply, NewState}
>
> However, the call to S2 may block for a long period of time, thus
> blocking all calls to S1 in the interim.
>
> What would be nice is a "tail call" function in the gen_server
> package that would allows you to forward the From "continuation"
> to another gen_server module, but returns to its caller immediately.
>
> handle_call(Request, From, State) ->
> %% ... the same pre-processing goes on ...
> %% This call won't block
> case gen_server:tail_call(S2, Arg, From) of
> ok ->
> {noreply, NewState}; % S2 will reply to our caller
> {error, Reason} ->
> %% error looking up S2
> {reply, {error, SomeReason}, SomeState}
> end.
>
> The implementation for the simplest clause of tail_call would be
> something like:
>
> tail_call(Pid, Request, From) when pid(Pid), node(Pid) == node() ->
> Pid ! {'$gen_call', From, Request},
> ok.
>
> [Okay, so it doesn't quite handle all error cases, since the original
> gen_server caller will only catch 'EXIT' messages from the Pid of
> the server it originally called. I'm sure there are ways to deal
> with this.]
>
> I realize that there are other workarounds, including:
>
> - returning appropriate information from S1, and
> allowing the original client process to call S2;
>
> - having S1 spawn a new process to make the blocking call;
>
> - explicitly passing the From "continuation" as an
> argument to a special method of S2.
>
> However, these workarounds are unappealing. Am I overlooking any
> other options or existing infrastructure for accomplishing what I
> want?
>
> I await the comments of those wiser in the ways of Erlang than I.
>
> Jim
Post generated using Mail2Forum (http://m2f.sourceforge.net) |
|
|
| Back to top |
|
| etxuwig at etxb.ericsson. |
Posted: Fri Sep 17, 1999 8:40 am |
|
|
|
Guest
|
On Thu, 16 Sep 1999, Jim Larson wrote:
jim>What would be nice is a "tail call" function in the gen_server
jim>package that would allows you to forward the From "continuation"
jim>to another gen_server module, but returns to its caller immediately.
jim>
jim> handle_call(Request, From, State) ->
jim> %% ... the same pre-processing goes on ...
jim> %% This call won't block
jim> case gen_server:tail_call(S2, Arg, From) of
jim> ok ->
jim> {noreply, NewState}; % S2 will reply to our caller
jim> {error, Reason} ->
jim> %% error looking up S2
jim> {reply, {error, SomeReason}, SomeState}
jim> end.
jim>
jim>The implementation for the simplest clause of tail_call would be
jim>something like:
jim>
jim> tail_call(Pid, Request, From) when pid(Pid), node(Pid) == node() ->
jim> Pid ! {'$gen_call', From, Request},
jim> ok.
What you're proposing is quite doable in erlang 47.4.0, but becomes tricky
in the commercially available R5B release. I'll explain why below.
Another way to do it (as you also point out) would be to let the client
function handle the redirect:
req(To, Request) ->
case gen_server:call(To, Request) of
{redirect, NewTo, NewRequest} ->
req(NewTo, NewRequest);
Reply ->
Reply
end.
It leads to a few more messages, but unless you have extreme response-time
requirements, or the calls are passed across node limits, it probably
doesn't matter much. Since it's common practice to implement API functions
as "wrappers" around the gen_server:call() (don't let the implementation
show in the interface), the client won't know that it's been redirected.
jim>[Okay, so it doesn't quite handle all error cases, since the original
jim>gen_server caller will only catch 'EXIT' messages from the Pid of
jim>the server it originally called. I'm sure there are ways to deal
jim>with this.]
Actually, in erlang-47.4.0, the client only detects timeouts and nodedowns
in gen:call(). However, in R5B (the latest commercial release), and in the
upcoming Open Source release, gen:call() uses a new one-way monitor
function to make sure the server doesn't crash while processing the call.
This is needed in order to avoid unnecessary latency.
The problem, from your perspective, becomes that the client will continue
to monitor the first server, even though the call is passed to another
server. A function like gen_server:tail_call() wouldn't be able to do much
about that, since it would execute in a process other than the one doing
the monitoring.
jim>However, these workarounds are unappealing. Am I overlooking any
jim>other options or existing infrastructure for accomplishing what I
jim>want?
It's difficult to tell without knowing the specifics of the problem, but
sometimes it's possible to let the client to part of the pre-processing.
You can also let the client read an ETS table (e.g. maintained by S1) in
order to determine which server to address.
/Uffe
Ulf Wiger, Chief Designer AXD 301 <ulf.wiger_at_etx.ericsson.se>
Ericsson Telecom AB tfn: +46 8 719 81 95
Varuv |
|
|
| Back to top |
|
| cesarini at erlang.ericss |
Posted: Fri Sep 17, 1999 10:15 am |
|
|
|
Guest
|
> jim>[Okay, so it doesn't quite handle all error cases, since the original
> jim>gen_server caller will only catch 'EXIT' messages from the Pid of
> jim>the server it originally called. I'm sure there are ways to deal
> jim>with this.]
>
> Actually, in erlang-47.4.0, the client only detects timeouts and nodedowns
> in gen:call(). However, in R5B (the latest commercial release), and in the
> upcoming Open Source release, gen:call() uses a new one-way monitor
> function to make sure the server doesn't crash while processing the call.
> This is needed in order to avoid unnecessary latency.
>
> The problem, from your perspective, becomes that the client will continue
> to monitor the first server, even though the call is passed to another
> server. A function like gen_server:tail_call() wouldn't be able to do much
> about that, since it would execute in a process other than the one doing
> the monitoring.
The above isn't an issue if you place S1 and S2 in the same supervisor
tree with a one-for-all or rest-for-one relationship (Where S2 is started
before S1) (Or have an application coordinator triggering a restart if
they are in different applications). As there is a clear dependency
between S1 and S2, if S1 exits abnormally, S2 should also be terminated
and restarted.
It is obvious that different process/application types have different
robustness requirements. I have however always pushed the idea that one
should always try to keep the restart strategy as simple as possible,
avoiding the handling of all special cases which can occur (And
decentralize in a generic module if some special cases have to be catered
for as they occur often), as these work arounds often lead to new bugs
instead of solving their predefined tasks. Thereof, if S2 exits, make S1
exit as well, and restart them.
Cheers,
Francesco
Post generated using Mail2Forum (http://m2f.sourceforge.net) |
|
|
| Back to top |
|
|
|
All times are GMT
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum You cannot attach files in this forum You cannot download files in this forum
|
|
|