Permalink
Browse files

Makefile fix & tail_apply() added (not tested)

  • Loading branch information...
1 parent 68435f8 commit 150f3af395b686b368cca3b019ec0626ab979f9f Ulf Wiger committed Sep 18, 2010
Showing with 83 additions and 8 deletions.
  1. +1 −1 Makefile
  2. +2 −0 include/plain_fsm.hrl
  3. +80 −7 src/plain_fsm.erl
View
@@ -13,7 +13,7 @@
## See the License for the specific language governing permissions and
## limitations under the License.
##==============================================================================
-.PHONY: rel all clean
+.PHONY: all compile clean test doc
all: compile
View
@@ -25,3 +25,5 @@
-compile({parse_transform, plain_fsm_xform}).
+-define(tail_apply(F,C,S),
+ plain_fsm:tail_apply(F, ?MODULE:data_vsn(), ?MODULE, C, S)).
View
@@ -223,6 +223,9 @@
%% Wrapper function used for waking up from hibernation
-export([wake_up/5]).
+%% Helper function for tail-recursive blocking calls
+-export([tail_apply/5]).
+
%% Internal housekeeping records. The split into two records is used
%% to separate the variables that are passed as explicit arguments in
%% the sys API from the ones that are embedded in the 'state'.
@@ -393,19 +396,89 @@ hibernate(_M, _F, _A) ->
wake_up(OldVsn, Module, M, F, [S] = A) ->
case Module:data_vsn() of
OldVsn ->
-%% io:format("waking up, same code version.~n"
-%% "messages: ~p~n", [process_info(self(), messages)]),
apply(M, F, A);
_NewVsn ->
-%% io:format("waking up, new code version - "
-%% "will call code_change().~n"
-%% "messages: ~p~n"
-%% "OldState = ~p~n", [process_info(self(), messages), S]),
{ok, S1} = Module:code_change(OldVsn, S, hibernate),
-%% io:format("New state = ~p~n", [S1]),
apply(M, F, [S1])
end.
+%% @spec tail_apply(Fun, OldVsn, Module, ContF, S) -> NEVER_RETURNS
+%%
+%% @doc Helper function to dispatch blocking calls as tail calls.
+%% During code change, it can be a problem that processes lie in blocking
+%% calls - say, e.g., to `gen_tcp:connect(...)'. If the module is reloaded,
+%% the calling function will still be on the call stack, and may eventually
+%% get the process killed (as the VM only holds two versions of the module).
+%%
+%% This function is most easily called using the macro
+%% `?tail_apply(F, ContF, S)', which expands to
+%% <pre>
+%% plain_fsm:tail_apply(F, ?MODULE:data_vsn(), ?MODULE, ContF, S)
+%% </pre>
+%%
+%% In this case, `?MODULE:data_vsn()' will have been automatically
+%% generated by plain_fsm, or is manually updated whenever the internal
+%% representation of the state `S' is changed.
+%%
+%% `ContF' represents an exported function in the calling module,
+%% `ContF(Status, Result, S)'
+%% Status :: ok | error
+%% Result :: fun() | any()
+%%
+%% If the call to `Fun()' fails, the exception (throw, error or exit) will
+%% be caught, and `Result' will be a fun (arity 0), which can be called
+%% to "re-throw" the exception. This way, the continuation function can
+%% catch exceptions in its own try/catch pattern.
+%%
+%% 'Status' will be `error' if `Fun()' fails, otherwise `ok'.
+%%
+%% Thus, the simplest implementation of `ContF' would be:
+%% <pre>
+%% ContF(ok, Result, S) ->
+%% handle_result(Result, S);
+%% ContF(error, E, _S) ->
+%% E().
+%% </pre>
+%%
+%% Note that this solution does not throw away the call stack, as
+%% e.g. a call to `hibernate/3' does. Thus, it is basically only
+%% tail-recursive as regards the calling function, placing
+%% plain_fsm:tail_apply/5 on the call stack rather than a function
+%% in the user module.
+%% @end
+%%
+tail_apply(F, OldVsn, Module, ContF, S) when is_function(F,0),
+ is_atom(ContF) ->
+ Return = fun(St,Res) ->
+ tail_return(Module, OldVsn, S, ContF, St, Res)
+ end,
+ try
+ Result = F(),
+ Return(ok, Result)
+ catch
+ throw:T ->
+ Return(error, fun() ->
+ throw(T)
+ end);
+ error:E ->
+ Return(error, fun() ->
+ erlang:error(E)
+ end);
+ exit:E ->
+ Return(error, fun() ->
+ exit(E)
+ end)
+ end.
+
+tail_return(Module, OldVsn, S, ContF, Status, Res) ->
+ case Module:data_vsn() of
+ OldVsn ->
+ Module:ContF(Status, Res, S);
+ _NewVsn ->
+ {ok, S1} = Module:code_change(OldVsn, S, tail_apply),
+ Module:ContF(Status, Res, S1)
+ end.
+
%% @spec parent_EXIT(Reason, State) -> EXIT
%%

0 comments on commit 150f3af

Please sign in to comment.