An Introduction to gen_server: “ErlyBank”

Posted by on September 06, 2008

This is the first article in a series of articles introducing all the concepts pertaining to Erlang/OTP. In order to find all these articles later, they are tagged with a unique tag to the series: otp introduction. As promised in my initial introduction to OTP, we will be making a server to handle the fake bank accounts of people at “ErlyBank” (Yes, I enjoy coming up with silly names).

The Scenario: ErlyBank is just starting and they want to start off on the right foot by creating a scalable system for managing the bank accounts of its important customer-base. Having heard about the powers of Erlang, we have been hired to do that! But to test us out, they first want a simple server to handle the creation and destruction of accounts, depositing money, and withdrawing money. They just want a prototype, not something they’ll actually be putting into production.

The Result: We will create a simple server and client to do everything they asked using gen_server. Since its just a prototype, we’ll store the account information in memory for now. The accounts will only be referenced by name, no other information is necessary to create an account. And of course we’ll have validation on withdrawals and deposits.

Note: Before beginning this series, I am assuming you have a basic knowledge of the Erlang syntax. If not, I recommend reading my compendium of beginner erlang resources to find a place you can learn Erlang.

If you’re ready, click read more below to get started! (Unless you’re viewing the whole article already :) )

What is gen_server?

gen_server is a behavior module for implementing client-server relationships. By using this OTP module, you’re given a lot of things “for free,” as I’ll explain later. Also, later in the series, when I talk about supervisors and error reporting, this module will fit right in without many changes.

Since gen_server is a behavior, you’re expected to adhere to a certain number of callbacks. These callbacks, in no particular order, are:

  • init/1 - Initializes the server.
  • handle_call/3 - Handles a call made to the server. The client who called the server is blocked until you return from this method.
  • handle_cast/2 - Handles a cast made to the server. A cast is similar to a call except that it is asynchronous; the client continues while this cast is running.
  • handle_info/2 - A sort of “catch all” method. If a message is sent to the server process that isn’t a call or a cast, it will come here. An example of such a message is an EXIT process message if your server is linked to any other process.
  • terminate/2 - Called while the server is stopping, so any cleanup can be done.
  • code_change/3 - Called when the server is being upgraded in real-time. This method will be covered in much greater detail in future articles, but you must at least put the stub of the method into your module to adhere to the behavior.

The Server Skeleton

To start off every gen_server file, I always use a common skeleton. If you can, create a snippet or something with your text editor to paste the skeleton. The skeleton can be viewed here. Please take a quick look at it.

Note: To save space, I won’t ever paste the entire file here in this post, I will try to link to the whole thing when such a change occurs. But for the most part, I will just be pasting in snippets.

As you can see, the module is named eb_server, for “ErlyBank Server.” It implements all the callback methods I mentioned above and also adds one more: start_link/0. This will be the method used to start the server. I’ve pasted this portion of the code below:

start_link() ->
  gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

 
This calls the start_link/4 method of gen_server, which starts the server and registers the process to the atom defined by the macro SERVER, which by default is just the module name. The remaining arguments are the module of the gen_server, in this case itself, any arguments, and finally any options. We leave the arguments blank because we don’t need any and we don’t specify any options. For more information, an in-depth description of the method is available on the gen_server manual page.

Initializing The ErlyBank Server

Once the gen_server:start_link method is called, it calls the init method of the actual server. It is in this method you should initialize any state for the server. State can be anything you want: An atom, a list of values, a fun, anything! This state is passed around in every callback to the server. In this case, we’d like to maintain a list of all accounts and their account balance. Additionally, we’d like to look up these accounts by name. To do this, I’m going to use the dict module, which stores key-value pairs in memory.

Note to Object-Oriented Minds: If you come from an OOP language, you can think of the server state as its instance variables. In every gen_server callback, you’ll have these instance variables available, and you can change them, too.

So here is my final init method:

init(_Args) ->
  {ok, dict:new()}.

 
Really, its that simple! One of the return values expected for init is {ok, State} so for the ErlyBank server, I’m just returning ok, and an empty dictionary for the state. And we don’t use the arguments to init (which is an empty list anyway, remember, from start_link), so I just prepend the argument with an underscore to note that.

To Call or To Cast, That is the Question

Before we implement the bulk of the server, I want to quickly reiterate the difference between a call and a cast.

A call to the gen_server is a blocking method for the client. This means that when the client sends a message to the server, it waits for a response before moving on. You use a call when you need a response, such as if you’re asking what the balance of an account is.

A cast to the gen_server is a non-blocking or asynchronous method. This means that when the client sends a message to the server, it continues on, without receiving a response. Now, Erlang guarantees that all messages sent to a process are received, so unless you explicitly need a response value from the server, you should cast, as it will keep your client executing. That is to say, you don’t need to make a “call” to the server if you simply want a response to make sure the server received the message, since Erlang guarantees this.

Bank Account Creation

First things first, ErlyBank needs a way to create new accounts. Quickly, test yourself: If you were creating a bank account, would you cast or call to the server? Think hard… what value would you need returned? If you said call you’re correct, normally anyways. You usually want to make sure the account was created successfully without just assuming it was so. But in this case, I’m going to implement it as a cast, since we won’t be error checking for now.

First, I’m going to create the API method, the method outside modules will call to create an account:

%%--------------------------------------------------------------------
%% Function: create_account(Name) -> ok
%% Description: Creates a bank account for the person with name Name
%%--------------------------------------------------------------------
create_account(Name) ->
  gen_server:cast(?SERVER, {create, Name}).

 

This sends a cast to the server, which we registered as ?SERVER in start_link. The request is a tuple {create, Name}. Since this is a cast, it returns immediately with “ok,” which is also returned from the function.

Now, we need to write the part to handle this cast, which is a callback for the gen_server:

handle_cast({create, Name}, State) ->
  {noreply, dict:store(Name, 0, State)};
handle_cast(_Msg, State) ->
  {noreply, State}.

 

As you can see, we just add another function definition for handle_cast to capture our create request. We then store it in the dictionary, with a value of 0, representing the current account balance. The return value expected for a handle_cast is {noreply, State} where State is the new state of the server. So this time, we return a new dictionary with the added account.

Also, notice I added a catch-all function at the end. This is not just good practice here, but for all functional programming in general. You can use this method to just absorb the message silently, or you could always raise an exception if you have to. In this case, I absorb it silently.

The entire eb_server.erl at this point can be viewed here.

Money Deposit

We promised our client, ErlyBank, that we’d add an API to deposit money into a person’s account, and that we’d include basic validation. So we need to write a deposit API method, and the server also needs to verify that the account exists before depositing money, since ErlyBank doesn’t want would-be customer’s money going into a black hole. Again, test yourself: A cast or call? This one is easy: A call. We need to make sure the deposit was successful, and notify the user.

Just like before, I’m going to first write the API method:

%%--------------------------------------------------------------------
%% Function: deposit(Name, Amount) -> {ok, Balance} | {error, Reason}
%% Description: Deposits Amount into Name's account. Returns the
%% balance if successful, otherwise returns an error and reason.
%%--------------------------------------------------------------------
deposit(Name, Amount) ->
  gen_server:call(?SERVER, {deposit, Name, Amount}).

 

Nothing exciting here. We just send along a message to the server. The above should look familiar to you, as its the exact same as a cast so far. But the difference is in the server code:

handle_call({deposit, Name, Amount}, _From, State) ->
  case dict:find(Name, State) of
    {ok, Value} ->
      NewBalance = Value + Amount,
      Response = {ok, NewBalance},
      NewState = dict:store(Name, NewBalance, State),
      {reply, Response, NewState};
    error ->
      {reply, {error, account_does_not_exist}, State}
  end;
handle_call(_Request, _From, State) ->
  Reply = ok,
  {reply, Reply, State}.

 

Whoa! Lots of new stuff! First, the method definition looks similar to handle_cast, except for the added From argument, which we don’t use. This argument is the pid of the calling process, so we can send additional messages if needed.

We promised ErlyBank we’d validate the existence of accounts, and the first line is where we do it. We attempt to find a value from the state dictionary for the person trying to deposit the account. The find method of dict returns one of two values: Either {ok, Value} or error.

In the case that the account exists, Value equals the current balance in the account, so we add the deposited amount to that. We then store the new balance for the account into the dictionary and set that to a variable. I also store the response in a variable, which should look just like the comment for the deposit API said it would: {ok, Balance}. Then, by returning the tuple {reply, Reply, State}, the server sends the Reply back to the calling process, and stores the new state.

On the other hand, if the account doesn’t exist, we don’t change the state at all, but we reply with the tuple {error, account_does_not_exist}, which again follows the spec in the comments of the deposit API.

Again, here is the updated eb_server.erl source after this addition.

Account Destruction and Money Withdrawals

I am actually going to leave it as an exercise to the reader to complete the account destruction and withdrawal APIs for the module. You have all the necessary knowledge to do these things. If you need any help with the dict module, refer to the dict module API reference. For withdrawals, please validate both that the account exists and that it has enough funds for the withdrawal. You do not have to handle the case of withdrawing negative funds.

Once you’ve finished, or if you’ve given up (I hope not!), you can view the answer here.

Final Notes

In this article, I showed you the basics of gen_server and how to create a client-server relationship. I did not cover the full capabilities of gen_server which include such things as timeouts for message receiving and stopping the server, but I feel I’ve explained a solid portion of the behavior module.

If you wish to learn more about the callback methods, possible return values, and more advanced usages of gen_server, read the manual on gen_server. Read all of it, really.

Also, I know I did not touch and I barely mentioned the code_change/3 method, which for some reason causes the most excitement in people. Don’t worry, I have an entire article (near the end of the series) dedicated to deploying real-time upgrades to running systems, and this is where this method will come into play.

The next article, in a few days, will be on gen_fsm. So if this article tickled your brain, feel free to jump in on that manual and get a head start! Maybe you can guess what wacky continuation of ErlyBank I’ll be adding with gen_fsm. ;)

Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

  1. J Sep 06, 2008 08:44

    Nice tutorial! I think this will help out a lot of beginners. This is one of the better resources I’ve seen explaining OTP. Thanks.

  2. zamous Sep 06, 2008 09:06

    Nice job. Hoping to see a bunch more articles like this.

  3. Samuel Tesla Sep 06, 2008 09:10

    Excellent article, and congrats on making it to the top of Hacker News.

    I have one factual correction, though. Erlang does not guarantee that messages will be delivered. All it guarantees is that if they are delivered, they will be delivered in order which they were sent.

    I’m starting a similar series on my blog, except I’m writing some blog software. Should be fun.

  4. Mitchell Sep 06, 2008 09:31

    Thanks all.

    And Samuel,

    According to the Erlang FAQ on question 10.10, linked here, it guarantees the message will always be delivered “if nothing breaks.” And if something does break, the client will be notified if they’re linked.

    That is where I got my facts from. And with regards to the order, they are guaranteed to be received in the same order to the same process. If you send A to B then C to D, C might be received at D before A is received at B, so order is not entirely guaranteed.

    But these sorts of things are tricky to deal with :)

  5. Curious Sep 06, 2008 10:13

    I haven’t done any programming in Erlang, so excuse me if this is a trival question.

    I see that the deposit handler first retrieves the balance, then modifies it before it is inserted back into the dictionary.

    What happens if two clients deposits money into the same account? Does Erlang block access to the dictionary when you do “dict:find(Name, State)”

  6. Mitchell Sep 06, 2008 11:07

    Actually, the server is running on a single process, so when you make a call to the server, it will block until the previous call is complete. A gen_server by itself doesn’t handle multiple clients concurrently.

    And although this feature would not be much harder to add, and I thought about adding it, I figured it would be much easier to give a basic gen_server example first, and I can always polish it out later :)

  7. Samuel Tesla Sep 07, 2008 07:30

    Assuming you link or monitor the destination process, you will be notified, yes. However, simply sending the message won’t do that.

    There’s a really common idiom in a lot of Erlang code which is to send a message then immediately monitor the process it was sent to. Then you wait for a response or the ‘DOWN’ message. I believe that’s what gen_server:call does, actually.

    Once again, great post!

  8. Mitchell Sep 07, 2008 09:40

    Right, assuming you’re linked. You’re right, Samuel. And if this were a real-world application, with it being an ATM, I would definitely wait for a success response ;)

    Thanks :)

  9. [...] the second article in my Erlang/OTP introduction series. If you haven’t yet, I recommend you read the first article which talks about gen_server and lays the foundation for our bank system before moving onto this [...]

  10. [...] is the third article in the otp introduction series. If you haven’t yet, I recommend you start with the first article which talks about gen_server and lays the foundation for our bank system. Again, if you’re a [...]

  11. Swaroop Belur Sep 10, 2008 22:42

    Hi

    Just stumbled upon ur series.

    1. Excellent for a erlang novice
    2. Very well written and crisp stuff

    Looking forward to read rest of series now.

    One question:
    Nothing against erlang but have u looked at Disco by any chance any thoughts
    on it? (Mainly from programming experience)

    Thanks for content
    -swaroop

  12. Mitchell Sep 10, 2008 22:54

    Hi Swaroop,

    First, thank you for the kind words. Next, to answer your question, I have taken a very brief look at Disco but I haven’t had enough experience using it in practice to come to a valuable conclusion about it.

    Maybe I will, but at the moment I don’t have a need for disco :)

  13. [...] is the fourth article in the otp introduction series. If you haven’t yet, I recommend you start with the first article which talks about gen_server and lays the foundation for our bank system. If you are a quick [...]

  14. [...] is the fifth article in the otp introduction series. If you haven’t yet, I recommend you start with the first article which talks about gen_server and lays the foundation for our bank system. If you’re a quick [...]

  15. [...] An Introduction to gen_server: "ErlyBank" [...]

  16. [...] is the sixth article in the otp introduction series. If you haven’t yet, I recommend you start with the first article which talks about gen_server and lays the foundation for our bank system. If you’re a quick [...]

  17. [...] final article of the Erlang/OTP introduction series. If you haven’t already, I recommend you read the first article which lays the foundation for the application which we’ll be upgrading in addition to [...]

  18. David Weldon Sep 29, 2008 12:19

    “A gen_server by itself doesn’t handle multiple clients concurrently. And although this feature would not be much harder to add…” Can you give some brief insight as to how one could accomplish this?

  19. Mitchell Sep 29, 2008 15:19

    David,

    Literally, I was correct in saying that a gen_server can’t handle multiple clients concurrently. But normally this isn’t an issue since casting is so quick (since its a one-way message). If you want a gen_server to begin processing a message and be able to send back to the caller, I would spawn off the long running process, store the From parameter of gen_server:call and manually use gen_server:reply to reply via the spawned process.

    To put this into a clearer example (I hope), imagine having a math server (based on gen_server) and there is a call which calculates all the primes up to n. For certain large n, this process can take quite awhile, so instead of doing all the calculations in the math server process, I would do something like the following:

    handle_call({primes, N}, From, State) ->
      spawn(?MODULE, calculate_primes, [From, N]),
      {noreply, State}.
    
    calculate_primes(From, N) ->
      Primes = [],
      % Use some method of calculating prime numbers...
      gen_server:reply(From, {primes, Primes}).
    

     

    Hopefully the example above poses a fairly obvious solution. As you can see in handle_call, since it spawns off the calculation work, it is able to become ready to handle more calls/casts/etc. right away. Then, when the calculate_primes method ends, it explicitly sends the respond back to the caller.

    Mitchell

  20. David Weldon Sep 29, 2008 18:29

    That makes perfect sense. Thanks for the fast reply Mitchell. Your articles are fantastic!

  21. John Bender Dec 14, 2008 14:02

    Awesome set of articles. They’ve been linked to on http://erlanguid.com, and I hope there are other avenues for people to find them. Great read, and thank you for taking the time to teach others!

  22. pavlov Jan 07, 2009 16:22

    How would I create an HTTP interface to handle_call?

Comments

Comments: