[erlang-questions] (not) understanding mnesia transactions

Chandru chandrashekhar.mullaparthi@REDACTED
Thu Jun 18 00:57:32 CEST 2009


Hi,

Your code works as you expect it to if you first acquire a write lock on the
table before you read from it.

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

What I think is happening is that a read lock is acquired when you first
read from the table. According to the mnesia manual:

"Read locks may be shared, which means that if one transaction manages to
acquire a read lock on an item, other transactions may also acquire a read
lock on the same item. However, if someone has a read lock no one can
acquire a write lock at the same item. If some one has a write lock no one
can acquire a read lock nor a write lock at the same item."

So all your clients acquire read locks, decide that a record for a URL does
not exist, and create a record. I'm not sure what locks are acquired for
records which do not exist. I'm sure the answer lies in reading the mnesia
source code...

cheers
Chandru

2009/6/17 Seth Falcon <seth@REDACTED>

> 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
>
> ________________________________________________________________
> erlang-questions mailing list. See http://www.erlang.org/faq.html
> erlang-questions (at) erlang.org
>
>


More information about the erlang-questions mailing list