stl and yaws

Luke Gorrie <>
Tue Jan 14 15:25:08 CET 2003


Vladimir Sekissov <> wrote:
> 
> luke> They do this in Scheme using call/cc. Looking forward to seeing how
> luke> the mdisp-based approach works!
> 
> Without support for call-with-current-continuation I see only two
> approaches:
> 
> 1. syntax transformation (may be I try it somewhere).
> 2. Continuation style programming as Distel does.
> 
> Now I use the second one. Every function must return
> 
> {ok, Result, ContFun, ContArgs} | {ok, Result, die}

Sounds good, since that's basically the same style as e.g. gen_fsm.

I guess the idea is to keep all the continuations (states) in the
server for a period of time, and give the client some ID that he can
post in his forms to say which state he's posting towards? So it would
be like regular state machines, except every state you reach is
persistent, so you can go "back in time" with the BACK button, and
explore parallel universes :-)

BTW, the other day I wrote a little server for storing temporary
(time-limited) server state like that - attached, incase it's useful.

In our application though, we encode pretty much all of our state into
fairly meaningful URLs by hand. This is nice since we don't have to
remember continuations (they're in the URL / query string), and as a
user you can take shortcuts by writing the magic URLs by hand (we
smile upon this.) The encoding/decoding of the URLs is fairly ad hoc
just now though.

Cheers,
Luke

-------------- next part --------------
%%%----------------------------------------------------------------------
%%% File    : portal_servcookie.erl
%%% Author  : Luke Gorrie <>
%%% Purpose : Server for temporary "server cookies" -- time-limited
%%%           key/value pairs.
%%% Created : 10 Jan 2003 by Luke Gorrie <>
%%%----------------------------------------------------------------------

%% This server has two ets tables: "new" and "old." Both contain
%% {{Session, Key}, Value} tuples of temporary key/value associations
%% belonging to portal user sessions.
%%
%% The "old" table is periodically cleared and swapped with the "new"
%% table.

-module(portal_servcookie).
-author('').

%%-compile(export_all).
%%-export([Function/Arity, ...]).

-behaviour(gen_server).

%% External exports
-export([start_link/0, set_value/2, get_value/2]).

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

-record(state,
	{new, old, counter=0}).

%% By default, swap new/old tables every 2 minutes, giving each entry
%% a lifetime of between 2 and 4 mintues.
-define(DEFAULT_INTERVAL, 120000).

%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start_link() ->
    start_link(?DEFAULT_INTERVAL).

start_link(Interval) ->
    gen_server:start_link({local, portal_servcookie},
			  portal_servcookie,
			  Interval,
			  []).

%% Set a value in the session and return a "cookie". The value can be
%% read back using the cookie with get_value/2, but will time-out and
%% disappear after a few minutes.
%%
%% Returns: Cookie = integer(), cookie to read the value back with.
set_value(Session, Value) ->
    gen_server:call(?MODULE, {set_value, Session, Value}).

%% Read a previously set value, if it still exists.
%%
%% Returns: {ok, Value} | not_found
get_value(Session, Cookie) ->
    gen_server:call(?MODULE, {get_value, Session, Cookie}).

%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------

init(Interval) ->
    timer:send_interval(Interval, time_to_swap),
    %% Tables are only named for debugging purposes.
    {ok, #state{new=ets:new(portal_servercookie_a,
			    [set, public, named_table]),
		old=ets:new(portal_servercookie_b,
			    [set, public, named_table])}}.

handle_call({set_value, Session, Value}, From, State) ->
    Key = State#state.counter,
    ets:insert(State#state.new, {{Session, Key}, Value}),
    {reply, Key, State#state{counter=Key+1}};

handle_call({get_value, Session, Key}, From, State=#state{new=New, old=Old}) ->
    Reply = case lookup(New, Session, Key) of
		{ok, Value} ->
		    {ok, Value};
		not_found ->
		    lookup(Old, Session, Key)
	    end,
   {reply, Reply, State}.

handle_cast(Msg, State) ->
    {noreply, State}.

%% Handle periodic swap.
handle_info(time_to_swap, State = #state{new=New, old=Old}) ->
    ets:delete_all_objects(Old),
    ets:insert(New, {status, old}),
    ets:insert(Old, {status, new}),
    %% Wrap counter after ten million entries, just to keep it from
    %% growing indefinitely. "defensive programming"
    Counter = if State#state.counter > 10000000 ->
		      0;
		 true ->
		      State#state.counter
	      end,
    {noreply, #state{new=Old, old=New, counter=Counter}};

handle_info(Info, State) ->
    {noreply, State}.

terminate(Reason, State) ->
    ok.

code_change(OldVsn, State, Extra) ->
    {ok, State}.

%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------

lookup(Table, Session, Key) ->
    case ets:lookup(Table, {Session, Key}) of
	[{_, Value}] -> {ok, Value};
	[]           -> not_found
    end.



More information about the erlang-questions mailing list