(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