Non-blocking server behaviour
-module(gen_nbs).
-behaviour(gen_nbs).
%% API
-export([start_link/0]).
%% gen_nbs callbacks
-export([init/1,
handle_msg/3,
handle_ack/3,
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2]).
%%%===================================================================
%%% API
%%%===================================================================
start_link() ->
gen_nbs:start_link(?MODULE, [], []).
%%%===================================================================
%%% gen_nbs callbacks
%%%===================================================================
init([]) ->
{ok, #{}}.
handle_msg(_Msg, From, State) ->
Ack = ok,
{ack, From, Ack, State}.
handle_ack(_Ack, _Tag, State) ->
{ok, State}.
handle_cast(_Msg, State) ->
{ok, State}.
handle_info(_Info, State) ->
{ok, State}.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, _State) ->
ok.
%%%===================================================================
%%% Internal functions
%%%===================================================================
Module:init/1 acts just like gen_server's version.
Module:handle_msg/3 handles incoming messages. Possible returned values are:
- {ack, From, Ack, State [, Timeout | hibernate]}
- {fail, From, Reason, State, [, Timeout | hibernate]}
- {await, Await, State [, Timeout | hibernate]}
- {ok, State, [, Timeout | hibernate]}
- {stop, Reason, State}
Module:handle_ack/3 handles responses to outgoing messages. Possible returned values are the same. 'Ack' parameter can be one of the following:
- {ack, Data} if another process has responded with 'ack'
- {fail, Reason} if another process has responded with 'fail'
- {fail, timeout} if timeout of sending message to another process had triggered before another process responded of another process hasn't responded
- Recursive map for packaged messages
Module:handle_cast/3, Module:handle_info/3, Module:code_change/3 acts just like in gen_server except for returning values the same as shown above.
Single message:
gen_nbs:msg(Recepient, Payload)
or
gen_nbs:msg(Recepient, Payload, CompletionFun)
Prepare a single message with 'Payload' to be sent to the 'Recepient'. 'CompletionFun' can be used to make some actions after response has been received but before 'handle_ack'. It will be called in sender process. Useful for closing connections and so on.
Return:
gen_nbs:return(Payload)
or
gen_nbs:return(Payload, CompletionFun)
Immediately return a value without sending it anywhere.
Package:
gen_nbs:package(Recepient, Msgs)
or
gen_nbs:package(Recepient, Msgs, CompletionFun)
Prepare a package of messages to be sent to their corrresponding recepients. 'Msgs' is a recursive map where each key is an arbitrary identifier and its corresponding value is an another recursive map of messages or single message or return. 'handle_ack' will be called after all the responses (or timeouts). 'CompletionFun' acts the same way.
There are two ways of sending message.
If current process is a gen_nbs process you can send message this way:
Await = gen_nbs:transmit(Msg, Tag)
or
Await = gen_nbs:transmit(Msg, Tag, Timeout)
This 'await' value should be given to {await, Await, State} return value of a callback. After the response is received 'handle_ack' will be called with corresponding response and tag. Tag is just any term you can use to identify message.
You can use {await, Awaits, State} with list of awaits then multiple 'handle_ack' will be called each for one await.
If current process is not a gen_nbs process you should use this: gen_nbs:await(Msg)
It blocks until a response is received and return it as a function result.
Use gen_nbs:safe_call to avoid certain pitfalls while calling gen_server from inside the gen_nbs
handle_msg(load_data, From, Connection) ->
CompletionFun = fun({ack, Data}) ->
{ack, jiffy:decode(Data)};
({fail, Reason}) ->
{fail, Reason}
end,
Msg = case Connection of
undefined ->
gen_nbs:return({fail, no_connection});
Connection ->
gen_nbs:msg(Connection, load_data)
end,
Tag = {load_tag, From},
Await = gen_nbs:transmit(Msg, Tag),
{await, Await, Connection}.
handle_ack({ack, Data}, {load_tag, From}, _Connection) ->
{ack, From, Data, Connection};
handle_ack({fail, Reason}, {load_msg, From}, _Connection) ->
io:format("Error ~p while loading data ~n", [Reason]),
{fail, From, Reason, Connection).
... more examples will be added later