Especially for you I have prepared small library that I want to publish on Hex later:
-module(gen_local).
%% API exports
-export([start/2,
call/2,
cast/2,
send/2]).
-record(state, {state, module}).
%%====================================================================
%% API functions
%%====================================================================
start(Module, Arg) ->
case Module:init(Arg) of
{ok, State} -> {ok, #state{state = State, module = Module}};
{ok, State, {continue, Msg}} ->
handle_continue(Msg, #state{state = State, module = Module});
{ok, State, _Timeout} -> {ok, #state{state = State, module = Module}};
ignore -> ignore;
{stop, Reason} -> {stopped, Reason}
end.
call(#state{module = Module, state = State} = S, Msg) ->
Tag = make_ref(),
case Module:handle_call(Msg, {self(), Tag}, State) of
{reply, Reply, NewState} -> {ok, Reply, S#state{state = NewState}};
{reply, Reply, NewState, {continue, Cont}} ->
case handle_continue(Cont, S#state{state = NewState}) of
{ok, NewNewState} -> {ok, Reply, NewNewState};
Other -> Other
end;
{reply, Reply, NewState, _Timeout} -> {ok, Reply, S#state{state = NewState}};
{noreply, NewState} -> async_reply(Tag, S#state{state = NewState});
{noreply, NewState, {continue, Cont}} ->
case handle_continue(Cont, S#state{state = NewState}) of
{ok, NewNewState} -> async_reply(Tag, S#state{state = NewNewState});
Other -> Other
end;
{noreply, NewState, _Timeout} -> async_reply(Tag, S#state{state = NewState});
{stop, Reason, NewState} -> {stopped, Reason, NewState};
{stop, Reason, Reply, NewState} -> {stopped, Reason, Reply, NewState}
end.
cast(S, Msg) ->
handle_reply(fake_call(S, handle_cast, Msg), S).
send(S, Msg) ->
handle_reply(fake_call(S, handle_info, Msg), S).
%%====================================================================
%% Internal functions
%%====================================================================
fake_call(#state{state = State, module = Module}, Callback, Msg) ->
Module:Callback(Msg, State).
handle_continue(Msg, S) ->
handle_reply(fake_call(S, handle_continue, Msg), S).
async_reply(Tag, State) ->
receive
{Tag, Reply} -> {ok, Reply, State}
end.
handle_reply({noreply, NewState}, S) ->
{ok, S#state{state = NewState}};
handle_reply({noreply, NewState, {continue, Msg}}, S) ->
handle_continue(Msg, S#state{state = NewState});
handle_reply({noreply, NewState, _Timeout}, S) ->
{ok, S#state{state = NewState}};
handle_reply({stop, Reason, NewState}, _S) ->
{stopped, Reason, NewState}.
This provide interface similar to the gen_server
but functions are run synchronously instead of asynchronously. This should make testing of gen_server
a little bit easier (however there still is no timeout, so you cannot test that).
EDIT:
Published on GitHub for now.