[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