[erlang-questions] atomic ets access sequences

Thomas Lindgren thomasl_erlang@REDACTED
Mon Jun 4 19:48:16 CEST 2007

This was something I intended to answer in detail, but
for some reason it got stuck in the TODO-list. Anyway,
here goes a short comment:

I have found that I usually want to do a short
sequence of operations to a single key, for example
"lookup and delete if exists, returning the deleted
tuple(s)". This is much less general than a
transaction, but it still should be atomic.

It's difficult to specify what operations should be
permitted beforehand, so the solution should be fairly
general. The main problem with a general approach is,
I suppose, feature creep: if we permit, say,
ets:atomic(Fun, Key), then we should restrict Fun to
something that cannot hog the Key forever (among other

So your proposal really makes me think not of a new
set of C BIFs, but of extending the existing
mini-language of match specifications to also permit
some set of atomic operations inside them. I'm not
sure how far that can or should be taken. (Would the
end result even look like a match spec?)

(By the way, one big candidate for cleanup in an
"Erlang Redux" would surely be to get rid of the awful
pseudo-syntax trees for match specifications and make
fun2ms or the equivalent the default.)


--- "Ulf Wiger (TN/EAB)" <ulf.wiger@REDACTED>

> After bashing ets, I will now venture to try to 
> make it even dirtier than it already is. (:
> I've put together a BIF in erl_db.c (the ets
> "module"), 
> called seq/3. Rather than posting the C code (which
> is 
> certainly horrific, as I am no C programmer; it is 
> also pretty bulky), I've written a reasonably 
> equivalent Erlang function (attached).
> The main difference between the two, except for the 
> C version being faster, is that the C version is 
> atomic. It allows for some rudimentary lookups and 
> selects, followed by updates (insert  <<eets.erl>>
> and delete), 
> provided that the preconditions are met.
> I've so far only written one simple example: 
> increment(Tab,Key,Incr),
> which increments a counter object if it exists, 
> or creates it with a reasonable default (Incr), 
> if it doesn't. This is what it looks like - ugly,
> but I think the main ugliness lies in the 
> unavoidable select patterns:
> increment(Tab, Key, Incr) ->
>   Pat = [{ {Key, '$1'}, [],
>          [{{ Key, {'+','$1',Incr} }}] }],
>   seq(Tab, [{1, bind, Pat}],
>       [{foreach, 1, insert},
>        {if_nil,  1, [{insert, {Key, Incr}}]}]).
> (Explanation: The call is seq(Tab, Preconditions,
> Actions)
> The {1, bind, Pat} calls ets:select(Tab, Pat) and
> saves the 
> result in a numbered slot (there are 10 of them). 
> The foreach instruction takes each object in slot 1 
> and - in this case - calls ets:insert(Tab, Obj); the
> if_nil instruction is run if slot 1 contains []. It
> then
> initializes the counter object.)
> Here's where you get to provide feedback, shout
> heresy!! 
> or perhaps ask to get the C code an help whip it
> into 
> shape. Some of you may have good suggestions on how
> to 
> make it more intuitive. I've tried to limit myself
> to
> actions that are unlikely to crash, once the
> preconditions
> are met.
> The biggest gripe of my own is that the function
> cannot
> be made to return anything other than true or false,
> but 
> I've saturated my quota for prototyping in C for
> now.
> One use I had in mind is the extended process
> registry,
> which is currently dependent on sending all
> registrations
> to a gen_server. Using this function, registering a
> unique
> name could look like this:
> reg(Name) ->
>   Pat = [{{{Name,rn,'_'}}, [], [true]}]}]
>   seq(?REG, 
>       [{0, select_count, Pat],
>       [{insert, [{{Name,rn,self()}},
>                  {{self(),n,Name}}]).
> (The select_count pattern returns 1 if there is 
> a reverse mapping of the name Name to any pid.
> We perform the insert action only if the 
> select_count result is 0.)
> In a simple test, this turned out to be less than
> twice as fast as my Jungerl version (proc). This
> is a bit disappointing, but latency should be much
> improved, and a registry basically needs to be as 
> lightweight as it can possibly be.
> Another obvious use would be a sort of transaction
> memory, where an object is first read, then
> modified,
> and later committed, assuming that the stored object
> hasn't been changed by someone else:
> commit(Tab, NewObj, OldObj) ->
>   Pat = [{OldObj,[],[true]}],
>   seq(Tab, [{0,select_count,Pat}],
>       [{insert, NewObj}]).
> Anyway, there it is. Comments?
> BR,
> Ulf W
> > _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED

Building a website is a piece of cake. Yahoo! Small Business gives you all the tools to get online.

More information about the erlang-questions mailing list