[erlang-questions] Is using gen_server behaviour appropriate for this usecase?

49 views
Skip to first unread message

Vineet Naik

unread,
Nov 30, 2012, 3:25:38 AM11/30/12
to erlang-q...@erlang.org
Hello,

I am an Erlang newbie and trying to write a simple chat bot as an
XMPP external component. I am using exmpp library and following
along these tutorials[1]

So far I have one module `bot_server` that uses the gen_server
interface. Inside it's `handle_info` callback, incoming messages
from various client will be received. To handle and reply to the
these messages, I am thinking of spawning a "bot" process per
client. It will stay alive as long as the client is available
ie. when the client sends "unavailable" presence, it will die. I
also need to keep a list of all the alive bot processes in the
bot_server's state.

My question is, would it be appropriate to implement the bot as a
gen_server too considering that it needs to handle two calls, one
for handling incoming message (asynchronous) and second for
killing itself (synchronous)?

In general, when should one use gen_server and when should
one write a simple loop function?

[1] exmpp tutorials:
http://blog.process-one.net/scalable_xmpp_bots_with_erlang_and_exmpp_part_i/

Thanks,
Vineet
_______________________________________________
erlang-questions mailing list
erlang-q...@erlang.org
http://erlang.org/mailman/listinfo/erlang-questions

Roman Gafiyatullin

unread,
Nov 30, 2012, 3:36:35 AM11/30/12
to Vineet Naik, erlang-q...@erlang.org
Hi,

In most of the cases one shall not use "simple loop functions": raw process cannot participate properly in the supervision tree.

If you need some new tricky specific model of behaving - better implement it over gen_server. 
For instance supervisor is a gen_server :)

If you need something more low level - use 'gen' module (see how gen_fsm is implemented).

--
Regards
RG ( +375 33 602 5080, UTC+3 )

Vineet Naik

unread,
Nov 30, 2012, 4:49:27 AM11/30/12
to Roman Gafiyatullin, erlang-q...@erlang.org
Hi Roman,

Thanks for replying.

I think gen_server pretty much fits my case. But I would like to
know whether it's fine to use gen_server if the model is not
strictly based on client-server message passing. Does Erlang have
any thing like "abuse of the gen_server" anti-pattern :-) ?

Regards,
Vineet
--
Vineet Naik

Roman Gafiyatullin

unread,
Nov 30, 2012, 5:43:49 AM11/30/12
to Vineet Naik, erlang-q...@erlang.org
Vineet,

Using standard OTP behaviours for handling processes is not a bad practice.
It helps to always have unified predictable behaviour of the processes you spawn.

 
I will just enumerate several examples hoping those would be helpful for you:
* process A queries process B for something:
 [in A] 
{ok, Result} = gen_server:call( B, {request, Something}, CallTimeout ).

* procerss A asks B to do something but does not expect any response on this action:
 [in A] 
ok = gen_server:cast( B, {ack, ID} ).

* process A queries B for some results.
 Yet the query cannot be satisfied but will be once some event will be reported to B

 [in A]
it_occurred = gen_server:call( B, i_will_wait_for_a_while, infinity ).

 [in B]
handle_call( i_will_wait_for_a_while, From, State = #s{ reply_queue = Q } ) ->
 {no_reply, State #s{reply_queue = queue:in( From , Q )} };
handle_cast( the_event_i_told_you_about,  State = #s{ reply_queue = Q }) ->
{ReplyTo, NewQ} = queue:out( Q ),
 _Ignored = gen_server:reply( ReplyTo, it_occured  ),
{no_reply, State #s{ reply_queue = NewQ }};
...
In this very case it's probably better to think about employing gen_fsm behaviour - to handle the states like probably 'starving', 'caught_up', 'lagging' etc…

* Say we are handling some named resources each with one process.
   A client code if wants to interact with the resource needs to get the pid of the process owning the resource.
   A client code knows the name of the resource only.

resource_owner.erl:
maybe_start_resource_owner( ResourceName ) ->
case catch supervisor:start_link( resource_owners_sup , { ResourceName }) of
{ ok, Pid } -> ok;
ignore -> ok
end.

init({ ResourceName }) ->
case catch gproc:add_local_name( {resource_owner, ResourceName} ) of
true ->
{ok, #s{ resource_name = ResourceName }};
_ ->
ignore
end.

client_code.erl:
get_owner_pid_by_resource_name( ResourceName ) ->
ok = resource_owner:maybe_start_resource_owner( ResourceName ),
{ResourceOwnerPid, _} = gproc:await( {n, l, {resource_owner, ResourceName} } ),
{ok, ResourceOwnerPid}.

The main moral here - use the standard behaviours when spawning your processes. 
The OTP team has solved most of the potential problems for you already :)

--
RG

Jachym Holecek

unread,
Nov 30, 2012, 5:56:53 AM11/30/12
to Roman Gafiyatullin, erlang-q...@erlang.org
# Roman Gafiyatullin 2012-11-30:
> The main moral here - use the standard behaviours when spawning your processes.
> The OTP team has solved most of the potential problems for you already :)

The OTP has provided behaviours capturing generic patterns that one encouters
quite often in practice. When the problem at hand fits one such pattern and
nothing in the implementation of the behaviour is playing against desired
operational characteristics that you're working towards, great, standard
behaviour is a good fit.

In other cases though you'll want to create your own generic behaviour.
In other cases you'll want to proc_lib:spawn_link/X your own thing.
In other cases you'll want to erlang:spawn_link/X your own thing.
In some cases you'll want a short-lived one-off process.
In other cases you'll want a hand-written control loop.
Sometimes you want to put these under supervisor, other times you don't.
Repeat the above for non-linking ways of spawning processes.

Erlang provides concurrency primitives as part of the base language, OTP
libraries provide various degrees of elaboration on top of those primitives.
There is a wealth of tools available -- this is not a result OTP designer's
indecisiveness, it is intentional. They are all legitimate tools and each
solves a different problem. Differences between them may be subtle and not
apparent at first, it's good to play around and try different approaches.

Just my 2p, really...

BR,
-- Jachym

Garrett Smith

unread,
Nov 30, 2012, 8:48:54 AM11/30/12
to Vineet Naik, erlang-q...@erlang.org
Roman is spot on correct.

There's a school of thought that says new developers should learn core
Erlang and master it before tackling OTP. IMHO this is wrong. OTP *is*
core Erlang at this point.[1]

Just use OTP, right away. If the many moving parts of the standard OTP
behaviors seems like a steep learning curve, take a look at e2:

http://e2project.org

e2 is a veneer on top of OTP that, at least in my opinion (I wrote it
for this reason :) makes canonical development a lot easier. You might
start by reading/following the tutorial:

http://e2project.org/tutorial.html

The tutorial has more to do with general application development in
Erlang than it does in with the specifics of the e2 library.

Garrett

[1] There's a long history of Erlang that separates the core from OTP.
I understand how books, tutorials, teaching philosophies, and common
wisdom would treat OTP as an advanced topic. But as a relative
newcomer to Erlang, not having any historical context, etc. I found
this treatment unhelpful in my own learning. IMO "gen_server" (e2
calls these "services" and "tasks" depending on the scenario) is a
starting point, after one has learned the basics of the language
itself.

Vineet Naik

unread,
Nov 30, 2012, 11:44:26 AM11/30/12
to Garrett Smith, erlang-q...@erlang.org
@Roman
Thanks for the detailed explanation, those examples were really helpful

@Jachym
Thanks for replying. I am only half way through "Learn you some Erlang" and 
picking up patterns from whatever I learnt so far from there. At this 
moment I don't mind frameworks making decisions for me. But I totally 
understand your point..

@Garret
Thanks, I will give e2 a try. 
--
Vineet Naik

Robert Virding

unread,
Dec 1, 2012, 9:27:36 PM12/1/12
to Garrett Smith, erlang-q...@erlang.org
Sorry, I don't agree with you here. While using OTP is generally a Good Thing, using OTP because you know WHY you need to use use it is a Much Much Much Better Thing! You see cases of where people have blindly used gen_servers then struggle and produce convoluted code to get around the very features which gen_servers provide. So I would say that you should know what the basic Erlang provides and how you use it, know what OTP provides and how you use it and choose that which is closest to what you need.

I am not saying that you shouldn't use OTP, I am saying you should know why you are using OTP.

Actually it is not very difficult to roll your own processes which fit into OTP supervision trees. It is done using the proc_lib and sys modules and it describe quite well here http://www.erlang.org/doc/design_principles/spec_proc.html .

Robert

Ali Sabil

unread,
Dec 2, 2012, 12:06:22 PM12/2/12
to Robert Virding, Erlang
Hi all,

In case this would be useful, sometimes ago I put together a behavior module for implementing generic processes that fit into the OTP supervision trees. I didn't have much time to document it yet, but it's very stable and has been used in production. You can find the code here: https://github.com/asabil/gen_process

Garrett Smith

unread,
Dec 2, 2012, 12:41:17 PM12/2/12
to Robert Virding, erlang-q...@erlang.org
I'm not advocating that one should use proc_lib/gen_server facilities
blindly -- or incorrectly :)

The OP asked about roll-your-own message loops vs OTP -- and I'd cite
that as an example of why we should teach OTP as fundamental to
correct Erlang application development.

My motivation for writing e2 was that I observed new users struggling
with the complexity of the gen_server interface (primarily) and, given
the importance of it, wanted to make it easier to use correctly.

But, as you suggest, we should perhaps talk more about proc_lib and
how to use it when you're tempted to otherwise run a process outside
OTP facilities.
Reply all
Reply to author
Forward
0 new messages