gen_server behaviour, synchronous vs asynchronous

gen_server:handle_call vs gen_server:handle_cast

Now that you have a clear idea of what a basic gen_server is, I would like to take a closer look at the differences between gen_server:handle_cast/2 and gen_server:handle_call/3. It is a very simple thing to learn if you get the right example.

Synchronous communication with call

NO WAITING!
If your client requires a server response to continue its activity you will likely have to invoke the gen_server:call function. This way the client will wait for the server gen_server:handle_call/3 to complete its processing, most likely because the client will consume the server response in some way. This is synchronous communication. The consequences are obvious, a server receiving many calls will block clients waiting to get service.

Asynchronous communication with cast

When your client invokes a gen_server:cast it will be able to continue its activity right away without waiting for the server gen_server:handle_cast/2 to complete the request. The drawback is that the client will not get the server’s returned value (there are ways to overcome this). This is asynchronous communication

A gen_server sync/async example

I provide a simple example of a random number gen_server generator. This is a simple server that generates a random number up to a Max parameter provided. For the sake of simulating race conditions I have added some random waiting. Also I have added some custom log function that will help me to illustrate my point

The server

This is the code for the gen_server


% interface
-export([start/0,
stop/0,
do_something_synchronously/1,
do_something_asynchronously/1]).

% callbacks
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).

-define(TIME, 2).

%%====================================================================
%% Server interface
%%====================================================================
%% Booting server (and linking to it)
start() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

%% Stopping server asynchronously
stop() ->
gen_server:cast(?MODULE, shutdown).

do_something_asynchronously(Parameter) ->
gen_server:cast(?MODULE, {generate_random_number, Parameter}).

do_something_synchronously(Parameter) ->
gen_server:call(?MODULE, {generate_random_number, Parameter}).

%%====================================================================
%% gen_server callbacks
%%====================================================================
% Init with trap exit
init([]) ->
random:seed(now()),
util:log("SERVER: Initializing"),
{ok, initialized}.

%% Synchronous calls
handle_call({generate_random_number, Max}, _, State) ->
Time = generate_random(Max),
{reply, Time, State};
handle_call(_, _, State) -> {reply, error, State}.

%% Asynchronous calls
handle_cast({generate_random_number, Max}, State) ->
generate_random(Max),
{noreply, State};
handle_cast(shutdown, State) ->
util:log("SERVER: stopped"),
{stop, normal, State}.

%% Informative calls
handle_info(_Message, _Server) -> {noreply, _Server}.

%% Server termination
terminate(_Reason, _Server) -> ok.

%% Code change
code_change(_OldVersion, _Server, _Extra) -> {ok, _Server}.

%%====================================================================
%% Private functions
%%====================================================================
generate_random(Max) ->
Rand = random:uniform(Max),
util:log("SERVER: Generating random up to '~p', will take me ~p seconds ",[Max, ?TIME]),
timer:sleep(?TIME * 1000),
util:log("SERVER: Done!, generated number was ~p", [Rand]),
Rand.

To run the example invoke the code of the console module


%% run the tests, far from functional but it shows the example running
run() ->

timer:sleep(2000),
util:log("CLIENT: Starting server"),
random_number_server:start(),

util:log("CLIENT: sync request"),

util:log(
"CLIENT: Received from random_number_server ~p",
[random_number_server:do_something_synchronously(1000)]
),

util:log("CLIENT: do_something_synchronously done"),

util:log("CLIENT: async request"),

util:log(
"CLIENT: Received from random_number_server ~p",
[random_number_server:do_something_asynchronously(1000)]
),

util:log("CLIENT: stopping server"),

random_number_server:stop().


Following is the output, where the <0.2.0> pid is the client (console module), <0.34.0> is the server.

The first random_number_server:do_something_synchronously(2) is implemented as a call and blocks the client (in this case the console process) until the server is Done! answering with the random number.

The function call to the random_number_server:do_something_asynchronously(3), invoked as a cast just returns ok and continues its processing by executing the stop. The server processes the messages in order, first the async call that is finished when it outputs Done! and later the stop request.

This should clarify it, just grab the code and enjoy!

 

Bytefilia