(not) understanding mnesia transactions

Seth Falcon seth@REDACTED
Wed Jun 17 18:40:52 CEST 2009


Hi all,

I'm trying to use mnesia to store records where I associate a unique
integer for a given string.  I'm using mnesia:dirty_update_counter/3
to obtain a sequence of integers.  Inside a transaction, I'm checking
to see if the given string is already in the table.  If so, return
what is there; if not, get the next integer and store it.

Here's my get_code function:

get_code(Url) ->
    F = fun() ->
                Q = qlc:q([X || X <- mnesia:table(url), X#url.url =:= Url ]),
                case qlc:e(Q) of
                    [] ->
                        New = #url{url = Url, code = next_int()},
                        mnesia:write(New),
                        New;
                    [Found] ->
                        Found
                end
        end,
    {atomic, Val} = mnesia:transaction(F),
    Val.

So far so good.  Since the check for exists and the write are done
within a single transaction, I'm expecting that I can have concurrent
calls for the same string and that mnesia will serialize the requests
for me.

When I write a test case that has 5 processes making identical
requests, I end up with the same string being mapped to different
integers in the table.  Each process in my test does

   lists:map(fun get_code/1, lists:seq(1, 10))

And I'm expecting the return values from all processes to match.  But
they do not.  Can anyone point me in the right direction?

Below is a more complete example that demonstrates what I'm seeing.
You can run it as:

   erlc huh.erl
   mkdir testdb
   erl -mnesia dir '"testdb"'
   > huh:test().

%% -- huh.erl
-module(huh).

-export([get_code/1, get_url/1, start/0, stop/0, test/0]).

-record(counter, {id = 0, ver = 1}).
-record(url, {code, url}).

-include_lib("stdlib/include/qlc.hrl").

start() ->
    Nodes = [node()],
    mnesia:create_schema(Nodes),
    mnesia:start(),
    mnesia:create_table(url, [{disc_copies, Nodes},
                              {attributes, record_info(fields, url)},
                              {index, [url]}]),
    mnesia:create_table(counter, [{disc_copies, Nodes},
                                  {attributes, record_info(fields, counter)}]),
    mnesia:wait_for_tables([url, counter], 20000).

stop() ->
    mnesia:stop().

get_code(Url) ->
    F = fun() ->
                Q = qlc:q([X || X <- mnesia:table(url), X#url.url =:= Url ]),
                Rec = qlc:e(Q),
                %%case mnesia:index_read(url, Url, #url.url) of
                case Rec of
                    [] ->
                        New = #url{url = Url, code = next_int()},
                        mnesia:write(New),
                        New;
                    [Found] ->
                        Found
                end
        end,
    {atomic, Val} = mnesia:transaction(F),
    Val.

get_url(Code) ->
    F = fun() ->
                Q = qlc:q([X || X <- mnesia:table(url), X#url.code =:= Code ]),
                Recs = qlc:e(Q),
                case Recs of
                    [] ->
                        undefined;
                    [Rec] ->
                        Rec
                end
        end,
    {atomic, Val} = mnesia:transaction(F),
    Val.

%% @spec next_int() -> integer()
%% @doc This is the integer sequence used to uniquely identify URLs
next_int() ->
    mnesia:dirty_update_counter(counter, id, 1).


simple_gather(Pid) ->
    receive
        {Pid, Val} ->
            Val
    end.

test() ->
    start(),
    NumClients = 5,
    Seq = lists:map(fun erlang:integer_to_list/1, lists:seq(1, 10)),
    Parent = self(),
    F = fun() ->
                Codes = lists:map(fun(N) ->
                                          get_code("http://" ++ N)
                                  end,
                                  Seq),
                Parent ! {self(), Codes}
        end,
    Pids = lists:map(fun(_X) -> spawn(F) end, lists:seq(1, NumClients)),
    %% Results = gather(Pids),
    Results = [ simple_gather(Pid) || Pid <- Pids ],
    io:format("PIDS: ~p~n", [Pids]),
    io:format("Results ~p~n", [Results]),
    Codes = [ X#url.code || X <- hd(Results) ],
    io:format("Expecting ~p~n", [lists:seq(1, 10)]),
    io:format("Got codes ~p~n", [Codes]),
    ok.

%% -- end huh.erl


More information about the erlang-questions mailing list