non-atomic nature of mnesia:dirty_update_counter

Ken Ellis <>
Sun Mar 6 06:54:33 CET 2011


Hello all,

The description of mnesia:dirty_update_counter describes it as atomic.
 However I have found that two processes calling dirty_update_counter
can obtain the same value.  The description in the docs says:
"mnesia:dirty_update_counter/3 is performed as an atomic operation
despite the fact that it is not protected by a transaction."  If I
understand atomic to mean
http://en.wikipedia.org/wiki/Linearizability, two processes calling
dirty_update_counter can interfere with each other, as shown below.
To repro, you'll have to set up the tables as shown in the mnesia:info
output, then run the test, and compare the list of numbers spit out
into the file.

After running test_counter:run(5000,1) on each node simultaneously,
and comparing the files, i get 2893 duplicates out of 5000.  Although
the counter has been incremented by 10000.  I can't repro running on a
single node with a large number of processes, so it seems atomic in
that case.

Would any of you call this atomic?  Or was whoever wrote that doc
thinking that the operation is atomic in the sense that its
all-or-nothing.  But I'd propose removing the word atomic from
dirty_update_counter and replace it with a more crisply defined
guarantee (if an error is returned, the counter is guaranteed not to
have been incremented -- the ACID version of atomic), and to warn that
multiple processes calling the method can interfere and obtain the
same value.

Ken





-module(test_counter).

-export([run/2,init/1]).

-record(counter, {name,
		  value}).


init(NodeList) ->
    mnesia:create_table(counter, [{attributes, record_info(fields,counter)},
				  {disc_copies, NodeList}]).

do_spawn(F,TSleep) ->
    timer:sleep(TSleep),
    spawn(F).

run(NProcs,TSleep) ->
    Self = self(),
    CtrF = fun() ->
		   Self ! mnesia:dirty_update_counter(counter,event_id,1)
	   end,
    [ do_spawn(CtrF,TSleep) || _<-lists:duplicate(NProcs,0) ],
    CtrVals = gather(NProcs,[]),
    file:write_file("/tmp/counter_values",io_lib:format("~p",[CtrVals])).



gather(0,L) -> L;
gather(N,L) ->
    receive
	Ctr -> gather(N-1,[Ctr|L])	
    end.




()44> mnesia:info().
---> Processes holding locks <---
---> Processes waiting for locks <---
---> Participant transactions <---
---> Coordinator transactions <---
---> Uncertain transactions <---
---> Active tables <---
counter        : with 1        records occupying 310      words of mem
schema         : with 2        records occupying 520      words of mem
===> System info in version "4.4.16", debug level = none <===
opt_disc. Directory "/root/" is used.
use fallback at restart = false
running db nodes   =
['','']
stopped db nodes   = []
master node tables = []
remote             = []
ram_copies         = []
disc_copies        = [counter,schema]
disc_only_copies   = []
[{'',disc_copies},
 {'',disc_copies}] = [schema,counter]
3 transactions committed, 0 aborted, 0 restarted, 144315 logged to disc
0 held locks, 0 in queue; 0 local transactions, 0 remote
0 transactions waits for other nodes: []


More information about the erlang-questions mailing list