Alberto Perri
Alberto Perri

Reputation: 1

Counter implemented using Client Server in Erlang OTP

I am trying to implement a counter using the Erlang/OTP Client Server. In the following code I can set a count to count up from, but the incr() function does not work. Could someone please explain to me what I am doing wrong?


-module(counter2).
-behaviour(gen_server).

-export([start/1, stop/0, incr/0, get_count/0]).
-export([init/1, handle_call/3, handle_cast/2, terminate/2, handle_info/2]).

% Client Functions
start(Args) ->
    gen_server:start_link({local, counter2}, counter2, [Args], []).

stop() ->
    gen_server:cast(conter2, stop).

incr() ->
    io:format("incr called: ~n"),
    gen_server:call(counter2, {incr, self()}).

get_count() ->
    gen_server:call(counter2, {get_count, self()}).

% Callback functions
init(Args) ->
    Count = Args,
    {ok, Count}.

handle_call({incr, Pid}, _From, Count) ->
    {NewCount, Reply} = incr(Count, Pid),
    io:format("In incr Reply: ~p~n", [Reply]),
    {reply, Reply, NewCount};
handle_call({get_count, Pid}, _From, Count) ->
    {NewCount, Reply} = get_count(Count, Pid),
    {reply, Reply, NewCount}.

handle_cast(stop, Count) ->
    {stop, normal, Count}.

handle_info(_Msg, Count) ->
    {noreply, Count}.

terminate(_Reason, _Count) ->
    ok.

incr(Count, _Pid) ->
    io:format("In incr:~n"),
    NewCount = (Count + 1),
    {ok, NewCount}.

get_count(Count, _Pid) ->
    {ok, Count}.

The counter function incr() should count up from a set value and return the value to the client request.

Upvotes: 0

Views: 48

Answers (1)

Steve Vinoski
Steve Vinoski

Reputation: 20024

You don't show how you're calling the start/1 function, so I'm guessing it's something like this:

counter2:start(42).

The start/1 function passes its arguments to init/1 by putting them in a list argument to gen_server:start_link/4:

gen_server:start_link({local, counter2}, counter2, [Args], []).

This means that for our example, the init/1 function is getting Args as [42], which is stored directly as the server state, and is not what you intended. In an erlang shell if you first start your server and then use the sys:get_state/1 to examine its state, you'll see that it's not storing the number:

1> counter2:start(42).
{ok,<0.161.0>}
2> sys:get_state(counter2).
"*"

The state is "*" which is the same as [42] since 42 is the ASCII value for the asterisk character.

You can fix this by changing the start/1 function to pass Args directly:

gen_server:start_link({local, counter2}, counter2, Args, []).

If we recompile and try our experiment in the erlang shell again, now the state is correct:

3> c("counter2").
counter2
4> counter2:start(42).
{ok,<0.179.0>}
5> sys:get_state(counter2).
42

Now let's call incr/0:

6> counter2:incr().
incr called: 
In incr: 
In incr Reply: 43
43

It seems to work, but unfortunately calling incr/0 again crashes the server:

7> counter2:incr().
incr called: 
In incr: 
=ERROR REPORT==== 8-Aug-2023::09:19:09.992451 ===
** Generic server counter2 terminating 

This is because the handle_call/3 function mishandles the reply from the incr/2 function:

handle_call({incr, Pid}, _From, Count) ->
    {NewCount, Reply} = incr(Count, Pid),

The NewCount and Reply members of the tuple receiving the result are switched; they should be:

handle_call({incr, Pid}, _From, Count) ->
    {Reply, NewCount} = incr(Count, Pid),

With this change, and if we fix the io:format call following this code to print NewCount, then multiple increments work correctly, and our state is correct:

8> counter2:incr().
incr called: 
In incr: 
In incr Reply: 43
ok
9> counter2:incr().
incr called: 
In incr: 
In incr Reply: 44
ok
10> sys:get_state(counter2).
44

The counter2:get_count/0 function also has the same problem with the reversed tuple members:

handle_call({get_count, Pid}, _From, Count) ->
    {NewCount, Reply} = get_count(Count, Pid),

Fixing that allows counter2:get_count/0 to work correctly as well, returning the expected value (same as what sys:get_state/1 shows):

11> counter2:get_count().
44

Lastly, the counter2:stop/0 function doesn't work either, due to a typo in the registered process name (conter2 instead of counter2). It should be:

stop() ->
    gen_server:cast(counter2, stop).

Upvotes: 0

Related Questions