Reputation: 1
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
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