0

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.

1
  • Thank you for the corrections Steve. I have now modified the file so that i should only count up from zero to a given value. But in the code, the if construct does not work to prevent this. Commented Aug 16, 2023 at 9:51

1 Answer 1

0

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).
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.