Permalink
Browse files

Initial commit. Very incomplete.

  • Loading branch information...
lehoff committed Aug 5, 2011
0 parents commit 4e94bec0543a7de43ef770bed3abb65b24aa2a77
Showing with 212 additions and 0 deletions.
  1. +63 −0 README.md
  2. +149 −0 src/chronos.erl
@@ -0,0 +1,63 @@
+# Chronos - a timer utility for Erlang.
+
+Erlang comes with some good utilities for timers, but there are some
+shortcomings that might become an issue for you as they did for me.
+
+Chronos tries to hide the book keeping from the user in a way that
+will be very familiar to those who have tried to implement protocols
+from the telecommunications realm.
+
+In addition to the abstraction the Chronos distribution also shows how
+to design the APIs of your code in such a way that it makes it easier
+to test the code. This part requires the use of the meck application
+by Adam Lindberg or some serious manual hacking... I am going to show
+the meck way of doing it.
+
+Before describing how to use the Chronos timers I will provide a brief
+overview of what the existing timer solutions has to offer so you can
+make an informed choice about which timer solution fits your problem
+the best.
+
+# Existing timer solutions
+
+## The timer module from the stdlib
+
+This is an excellent module in terms of abstraction: it provides all
+the functionality you would want in order to start and stop timers.
+
+Unfortunately there can only be one timer server for each Erlang node
+and that is that it can very easily become overloaded - see the
+[http://erlang.org/doc/efficiency_guide/commoncaveats.html] entry on
+the timer module.
+
+## Using the erlang modules timer functions
+
+As the Efficiency Guide states the erlang:send_after/3 and
+erlang:start_timer/3 are much more efficient than the timer module,
+but using them directly requires some book keeping which can clutter
+your code uncessarily.
+
+## Using the OTP timers
+
+I am assuming that you use Erlang/OTP to develop your software - if not you can skip this
+section! And in that case you probably never got to this line since
+the Chronos abstraction is too high level for your taste...
+
+For gen_server and gen_fsm you can specify a timer in the result tuple
+from your handler functions, e.g., for gen_fsm you can return
+{next_state,NextStateName,NewStateData,Timeout}. If the gen_server does not receive
+another message within Timeout milliseconds a timeout event will
+happen. The problem with this approach is that if any message arrives
+the timer is cancelled and in many cases you want to see a specific
+message before you cancel the timer or you want to have multiple
+timers running.
+
+You can have multiple timers with gen_fsm by using the
+gen_fsm:start_timer/2 function. The down side is that you have to do
+book keeping of timer references if you want to cancel the timer. This
+is similar to using the erlang modules timers mentioned above.
+
+Hmmm, perhaps the OTP timers can be used without much fuzz for this
+after all... just a matter of style.
+
+
@@ -0,0 +1,149 @@
+%%%-------------------------------------------------------------------
+%%% @author Torben Hoffmann <torben.lehoff@gmail.com>
+%%% @copyright (C) 2011, Torben Hoffmann
+%%% @doc
+%%%
+%%% @end
+%%% Created : 27 Jun 2011 by Torben Hoffmann <torben.lehoff@gmail.com>
+%%%-------------------------------------------------------------------
+-module(chronos).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0,
+ start_link/1,
+ stop/1,
+ start_timer/4,
+ stop_timer/2
+ ]).
+
+
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+
+%% Types
+-type server_name() :: atom().
+-type server_ref() :: server_name() | pid().
+-type timer_name() :: term().
+-type function_name() :: term().
+-type args() :: [term()].
+-type callback() :: {module(), function_name(), args()].
+
+-record(state,
+ {running = [] :: {[timer_name(), reference()]}
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+-start_link(server_name()) -> {'ok',pid()} | 'ignore' | {'error',term()}.
+start_link(ServerName) ->
+ gen_server:start_link({local, ServerName}, ?MODULE, [], []).
+
+-start_link() -> {'ok',pid()} | 'ignore' | {'error',term()}.
+start_link() ->
+ gen_server:start_link(?MODULE, [], []).
+
+-spec stop(server_name()) -> ok.
+stop(ServerRef) ->
+ gen_server:call(ServerRef, stop).
+
+-spec start_timer(server_ref(), timer_name(), pos_integer(), callback()) ->
+ 'ok' | {'error',term()}.
+start_timer(ServerRef, TimerName, Timeout, Callback) ->
+ gen_server:call(ServerRef, {start_timer, TimerName, Timeout, Callback}).
+
+-spec stop_timer(server_ref(), timer_name()) -> 'ok' | {'error',term()}.
+stop_timer(ServerRef, TimerName) ->
+ gen_server:call(ServerRef, {stop_timer, TimerName}).
+
+
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+
+-spec init([term()]) -> {'ok', #state{}} | {'stop', term()} | 'ignore'.
+init([]) ->
+ {ok, #state{}}.
+
+-spec handle_call(term(), term(), #state{}) ->
+ {'reply', 'ok' | {'error', term()} , #state{}} |
+ {'stop', term(), 'ok', #state{}}.
+handle_call({start_timer, Name, Time, Callback}, _From,
+ #state{running=R}=State) ->
+ %% If the Name timer is running we clean it up.
+ R1 = case lists:keytake(Name, 1, R) of
+ false ->
+ R;
+ {value, {_, TRef,}, Ra} ->
+ erlang:cancel_timer(TRef),
+ Ra
+ end,
+ TRefNew = erlang:start_timer(Time, self(), {Name,Callback}),
+ {reply, ok, State#state{running=[{Name,TRefNew}|R1]}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================

0 comments on commit 4e94bec

Please sign in to comment.