%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2016. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%
-module(mnesia_isolation_test).
-author('[email protected]').
-compile([export_all]).
-include("mnesia_test_lib.hrl").
init_per_testcase(Func, Conf) ->
mnesia_test_lib:init_per_testcase(Func, Conf).
end_per_testcase(Func, Conf) ->
mnesia_test_lib:end_per_testcase(Func, Conf).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
all() ->
[{group, locking}, {group, visibility}].
groups() ->
[{locking, [],
[no_conflict, simple_queue_conflict,
advanced_queue_conflict, simple_deadlock_conflict,
advanced_deadlock_conflict, schema_deadlock, lock_burst,
{group, sticky_locks}, {group, unbound_locking},
{group, admin_conflict}, nasty]},
{sticky_locks, [], [basic_sticky_functionality]},
{unbound_locking, [], [unbound1, unbound2]},
{admin_conflict, [],
[create_table, delete_table, move_table_copy,
add_table_index, del_table_index, transform_table,
snmp_open_table, snmp_close_table,
change_table_copy_type, change_table_access,
add_table_copy, del_table_copy, dump_tables,
{group, extra_admin_tests}]},
{extra_admin_tests, [],
[del_table_copy_1, del_table_copy_2, del_table_copy_3,
add_table_copy_1, add_table_copy_2, add_table_copy_3,
add_table_copy_4, move_table_copy_1, move_table_copy_2,
move_table_copy_3, move_table_copy_4]},
{visibility, [],
[dirty_updates_visible_direct,
dirty_reads_regardless_of_trans,
trans_update_invisibible_outside_trans,
trans_update_visible_inside_trans, write_shadows,
delete_shadows, write_delete_shadows_bag,
write_delete_shadows_bag2, {group, iteration},
shadow_search, snmp_shadows]},
{removed_resources, [], [rr_kill_copy]},
{iteration, [], [foldl, first_next]}].
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
no_conflict(suite) -> [];
no_conflict(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = no_conflict,
create_conflict_table(Tab, [Node1]),
Fun = fun(OtherOid, Lock1, Lock2) ->
%% Start two transactions
{success, [B, A]} = ?start_activities([Node1, Node1]),
?start_transactions([B, A]),
A ! fun() -> Lock1(one_oid(Tab)), ok end,
?match_receive({A, ok}),
B ! fun() -> Lock2(OtherOid), ok end,
?match_receive({B, ok}),
A ! fun() -> mnesia:abort(ok) end,
?match_receive({A, {aborted, ok}}),
B ! fun() -> mnesia:abort(ok) end,
?match_receive({B, {aborted, ok}})
end,
NoLocks = lock_funs(no_lock, any_granularity),
SharedLocks = lock_funs(shared_lock, any_granularity),
AnyLocks = lock_funs(any_lock, any_granularity),
OneOneFun = fun(Lock1, Lock2) -> Fun(one_oid(Tab), Lock1, Lock2) end,
fun_loop(OneOneFun, NoLocks, AnyLocks),
fun_loop(OneOneFun, AnyLocks, NoLocks),
fun_loop(OneOneFun, SharedLocks, SharedLocks),
%% Lock different objects
OneOtherFun = fun(Lock1, Lock2) -> Fun(other_oid(Tab), Lock1, Lock2) end,
OneSharedLocks = lock_funs(shared_lock, one),
OneExclusiveLocks = lock_funs(exclusive_lock, one),
fun_loop(OneOtherFun, OneSharedLocks, OneExclusiveLocks),
fun_loop(OneOtherFun, OneExclusiveLocks, OneSharedLocks),
fun_loop(OneOtherFun, OneExclusiveLocks, OneExclusiveLocks),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
simple_queue_conflict(suite) -> [];
simple_queue_conflict(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = simple_queue_conflict,
create_conflict_table(Tab, [Node1]),
Fun = fun(OneLock, OtherLock) ->
%% Start two transactions
{success, [B, A]} = ?start_activities([Node1, Node1]),
?start_transactions([B, A]),
A ! fun() -> OneLock(one_oid(Tab)), ok end,
?match_receive({A, ok}),
B ! fun() -> OtherLock(one_oid(Tab)), ok end,
wait_for_lock(B, [Node1], 20), % Max 10 sec
A ! end_trans,
?match_multi_receive([{A, {atomic, end_trans}}, {B, ok}]),
B ! fun() -> mnesia:abort(ok) end,
?match_receive({B, {aborted, ok}})
end,
OneSharedLocks = lock_funs(shared_lock, one),
AllSharedLocks = lock_funs(shared_lock, all),
OneExclusiveLocks = lock_funs(exclusive_lock, one),
AllExclusiveLocks = lock_funs(exclusive_lock, all),
fun_loop(Fun, OneExclusiveLocks, OneExclusiveLocks),
fun_loop(Fun, AllExclusiveLocks, AllExclusiveLocks),
fun_loop(Fun, OneExclusiveLocks, AllExclusiveLocks),
fun_loop(Fun, AllExclusiveLocks, OneExclusiveLocks),
fun_loop(Fun, OneSharedLocks, AllExclusiveLocks),
fun_loop(Fun, AllSharedLocks, OneExclusiveLocks),
ok.
wait_for_lock(Pid, Nodes, Retry) ->
wait_for_lock(Pid, Nodes, Retry, queue).
wait_for_lock(Pid, _Nodes, 0, queue) ->
Queue = mnesia:system_info(lock_queue),
?error("Timeout while waiting for lock on Pid ~p in queue ~p~n", [Pid, Queue]);
wait_for_lock(Pid, _Nodes, 0, held) ->
Held = mnesia:system_info(held_locks),
?error("Timeout while waiting for lock on Pid ~p (held) ~p~n", [Pid, Held]);
wait_for_lock(Pid, Nodes, N, Where) ->
rpc:multicall(Nodes, sys, get_status, [mnesia_locker]),
List = case Where of
queue ->
[rpc:call(Node, mnesia, system_info, [lock_queue]) || Node <- Nodes];
held ->
[rpc:call(Node, mnesia, system_info, [held_locks]) || Node <- Nodes]
end,
Q = lists:append(List),
check_q(Pid, Q, Nodes, N, Where).
check_q(Pid, [{_Oid, _Op, Pid, _Tid, _WFT} | _Tail], _N, _Count, _Where) -> ok;
check_q(Pid, [{_Oid, _Op, {tid,_,Pid}} | _Tail], _N, _Count, _Where) -> ok;
check_q(Pid, [_ | Tail], N, Count, Where) -> check_q(Pid, Tail, N, Count, Where);
check_q(Pid, [], N, Count, Where) ->
timer:sleep(200),
wait_for_lock(Pid, N, Count - 1, Where).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
advanced_queue_conflict(suite) -> [];
advanced_queue_conflict(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = advanced_queue_conflict,
create_conflict_table(Tab, [Node1]),
OneRec = {Tab, 3, 3},
OneOid = {Tab, 3},
OtherRec = {Tab, 4, 4},
OtherOid = {Tab, 4},
%% Start four transactions
{success, [D, C, B, A]} = ?start_activities(lists:duplicate(4, Node1)),
?start_transactions([D, C, B, A]),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
%% Acquire some locks
A ! fun() -> mnesia:write(OneRec) end,
?match_receive({A, ok}),
A ! fun() -> mnesia:read(OneOid) end,
?match_receive({A, [OneRec]}),
B ! fun() -> mnesia:write(OtherRec) end,
?match_receive({B, ok}),
B ! fun() -> mnesia:read(OneOid) end,
?match_receive(timeout),
C ! fun() -> mnesia:read(OtherOid) end,
?match_receive(timeout),
D ! fun() -> mnesia:wread(OtherOid) end,
?match_receive(timeout),
%% and release them in a certain order
A ! end_trans,
?match_multi_receive([{A, {atomic, end_trans}}, {B, [OneRec]}]),
B ! end_trans,
?match_multi_receive([{B, {atomic, end_trans}}, {C, [OtherRec]}]),
C ! end_trans,
?match_multi_receive([{C, {atomic, end_trans}}, {D, [OtherRec]}]),
D ! end_trans,
?match_receive({D, {atomic, end_trans}}),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
simple_deadlock_conflict(suite) -> [];
simple_deadlock_conflict(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = simple_deadlock_conflict,
create_conflict_table(Tab, [Node1]),
Rec = {Tab, 4, 4},
Oid = {Tab, 4},
%% Start two transactions
{success, [B, A]} = ?start_activities(lists:duplicate(2, Node1)),
mnesia_test_lib:start_transactions([B, A], 0), % A is newest
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
B ! fun() -> mnesia:write(Rec) end,
?match_receive({B, ok}),
A ! fun() -> mnesia:read(Oid) end,
?match_receive({A, {aborted, nomore}}),
B ! end_trans,
?match_receive({B, {atomic, end_trans}}),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
advanced_deadlock_conflict(suite) -> [];
advanced_deadlock_conflict(Config) when is_list(Config) ->
[Node1, Node2] = ?acquire_nodes(2, Config),
Tab = advanced_deadlock_conflict,
create_conflict_table(Tab, [Node2]),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
Rec = {Tab, 4, 4},
Oid = {Tab, 4},
%% Start two transactions
{success, [B, A]} = ?start_activities([Node1, Node2]),
mnesia_test_lib:start_sync_transactions([B, A], 0), % A is newest
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
B ! fun() -> mnesia:write(Rec) end,
?match_receive({B, ok}),
A ! fun() -> mnesia:read(Oid) end,
?match_receive({A, {aborted, nomore}}),
B ! end_trans,
?match_receive({B, {atomic, end_trans}}),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
%% Verify (and regression test) deadlock in del_table_copy(schema, Node)
schema_deadlock(Config) when is_list(Config) ->
Ns = [Node1, Node2] = ?acquire_nodes(2, Config),
?match({atomic, ok}, mnesia:create_table(a, [{disc_copies, Ns}])),
?match({atomic, ok}, mnesia:create_table(b, [{disc_copies, Ns}])),
Tester = self(),
Deadlocker = fun() ->
mnesia:write({a,1,1}), %% grab write lock on A
receive
continue ->
mnesia:write({b,1,1}), %% grab write lock on B
end_trans
end
end,
?match(stopped, rpc:call(Node2, mnesia, stop, [])),
timer:sleep(500), %% Let Node1 reconfigure
sys:get_status(mnesia_monitor),
DoingTrans = spawn_link(fun() -> Tester ! {self(),mnesia:transaction(Deadlocker)} end),
wait_for_lock(DoingTrans, [Node1], 10, held),
%% Will grab write locks on schema, a, and b
DoingSchema = spawn_link(fun() -> Tester ! {self(), mnesia:del_table_copy(schema, Node2)} end),
timer:sleep(500), %% Let schema trans start, and try to grab locks
DoingTrans ! continue,
?match(ok, receive {DoingTrans, {atomic, end_trans}} -> ok after 5000 -> timeout end),
?match(ok, receive {DoingSchema, {atomic, ok}} -> ok after 5000 -> timeout end),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
one_oid(Tab) -> {Tab, 1}.
other_oid(Tab) -> {Tab, 2}.
create_conflict_table(Tab, Nodes) ->
?match({atomic, ok}, mnesia:create_table([{name, Tab},
{ram_copies, Nodes},
{attributes, [key, val]},
{index, [val]}
])),
?match([], mnesia_test_lib:sync_tables(Nodes, [Tab])),
init_conflict_table(Tab).
init_conflict_table(Tab) ->
Recs = mnesia:dirty_match_object({Tab, '_', '_'}),
lists:foreach(fun(R) -> mnesia:dirty_delete_object(R) end, Recs),
Keys = [one_oid(Tab), other_oid(Tab)],
[mnesia:dirty_write({T, K, K}) || {T, K} <- Keys].
%% Apply Fun for each X and Y
fun_loop(Fun, Xs, Ys) ->
lists:foreach(fun(X) -> lists:foreach(fun(Y) -> do_fun(Fun, X, Y) end, Ys) end, Xs).
do_fun(Fun, X, Y) ->
Pid = spawn_link(?MODULE, do_fun, [self(), Fun, X, Y]),
receive
{done_fun, Pid} -> done_fun
end.
do_fun(Monitor, Fun, X, Y) ->
?log("{do_fun ~p~n", [[Fun, X, Y]]),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
Fun(X, Y),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
unlink(Monitor),
Monitor ! {done_fun, self()},
exit(done_fun).
%% Returns a list of fun's
lock_funs(no_lock, one) ->
[
fun(Oid) -> mnesia:dirty_read(Oid) end,
fun({Tab, Key}) -> mnesia:dirty_write({Tab, Key, Key}) end,
fun({Tab, Key}) -> mnesia:dirty_write({Tab, Key, Key}),
mnesia:dirty_update_counter({Tab, Key}, 0) end,
fun(Oid) -> mnesia:dirty_delete(Oid) end,
fun({Tab, Key}) -> mnesia:dirty_delete_object({Tab, Key, Key}) end,
fun({Tab, Key}) -> mnesia:dirty_match_object({Tab, Key, Key}) end,
fun({Tab, Key}) -> mnesia:dirty_index_match_object({Tab, Key, Key}, val) end,
fun({Tab, Key}) -> mnesia:dirty_index_read(Tab, Key, val) end,
fun({Tab, Key}) -> mnesia:dirty_index_match_object({Tab, '_', Key}, val) end
];
lock_funs(no_lock, all) ->
[
fun({Tab, _}) -> mnesia:dirty_match_object({Tab, '_', '_'}) end,
fun({Tab, _}) -> slot_iter(Tab) end,
fun({Tab, _}) -> key_iter(Tab) end
];
lock_funs(shared_lock, one) ->
[
fun(Oid) -> mnesia:read(Oid) end,
fun({Tab, Key}) ->
init_conflict_table(Tab),
mnesia:dirty_delete(other_oid(Tab)),
mnesia:match_object({Tab, Key, Key}) end
];
lock_funs(shared_lock, all) ->
[
fun({Tab, _}) -> mnesia:read_lock_table(Tab) end,
fun({Tab, Key}) -> mnesia:match_object({Tab, '_', Key}) end,
fun({Tab, _}) -> mnesia:match_object({Tab, '_', '_'}) end,
fun({Tab, _}) -> mnesia:all_keys(Tab) end,
fun({Tab, Key}) -> mnesia:index_match_object({Tab, '_', Key}, val) end,
fun({Tab, Key}) -> mnesia:index_read(Tab, Key, val) end
];
lock_funs(exclusive_lock, one) ->
[
fun(Oid) -> mnesia:wread(Oid) end,
fun({Tab, Key}) -> mnesia:write({Tab, Key, Key}) end,
fun(Oid) -> mnesia:delete(Oid) end,
fun({Tab, Key}) -> mnesia:delete_object({Tab, Key, Key}) end,
fun({Tab, Key}) -> mnesia:s_write({Tab, Key, Key}) end,
fun(Oid) -> mnesia:s_delete(Oid) end,
fun({Tab, Key}) -> mnesia:s_delete_object({Tab, Key, Key}) end
];
lock_funs(exclusive_lock, all) ->
[
fun({Tab, _}) -> mnesia:write_lock_table(Tab) end
];
lock_funs(Compatibility, any_granularity) ->
lists:append([lock_funs(Compatibility, Granularity) ||
Granularity <- [one, all]]);
lock_funs(any_lock, Granularity) ->
lists:append([lock_funs(Compatibility, Granularity) ||
Compatibility <- [no_lock, shared_lock, exclusive_lock]]).
slot_iter(Tab) ->
slot_iter(Tab, mnesia:dirty_slot(Tab, 0), 1).
slot_iter(_Tab, '$end_of_table', _) ->
[];
slot_iter(Tab, Recs, Slot) ->
Recs ++ slot_iter(Tab, mnesia:dirty_slot(Tab, Slot), Slot+1).
key_iter(Tab) ->
key_iter(Tab, mnesia:dirty_first(Tab)).
key_iter(_Tab, '$end_of_table') ->
[];
key_iter(Tab, Key) ->
[Key | key_iter(Tab, mnesia:dirty_next(Tab, Key))].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
lock_burst(suite) -> [];
lock_burst(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = burst,
?match({atomic, ok}, mnesia:create_table(Tab,
[{attributes, [a, b]},
{ram_copies, [Node1]}])),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
?match(ok, burst_em(Tab, 1000)),
?match([{burst,1,1000}], mnesia:dirty_read(Tab,1)),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
burst_em(Tab, N) ->
spawn_link(?MODULE, burst_counter, [self(), Tab, N]),
receive
burst_counter_done -> ok
end.
burst_counter(Monitor, Tab, N) when N > 0 ->
?match(ok, burst_gen(Tab, N, self())),
Monitor ! burst_receiver(N).
burst_receiver(0) ->
burst_counter_done;
burst_receiver(N) ->
receive
burst_incr_done ->
burst_receiver(N-1)
end.
burst_gen(_, 0, _) ->
ok;
burst_gen(Tab, N, Father) when is_integer(N), N > 0 ->
spawn_link(?MODULE, burst_incr, [Tab, Father]),
burst_gen(Tab, N-1, Father).
burst_incr(Tab, Father) ->
Fun = fun() ->
Val =
case mnesia:read({Tab, 1}) of
[{Tab, 1, V}] -> V;
[] -> 0
end,
mnesia:write({Tab, 1, Val+1})
end,
?match({atomic, ok}, mnesia:transaction(Fun)),
Father ! burst_incr_done.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
basic_sticky_functionality(suite) -> [];
basic_sticky_functionality(Config) when is_list(Config) ->
[N1, N2] = Nodes = ?acquire_nodes(2, Config),
Tab = basic_table,
Storage = mnesia_test_lib:storage_type(disc_copies, Config),
?match({atomic, ok}, mnesia:create_table(Tab, [{Storage, Nodes}])),
?match({atomic, ok}, mnesia:create_table(sync, [{ram_copies, Nodes}])),
Trans1 = fun() ->
?match(ok, mnesia:s_write({Tab, 1, 2})),
?match([{Tab, 1, 2}], mnesia:read({Tab, 1})),
?match(timeout, receive M -> M after 500 -> timeout end),
?match(ok, mnesia:s_write({Tab, 2, 2})),
?match(ok, mnesia:write({Tab, 42, 4711}))
end,
Trans2 = fun() ->
?match([{Tab, 1, 2}], mnesia:read({Tab, 1})),
?match(timeout, receive M -> M after 500 -> timeout end),
?match(ok, mnesia:write({Tab, 1, 4711})),
?match(ok, mnesia:s_write({Tab, 2, 4})),
?match(ok, mnesia:delete({Tab, 42}))
end,
rpc:call(N1, mnesia, transaction, [Trans1]),
?match([{Tab,N1}], rpc:call(N1, ?MODULE, get_sticky, [])),
?match([{Tab,N1}], rpc:call(N2, ?MODULE, get_sticky, [])),
rpc:call(N2, mnesia, transaction, [Trans2]),
?match([], rpc:call(N1, ?MODULE, get_sticky, [])),
?match([], rpc:call(N2, ?MODULE, get_sticky, [])),
Slock = fun() -> mnesia:read({sync,sync}),get_sticky() end,
?match({atomic, [{Tab,1, 4711}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)),
?match({atomic, [{Tab,N1}]}, rpc:call(N1, mnesia, transaction,
[fun() -> mnesia:s_write({Tab, 1, 3}),Slock() end])),
?match([{Tab,N1}], rpc:call(N2, ?MODULE, get_sticky, [])),
?match({atomic,[]}, rpc:call(N2, mnesia, transaction,
[fun() -> mnesia:s_write({Tab, 1, 4}),Slock() end])),
?match([], rpc:call(N1, ?MODULE, get_sticky, [])),
?match([], rpc:call(N2, ?MODULE, get_sticky, [])),
?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction,
[fun() -> mnesia:s_write({Tab, 1, 4}),Slock() end])),
?match({atomic,[]}, rpc:call(N1, mnesia, transaction,
[fun() -> mnesia:s_write({Tab, 1, 5}),Slock() end])),
?match({atomic,[{Tab,N1}]}, rpc:call(N1, mnesia, transaction,
[fun() -> mnesia:s_write({Tab, 1, 5}),Slock() end])),
?match({atomic,[]}, rpc:call(N2, mnesia, transaction,
[fun() -> mnesia:s_write({Tab, 1, 6}),Slock() end])),
?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction,
[fun() -> mnesia:s_write({Tab, 1, 7}),Slock() end])),
?match([{Tab,N2}], get_sticky()),
?match({atomic, [{Tab,1, 7}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
?match([{Tab,N2}], get_sticky()),
?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)),
?match([{Tab,N2}], get_sticky()),
?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction,
[fun() -> mnesia:s_write({Tab, 1, 6}),Slock() end])),
?match([{Tab,N2}], get_sticky()),
?match({atomic, [{Tab,1, 6}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
?match([{Tab,N2}], get_sticky()),
?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)),
?match([{Tab,N2}], get_sticky()),
?verify_mnesia(Nodes, []).
get_sticky() ->
mnesia_locker ! {get_table, self(), mnesia_sticky_locks},
receive {mnesia_sticky_locks, Locks} -> Locks end.
get_held() ->
mnesia_locker ! {get_table, self(), mnesia_sticky_locks},
receive {mnesia_sticky_locks, Locks} -> Locks end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
unbound1(suite) -> [];
unbound1(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
?match({atomic, ok}, mnesia:create_table(ul, [])),
Tester = self(),
Write = fun() ->
mnesia:write({ul, {key, {17,42}}, val}),
?log("~p Got write lock waiting...~n", [self()]),
Tester ! continue,
receive
continue ->
ok
end,
?log("..continuing~n", []),
ok
end,
{success, [A]} = ?start_activities([Node1]),
?start_transactions([A]),
A ! Write,
receive continue -> ok end,
Match = fun() ->
case catch mnesia:match_object({ul, {key, {'_', '$0'}}, '_'}) of
{'EXIT', What} -> %% Cyclic first time
?log("Cyclic Restarting~n", []),
A ! continue,
A ! end_trans,
exit(What);
Res ->
?log("Got match log ~p...~n", [Res]),
Res
end
end,
?match({atomic, [{ul,{key,{17,42}},val}]}, mnesia:transaction(Match)),
?match_receive({A, ok}),
?match_receive({A, {atomic, end_trans}}),
ok.
unbound2(suite) -> [];
unbound2(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
?match({atomic, ok}, mnesia:create_table(ul, [])),
{success, [B, A]} = ?start_activities([Node1, Node1]),
Me = self(),
Write = fun() ->
mnesia:write({ul, {key, {17,42}}, val}),
?log("~p Got write lock waiting... Tid ~p ~n",
[self(), get(mnesia_activity_state)]),
Me ! ok_lock,
receive
continue ->
ok
end,
?log("..continuing~n", []),
ok
end,
Match = fun() ->
receive
continueB ->
?log("~p, moving on TID ~p~n",
[self(), get(mnesia_activity_state)]),
Me ! {self(), continuing}
end,
case catch mnesia:match_object({ul, {key, {'_', '$0'}},
'_'}) of
{'EXIT', What} -> %% Cyclic first time
?log("Cyclic Restarting ~p ~n", [What]),
{should_not_happen,What};
Res ->
?log("Got match log ~p...~n", [Res]),
Res
end
end,
B ! fun() -> mnesia:transaction(Match) end,
timer:sleep(100), %% Let B be started first..
A ! fun() -> mnesia:transaction(Write) end,
receive ok_lock -> ok end,
B ! continueB,
?match_receive({B, continuing}),
%% B should now be in lock queue.
A ! continue,
?match_multi_receive([{A, {atomic, ok}},
{B, {atomic, [{ul,{key,{17,42}},val}]}}]),
ok.
receiver() ->
receive
{_Pid, begin_trans} ->
receiver();
Else ->
Else
after
10000 ->
timeout
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
create_table(suite) -> [];
create_table(Config) when is_list(Config) ->
[ThisNode, Node2] = ?acquire_nodes(2, Config),
Tab = c_t_tab,
Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
?match_receive({A, ok}), %% A is executed
DiskMaybe = mnesia_test_lib:storage_type(disc_copies, Config),
Pid = spawn_link(?MODULE, op, [self(), mnesia, create_table,
[test_tab1, [{DiskMaybe, [ThisNode]}]]]),
?match_multi_receive([{Pid, {atomic, ok}},
{'EXIT', Pid, normal}]), %% No Locks! op should be exec.
Pid2 = spawn_link(?MODULE, op, [self(), mnesia, create_table,
[test_tab2, [{ram_copies, [Node2]}]]]),
?match_multi_receive([{Pid2, {atomic, ok}},
{'EXIT', Pid2, normal}]), %% No Locks! op should be exec.
A ! end_trans,
?match_receive({A,{atomic,end_trans}}),
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
delete_table(suite) -> [];
delete_table(Config) when is_list(Config) ->
[ThisNode, Node2] = ?acquire_nodes(2, Config),
Tab = d_t_tab,
Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:read({Tab, 1}) end,
?match_receive({A, [{Tab, 1, 1, 0}]}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia, delete_table,
[Tab]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
move_table_copy(suite) -> [];
move_table_copy(Config) when is_list(Config) ->
[ThisNode, Node2] = ?acquire_nodes(2, Config),
Tab = m_t_c_tab,
Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 1, 2, 3}) end,
?match_receive({A, ok}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia, move_table_copy,
[Tab, ThisNode, Node2]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
timer:sleep(500), %% Don't know how to sync this !!!
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
sys:get_status(whereis(mnesia_tm)), % Explicit sync, release locks is async
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
add_table_index(suite) -> [];
add_table_index(Config) when is_list(Config) ->
[ThisNode, _Node2] = ?acquire_nodes(2, Config ++ [{tc_timeout, 60000}]),
Tab = a_t_i_tab,
Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
?match_receive({A, ok}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia,
add_table_index, [Tab, attr1]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
del_table_index(suite) -> [];
del_table_index(Config) when is_list(Config) ->
[ThisNode, _Node2] = ?acquire_nodes(2, Config),
Tab = d_t_i_tab,
Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
?match({atomic, ok}, mnesia:add_table_index(Tab, attr1)),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 51, 51, attr2}) end,
?match_receive({A, ok}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia, del_table_index,
[Tab, attr1]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
%% Locks released! op should be exec.
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
transform_table(suite) -> [];
transform_table(Config) when is_list(Config) ->
[ThisNode, Node2] = ?acquire_nodes(2, Config),
Tab = t_t_tab,
Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:read({Tab, 1}) end,
?match_receive({A, [{Tab, 1, 1, 0}]}), %% A is executed
Transform = fun({Table, Key, Attr1, Attr2}) -> % Need todo a transform
{Table, Key, {Attr1, Attr2}} end,
Pid = spawn_link(?MODULE, op, [self(), mnesia, transform_table,
[Tab, Transform, [key, attr1]]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
snmp_open_table(suite) -> [];
snmp_open_table(Config) when is_list(Config) ->
[ThisNode, _Node2] = ?acquire_nodes(2, Config),
Tab = s_o_t_tab,
Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 1, 1, 100}) end,
?match_receive({A, ok}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia, snmp_open_table,
[Tab, [{key, integer}]]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
%% Locks released! op should be exec. Can take a while (thats the timeout)
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
snmp_close_table(suite) -> [];
snmp_close_table(Config) when is_list(Config) ->
[ThisNode, _Node2] = ?acquire_nodes(2, Config),
Tab = s_c_t_tab,
Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
?match({atomic, ok}, mnesia:snmp_open_table(Tab, [{key, integer}])),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 1, 1, 100}) end,
?match_receive({A, ok}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia, snmp_close_table, [Tab]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
%% Locks released! op should be exec. Can take a while (thats the timeout)
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
change_table_copy_type(suite) -> [];
change_table_copy_type(Config) when is_list(Config) ->
[ThisNode, _Node2] = ?acquire_nodes(2, Config),
Tab = c_t_c_t_tab,
Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
?match_receive({A, ok}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia, change_table_copy_type,
[Tab, ThisNode, disc_copies]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
change_table_access(suite) -> [];
change_table_access(Config) when is_list(Config) ->
[ThisNode, _Node2] = ?acquire_nodes(2, Config),
Tab = c_t_a_tab,
Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
?match_receive({A, ok}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia, change_table_access_mode,
[Tab, read_only]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
add_table_copy(suite) -> [];
add_table_copy(Config) when is_list(Config) ->
[ThisNode, Node2] = ?acquire_nodes(2, Config),
Tab = a_t_c_tab,
Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
?match_receive({A, ok}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia, add_table_copy,
[Tab, Node2, ram_copies]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
del_table_copy(suite) -> [];
del_table_copy(Config) when is_list(Config) ->
[ThisNode, Node2] = ?acquire_nodes(2, Config),
Tab = d_t_c_tab,
Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 1, 2, 5}) end,
?match_receive({A, ok}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia, del_table_copy,
[Tab, ThisNode]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A, {atomic,end_trans}}),
?match_receive({Pid, {atomic, ok}}),
?match_receive({'EXIT', Pid, normal}),
timer:sleep(500), %% Don't know how to sync this !!!
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
sys:get_status(whereis(mnesia_tm)), % Explicit sync, release locks is async
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
dump_tables(suite) -> [];
dump_tables(Config) when is_list(Config) ->
[ThisNode, Node2] = ?acquire_nodes(2, Config),
Tab = dump_t_tab,
Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}],
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 50),
{success, [A]} = ?start_activities([ThisNode]),
mnesia_test_lib:start_sync_transactions([A], 0),
A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
?match_receive({A, ok}), %% A is executed
Pid = spawn_link(?MODULE, op, [self(), mnesia, dump_tables,
[[Tab]]]),
?match_receive(timeout), %% op waits for locks occupied by A
A ! end_trans, %% Kill A, locks should be released
?match_receive({A,{atomic,end_trans}}),
receive
Msg -> ?match({Pid, {atomic, ok}}, Msg)
after
timer:seconds(20) -> ?error("Operation timed out", [])
end,
sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
?match([], mnesia:system_info(held_locks)),
?match([], mnesia:system_info(lock_queue)),
ok.
op(Father, Mod, Fun, Args) ->
Res = apply(Mod, Fun, Args),
Father ! {self(), Res}.
insert(_Tab, 0) -> ok;
insert(Tab, N) when N > 0 ->
ok = mnesia:sync_dirty(fun() -> mnesia:write({Tab, N, N, 0}) end),
insert(Tab, N-1).
update_own(Tab, Key, Acc) ->
Update =
fun() ->
Res = mnesia:read({Tab, Key}),
case Res of
[{Tab, Key, Extra, Acc}] ->
mnesia:write({Tab,Key,Extra, Acc+1});
Val ->
{read, Val, {acc, Acc}}
end
end,
receive
{Pid, quit} -> Pid ! {self(), Acc}
after
0 ->
case mnesia:transaction(Update) of
{atomic, ok} ->
update_own(Tab, Key, Acc+1);
Else ->
?error("Trans failed on ~p with ~p~n"
"Info w2read ~p w2write ~p w2commit ~p storage ~p ~n",
[node(),
Else,
mnesia:table_info(Tab, where_to_read),
mnesia:table_info(Tab, where_to_write),
mnesia:table_info(Tab, where_to_commit),
mnesia:table_info(Tab, storage_type)])
end
end.
update_shared(Tab, Me, Acc) ->
Update =
fun() ->
W2R = mnesia:table_info(Tab, where_to_read),
Res = mnesia:read({Tab, 0}),
case Res of
[{Tab, Key, Extra, Val}] when element(Me, Extra) == Acc ->
Extra1 = setelement(Me, Extra, Acc+1),
Term = {Tab, Key, Extra1, Val+1},
ok = mnesia:write(Term),
% ?log("At ~p: ~p w2r ~p w2w ~p ~n",
% [node(), Term,
% mnesia:table_info(Tab, where_to_read),
W2W = mnesia:table_info(Tab, where_to_write),
W2C = mnesia:table_info(Tab, where_to_commit),
%% mnesia:table_info(Tab, storage_type)
% ]),
{_Mod, Tid, Ts} = get(mnesia_activity_state),
io:format("~p ~p~n", [Tid, ets:tab2list(element(2,Ts))]),
{ok,Term,{W2R,W2W,W2C}};
Val ->
Info = [{acc, Acc}, {me, Me},
{tid, element(2, mnesia:get_activity_id())},
{locks, mnesia:system_info(held_locks)}],
{read, Val, Info}
end
end,
receive
{Pid, quit} -> Pid ! {self(), Acc}
after
0 ->
case mnesia:transaction(Update) of
{atomic, {ok,Term,W2}} ->
io:format("~p:~p:(~p,~p) ~w@~w~n",
[erlang:unique_integer([monotonic,positive]),
node(),Me,Acc,Term,W2]),
update_shared(Tab, Me, Acc+1);
Else ->
?error("Trans failed on ~p with ~p~n"
"Info w2read ~p w2write ~p w2commit ~p storage ~p ~n",
[node(),
Else,
mnesia:table_info(Tab, where_to_read),
mnesia:table_info(Tab, where_to_write),
mnesia:table_info(Tab, where_to_commit),
mnesia:table_info(Tab, storage_type)
])
end
end.
init_admin(Def, N1, N2, N3) ->
Tab = schema_ops,
?match({atomic, ok}, mnesia:create_table(Tab, Def)),
insert(Tab, 1002),
Pid1 = spawn_link(N1, ?MODULE, update_own, [Tab, 1, 0]),
Pid2 = spawn_link(N2, ?MODULE, update_own, [Tab, 2, 0]),
Pid3 = spawn_link(N3, ?MODULE, update_own, [Tab, 3, 0]),
?match({atomic, ok},
mnesia:transaction(fun() -> mnesia:write({Tab, 0, {0,0,0}, 0}) end)),
Pid4 = spawn_link(N1, ?MODULE, update_shared, [Tab, 1, 0]),
Pid5 = spawn_link(N2, ?MODULE, update_shared, [Tab, 2, 0]),
Pid6 = spawn_link(N3, ?MODULE, update_shared, [Tab, 3, 0]),
{Pid1, Pid2, Pid3, Pid4, Pid5, Pid6}.
verify_results({P1, P2, P3, P4, P5, P6}) ->
Tab = schema_ops, N1 = node(P1), N2 = node(P2), N3 = node(P3),
try
P1 ! {self(), quit},
R1 = receive {P1, Res1} -> Res1 after 9000 -> throw({timeout,P1}) end,
P2 ! {self(), quit},
R2 = receive {P2, Res2} -> Res2 after 9000 -> throw({timeout,P2}) end,
P3 ! {self(), quit},
R3 = receive {P3, Res3} -> Res3 after 9000 -> throw({timeout,P3}) end,
P4 ! {self(), quit},
R4 = receive {P4, Res4} -> Res4 after 9000 -> throw({timeout,P4}) end,
P5 ! {self(), quit},
R5 = receive {P5, Res5} -> Res5 after 9000 -> throw({timeout,P5}) end,
P6 ! {self(), quit},
R6 = receive {P6, Res6} -> Res6 after 9000 -> throw({timeout,P6}) end,
?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write_lock_table(Tab) end)),
?log("Should be ~p~n", [R1]),
?match([{_, _, _, R1}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])),
?match([{_, _, _, R1}], rpc:call(N2, mnesia, dirty_read, [{Tab, 1}])),
?match([{_, _, _, R1}], rpc:call(N3, mnesia, dirty_read, [{Tab, 1}])),
?log("Should be ~p~n", [R2]),
?match([{_, _, _, R2}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])),
?match([{_, _, _, R2}], rpc:call(N2, mnesia, dirty_read, [{Tab, 2}])),
?match([{_, _, _, R2}], rpc:call(N3, mnesia, dirty_read, [{Tab, 2}])),
?log("Should be ~p~n", [R3]),
?match([{_, _, _, R3}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])),
?match([{_, _, _, R3}], rpc:call(N2, mnesia, dirty_read, [{Tab, 3}])),
?match([{_, _, _, R3}], rpc:call(N3, mnesia, dirty_read, [{Tab, 3}])),
Res = R4+R5+R6,
?log("Should be {~p+~p+~p}= ~p~n", [R4, R5, R6, Res]),
?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N1, mnesia, dirty_read, [{Tab, 0}])),
?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N2, mnesia, dirty_read, [{Tab, 0}])),
?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N3, mnesia, dirty_read, [{Tab, 0}]))
catch throw:{timeout, Pid} ->
mnesia_lib:dist_coredump(),
?error("Timeout ~p ~n", [Pid])
end.
get_info(Tab) ->
Info = mnesia:table_info(Tab, all),
mnesia_lib:verbose("~p~n", [Info]).
del_table_copy_1(suite) -> [];
del_table_copy_1(Config) when is_list(Config) ->
[_Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config),
del_table(Node2, Node2, Nodes). %Called on same Node as deleted
del_table_copy_2(suite) -> [];
del_table_copy_2(Config) when is_list(Config) ->
[Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config),
del_table(Node1, Node2, Nodes). %Called from other Node
del_table_copy_3(suite) -> [];
del_table_copy_3(Config) when is_list(Config) ->
[_Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
del_table(Node3, Node2, Nodes). %Called from Node w.o. table
%%% The actual test
del_table(CallFrom, DelNode, [Node1, Node2, Node3]) ->
Def = [{ram_copies, [Node1]}, {disc_copies, [Node2]},
{attributes, [key, attr1, attr2]}],
Tab = schema_ops,
Pids = init_admin(Def, Node1, Node2, Node3),
?log("Call from ~p delete table from ~p ~n", [CallFrom, DelNode]),
rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
?match({atomic, ok},
rpc:call(CallFrom, mnesia, del_table_copy, [Tab, DelNode])),
verify_results(Pids),
rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
?verify_mnesia([Node1, Node2, Node3], []).
add_table_copy_1(suite) -> [];
add_table_copy_1(Config) when is_list(Config) ->
[Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
Def = [{disc_only_copies, [Node1, Node2]},
{attributes, [key, attr1, attr2]}],
add_table(Node1, Node3, Nodes, Def).
add_table_copy_2(suite) -> [];
add_table_copy_2(Config) when is_list(Config) ->
[Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
Def = [{disc_only_copies, [Node1, Node2]},
{attributes, [key, attr1, attr2]}],
add_table(Node2, Node3, Nodes, Def).
add_table_copy_3(suite) -> [];
add_table_copy_3(Config) when is_list(Config) ->
[Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
Def = [{disc_only_copies, [Node1, Node2]},
{attributes, [key, attr1, attr2]}],
add_table(Node3, Node3, Nodes, Def).
add_table_copy_4(suite) -> [];
add_table_copy_4(Config) when is_list(Config) ->
[Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
Def = [{disc_only_copies, [Node1]},
{attributes, [key, attr1, attr2]}],
add_table(Node2, Node3, Nodes, Def).
%%% The actual test
add_table(CallFrom, AddNode, [Node1, Node2, Node3], Def) ->
Pids = init_admin(Def, Node1, Node2, Node3),
Tab = schema_ops,
?log("Call from ~p add table to ~p ~n", [CallFrom, AddNode]),
rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
?match({atomic, ok}, rpc:call(CallFrom, mnesia, add_table_copy,
[Tab, AddNode, ram_copies])),
verify_results(Pids),
rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
?verify_mnesia([Node1, Node2, Node3], []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
move_table_copy_1(suite) -> [];
move_table_copy_1(Config) when is_list(Config) ->
[Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
Def = [{disc_copies, [Node1, Node2]},
{attributes, [key, attr1, attr2]}],
move_table(Node1, Node1, Node3, Nodes, Def).
move_table_copy_2(suite) -> [];
move_table_copy_2(Config) when is_list(Config) ->
[Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
Def = [{disc_copies, [Node1, Node2]},
{attributes, [key, attr1, attr2]}],
move_table(Node2, Node1, Node3, Nodes, Def).
move_table_copy_3(suite) -> [];
move_table_copy_3(Config) when is_list(Config) ->
[Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
Def = [{disc_copies, [Node1, Node2]},
{attributes, [key, attr1, attr2]}],
move_table(Node3, Node1, Node3, Nodes, Def).
move_table_copy_4(suite) -> [];
move_table_copy_4(Config) when is_list(Config) ->
[Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
Def = [{disc_copies, [Node1]},
{attributes, [key, attr1, attr2]}],
move_table(Node2, Node1, Node3, Nodes, Def).
%%% The actual test
move_table(CallFrom, FromNode, ToNode, [Node1, Node2, Node3], Def) ->
Pids = init_admin(Def, Node1, Node2, Node3),
Tab = schema_ops,
?log("Call from ~p move table from ~p to ~p ~n", [CallFrom, FromNode, ToNode]),
rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
?match({atomic, ok}, rpc:call(CallFrom, mnesia, move_table_copy,
[Tab, FromNode, ToNode])),
verify_results(Pids),
rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
?verify_mnesia([Node1, Node2, Node3], []).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
dirty_updates_visible_direct(doc) ->
["One process can immediately see dirty updates of another"];
dirty_updates_visible_direct(suite) -> [];
dirty_updates_visible_direct(Config) when is_list(Config) ->
dirty_visibility(outside_trans, Config).
dirty_reads_regardless_of_trans(doc) ->
["Dirty reads are not affected by transaction context"];
dirty_reads_regardless_of_trans(suite) -> [];
dirty_reads_regardless_of_trans(Config) when is_list(Config) ->
dirty_visibility(inside_trans, Config).
dirty_visibility(Mode, Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = list_to_atom(lists:concat([dirty_visibility, '_', Mode])),
?match({atomic, ok}, mnesia:create_table([{name, Tab}, {ram_copies, [Node1]}])),
ValPos = 3,
?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
%% Start two processes
{success, [A]} = ?start_activities([Node1]),
case Mode of
inside_trans ->
?start_transactions([A]),
A ! fun() ->
mnesia:write({Tab, a, 11}),
mnesia:write({Tab, b, 22}),
mnesia:write({Tab, c, 1}),
mnesia:write({Tab, d, 2}),
mnesia:write({Tab, e, 3}),
lists:sort(mnesia:all_keys(Tab))
end,
?match_receive({A, [a, b, c, d, e]});
outside_trans ->
ignore
end,
RecA = {Tab, a, 1},
PatA = {Tab, '$1', 1},
RecB = {Tab, b, 3},
PatB = {Tab, '$1', 3},
RecB2 = {Tab, b, 2},
PatB2 = {Tab, '$1', 2},
?match([], mnesia:dirty_read({Tab, a})),
?match([], mnesia:dirty_read({Tab, b})),
?match([], mnesia:dirty_match_object(PatA)),
?match([], mnesia:dirty_match_object(PatB)),
?match([], mnesia:dirty_match_object(PatB2)),
?match([], mnesia:dirty_index_read(Tab, 1, ValPos)),
?match([], mnesia:dirty_index_read(Tab, 3, ValPos)),
?match([], mnesia:dirty_index_match_object(PatA, ValPos)),
?match([], mnesia:dirty_index_match_object(PatB, ValPos)),
?match([], mnesia:dirty_index_match_object(PatB2, ValPos)),
?match('$end_of_table', mnesia:dirty_first(Tab)),
%% dirty_write
A ! fun() -> mnesia:dirty_write(RecA) end,
?match_receive({A, ok}),
?match([RecA], mnesia:dirty_read({Tab, a})),
?match([RecA], mnesia:dirty_match_object(PatA)),
?match(a, mnesia:dirty_first(Tab)),
?match([RecA], mnesia:dirty_index_read(Tab, 1, ValPos)),
?match([RecA], mnesia:dirty_index_match_object(PatA, ValPos)),
?match('$end_of_table', mnesia:dirty_next(Tab, a)),
%% dirty_create
A ! fun() -> mnesia:dirty_write(RecB) end,
?match_receive({A, ok}),
?match([RecB], mnesia:dirty_read({Tab, b})),
?match([RecB], mnesia:dirty_match_object(PatB)),
?match([RecB], mnesia:dirty_index_read(Tab, 3, ValPos)),
?match([RecB], mnesia:dirty_index_match_object(PatB, ValPos)),
?match('$end_of_table',
mnesia:dirty_next(Tab, mnesia:dirty_next(Tab, mnesia:dirty_first(Tab)))),
%% dirty_update_counter
A ! fun() -> mnesia:dirty_update_counter({Tab, b}, -1) end,
?match_receive({A, _}),
?match([RecB2], mnesia:dirty_read({Tab, b})),
?match([], mnesia:dirty_match_object(PatB)),
?match([RecB2], mnesia:dirty_match_object(PatB2)),
?match([RecB2], mnesia:dirty_index_read(Tab, 2, ValPos)),
?match([], mnesia:dirty_index_match_object(PatB, ValPos)),
?match([RecB2], mnesia:dirty_index_match_object(PatB2, ValPos)),
?match('$end_of_table',
mnesia:dirty_next(Tab, mnesia:dirty_next(Tab, mnesia:dirty_first(Tab)))),
%% dirty_delete
A ! fun() -> mnesia:dirty_delete({Tab, b}) end,
?match_receive({A, ok}),
?match([], mnesia:dirty_read({Tab, b})),
?match([], mnesia:dirty_match_object(PatB2)),
?match([], mnesia:dirty_index_read(Tab, 3, ValPos)),
?match([], mnesia:dirty_index_match_object(PatB2, ValPos)),
?match(a, mnesia:dirty_first(Tab)),
?match('$end_of_table', mnesia:dirty_next(Tab, a)),
%% dirty_delete_object
?match([RecA], mnesia:dirty_match_object(PatA)),
A ! fun() -> mnesia:dirty_delete_object(RecA) end,
?match_receive({A, ok}),
?match([], mnesia:dirty_read({Tab, a})),
?match([], mnesia:dirty_match_object(PatA)),
?match([], mnesia:dirty_index_read(Tab, 1, ValPos)),
?match([], mnesia:dirty_index_match_object(PatA, ValPos)),
?match('$end_of_table', mnesia:dirty_first(Tab)),
case Mode of
inside_trans ->
A ! end_trans,
?match_receive({A, {atomic, end_trans}});
outside_trans ->
ignore
end,
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
trans_update_invisibible_outside_trans(doc) ->
["Updates in a transaction are invisible outside the transaction"];
trans_update_invisibible_outside_trans(suite) -> [];
trans_update_invisibible_outside_trans(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = trans_update_invisibible_outside_trans,
?match({atomic, ok}, mnesia:create_table([{name, Tab},
{ram_copies, [Node1]}])),
ValPos = 3,
RecA = {Tab, a, 1},
PatA = {Tab, '$1', 1},
RecB = {Tab, b, 3},
PatB = {Tab, '$1', 3},
?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
Verify =
fun() ->
?match([], mnesia:dirty_read({Tab, a})),
?match([], mnesia:dirty_read({Tab, b})),
?match([], mnesia:dirty_match_object(PatA)),
?match([], mnesia:dirty_match_object(PatB)),
?match([], mnesia:dirty_index_read(Tab, 1, ValPos)),
?match([], mnesia:dirty_index_read(Tab, 3, ValPos)),
?match([], mnesia:dirty_index_match_object(PatA, ValPos)),
?match([], mnesia:dirty_index_match_object(PatB, ValPos)),
?match('$end_of_table', mnesia:dirty_first(Tab))
end,
Fun = fun() ->
?match(ok, mnesia:write(RecA)),
Verify(),
?match(ok, mnesia:write(RecB)),
Verify(),
?match(ok, mnesia:delete({Tab, b})),
Verify(),
?match([RecA], mnesia:match_object(PatA)),
Verify(),
?match(ok, mnesia:delete_object(RecA)),
Verify(),
ok
end,
?match({atomic, ok}, mnesia:transaction(Fun)),
Verify(),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
trans_update_visible_inside_trans(doc) ->
["Updates in a transaction are visible in the same transaction"];
trans_update_visible_inside_trans(suite) -> [];
trans_update_visible_inside_trans(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = trans_update_visible_inside_trans,
?match({atomic, ok}, mnesia:create_table([{name, Tab},
{ram_copies, [Node1]}])),
ValPos = 3,
RecA = {Tab, a, 1},
PatA = {Tab, '$1', 1},
RecB = {Tab, b, 3},
PatB = {Tab, '$1', 3},
?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
Fun = fun() ->
%% write
?match(ok, mnesia:write(RecA)),
?match([RecA], mnesia:read({Tab, a})),
?match([RecA], mnesia:wread({Tab, a})),
?match([RecA], mnesia:match_object(PatA)),
?match([a], mnesia:all_keys(Tab)),
?match([RecA], mnesia:index_match_object(PatA, ValPos)),
?match([RecA], mnesia:index_read(Tab, 1, ValPos)),
%% create
?match(ok, mnesia:write(RecB)),
?match([RecB], mnesia:read({Tab, b})),
?match([RecB], mnesia:wread({Tab, b})),
?match([RecB], mnesia:match_object(PatB)),
?match([RecB], mnesia:index_match_object(PatB, ValPos)),
?match([RecB], mnesia:index_read(Tab, 3, ValPos)),
%% delete
?match(ok, mnesia:delete({Tab, b})),
?match([], mnesia:read({Tab, b})),
?match([], mnesia:wread({Tab, b})),
?match([], mnesia:match_object(PatB)),
?match([a], mnesia:all_keys(Tab)),
?match([], mnesia:index_match_object(PatB, ValPos)),
?match([], mnesia:index_read(Tab, 2, ValPos)),
?match([], mnesia:index_read(Tab, 3, ValPos)),
%% delete_object
?match(ok, mnesia:delete_object(RecA)),
?match([], mnesia:read({Tab, a})),
?match([], mnesia:wread({Tab, a})),
?match([], mnesia:match_object(PatA)),
?match([], mnesia:all_keys(Tab)),
?match([], mnesia:index_match_object(PatA, ValPos)),
?match([], mnesia:index_read(Tab, 2, ValPos)),
?match([], mnesia:index_read(Tab, 3, ValPos)),
ok
end,
?match({atomic, ok}, mnesia:transaction(Fun)),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
write_shadows(doc) ->
["Tests whether the shadow shows the correct object when",
"writing to the table"];
write_shadows(suite) -> [];
write_shadows(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = write_shadows,
?match({atomic, ok}, mnesia:create_table([{name, Tab},
{ram_copies, [Node1]},
{type, set}])),
ValPos = 3,
RecA1 = {Tab, a, 1},
PatA1 = {Tab, '$1', 1},
RecA2 = {Tab, a, 2},
PatA2 = {Tab, '$1', 2},
?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
Fun1 = fun() ->
?match(ok, mnesia:write(RecA1)),
ok
end,
?match({atomic, ok}, mnesia:transaction(Fun1)),
Fun2 = fun() ->
%% write shadow old write - is the confirmed value visable
%% in the shadow ?
?match([RecA1], mnesia:read({Tab, a})),
?match([RecA1], mnesia:wread({Tab, a})),
?match([RecA1], mnesia:match_object(PatA1)),
?match([a], mnesia:all_keys(Tab)),
?match([RecA1], mnesia:index_match_object(PatA1, ValPos)),
?match([RecA1], mnesia:index_read(Tab, 1, ValPos)),
%% write shadow new write - is a new value visable instead
%% of the old value ?
?match(ok, mnesia:write(RecA2)),
?match([RecA2], mnesia:read({Tab, a})),
?match([RecA2], mnesia:wread({Tab, a})),
?match([], mnesia:match_object(PatA1)), %% delete shadow old but not new write
?match([RecA2], mnesia:match_object(PatA2)), %% is the new value visable
?match([a], mnesia:all_keys(Tab)),
?match([RecA2], mnesia:index_match_object(PatA2, ValPos)),
?match([RecA2], mnesia:index_read(Tab, 2, ValPos)),
ok
end,
?match({atomic, ok}, mnesia:transaction(Fun2)),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
delete_shadows(doc) ->
["Test whether the shadow shows the correct object when deleting objects"];
delete_shadows(suite) -> [];
delete_shadows(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = delete_shadows,
?match({atomic, ok}, mnesia:create_table([{name, Tab},
{ram_copies, [Node1]},
{type, set}])),
ValPos = 3,
OidA = {Tab, a},
RecA1 = {Tab, a, 1},
PatA1 = {Tab, '$1', 1},
RecA2 = {Tab, a, 2},
PatA2 = {Tab, '$1', 2},
?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
Fun1 = fun() ->
?match(ok, mnesia:write(RecA1)),
ok
end,
?match({atomic, ok}, mnesia:transaction(Fun1)),
Fun2 = fun() ->
%% delete shadow old write - is the confirmed value invisible
%% when deleted in the transaction ?
?match(ok, mnesia:delete(OidA)),
?match([], mnesia:read({Tab, a})),
?match([], mnesia:wread({Tab, a})),
?match([], mnesia:match_object(PatA1)),
?match([], mnesia:all_keys(Tab)),
?match([], mnesia:index_match_object(PatA1, ValPos)),
?match([], mnesia:index_read(Tab, 1, ValPos)),
%% delete shadow old but not new write - is the new value visable
%% when the old one was deleted ?
?match(ok, mnesia:write(RecA2)),
?match([RecA2], mnesia:read({Tab, a})),
?match([RecA2], mnesia:wread({Tab, a})),
?match([], mnesia:match_object(PatA1)),
?match([RecA2], mnesia:match_object(PatA2)),
?match([a], mnesia:all_keys(Tab)),
?match([RecA2], mnesia:index_match_object(PatA2, ValPos)),
?match([RecA2], mnesia:index_read(Tab, 2, ValPos)),
%% delete shadow old and new write - is the new value invisable
%% when deleted ?
?match(ok, mnesia:delete(OidA)),
?match([], mnesia:read({Tab, a})),
?match([], mnesia:wread({Tab, a})),
?match([], mnesia:match_object(PatA2)),
?match([], mnesia:all_keys(Tab)),
?match([], mnesia:index_match_object(PatA2, ValPos)),
?match([], mnesia:index_read(Tab, 2, ValPos)),
ok
end,
?match({atomic, ok}, mnesia:transaction(Fun2)),
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
write_delete_shadows_bag(doc) ->
["Test the visibility of written and deleted objects in an bag type table"];
write_delete_shadows_bag(suite) -> [];
write_delete_shadows_bag(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = write_delete_shadows_bag,
?match({atomic, ok}, mnesia:create_table([{name, Tab},
{ram_copies, [Node1]},
{type, bag}])),
ValPos = 3,
OidA = {Tab, a},
RecA1 = {Tab, a, 1},
PatA1 = {Tab, '$1', 1},
RecA2 = {Tab, a, 2},
PatA2 = {Tab, '$1', 2},
RecA3 = {Tab, a, 3},
PatA3 = {Tab, '$1', 3},
PatA = {Tab, a, '_'},
?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
Fun1 = fun() ->
?match(ok, mnesia:write(RecA1)),
?match(ok, mnesia:write(RecA2)),
ok
end,
?match({atomic, ok}, mnesia:transaction(Fun1)),
Fun2 = fun() ->
%% delete shadow old write - is the confirmed value invisible
%% when deleted in the transaction ?
?match(ok, mnesia:delete_object(RecA1)),
?match([RecA2], mnesia:read({Tab, a})),
?match([RecA2], mnesia:wread({Tab, a})),
?match([RecA2], mnesia:match_object(PatA2)),
?match([a], mnesia:all_keys(Tab)),
?match([RecA2], mnesia:index_match_object(PatA2, ValPos)),
?match([RecA2], mnesia:index_read(Tab, 2, ValPos)),
?match(ok, mnesia:delete(OidA)),
?match([], mnesia:read({Tab, a})),
?match([], mnesia:wread({Tab, a})),
?match([], mnesia:match_object(PatA1)),
?match([], mnesia:all_keys(Tab)),
?match([], mnesia:index_match_object(PatA1, ValPos)),
?match([], mnesia:index_read(Tab, 1, ValPos)),
%% delete shadow old but not new write - are both new value visable
%% when the old one was deleted ?
?match(ok, mnesia:write(RecA2)),
?match(ok, mnesia:write(RecA3)),
?match([RecA2, RecA3], lists:sort(mnesia:read({Tab, a}))),
?match([RecA2, RecA3], lists:sort(mnesia:wread({Tab, a}))),
?match([RecA2], mnesia:match_object(PatA2)),
?match([a], mnesia:all_keys(Tab)),
?match([RecA2, RecA3], lists:sort(mnesia:match_object(PatA))),
?match([RecA2], mnesia:index_match_object(PatA2, ValPos)),
?match([RecA3], mnesia:index_match_object(PatA3, ValPos)),
?match([RecA2], mnesia:index_read(Tab, 2, ValPos)),
%% delete shadow old and new write - is the new value invisable
%% when deleted ?
?match(ok, mnesia:delete(OidA)),
?match([], mnesia:read({Tab, a})),
?match([], mnesia:wread({Tab, a})),
?match([], mnesia:match_object(PatA2)),
?match([], mnesia:all_keys(Tab)),
?match([], mnesia:index_match_object(PatA2, ValPos)),
?match([], mnesia:index_read(Tab, 2, ValPos)),
ok
end,
?match({atomic, ok}, mnesia:transaction(Fun2)),
ok.
write_delete_shadows_bag2(doc) ->
["Test the visibility of written and deleted objects in an bag type table "
"and verifies the results"];
write_delete_shadows_bag2(suite) -> [];
write_delete_shadows_bag2(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab = w_d_s_b,
?match({atomic, ok}, mnesia:create_table([{name, Tab},
{ram_copies, [Node1]},
{type, bag}])),
Del = fun() ->
R1 = mnesia:read({Tab, 1}),
mnesia:delete({Tab, 1}),
R2 = mnesia:read({Tab, 1}),
mnesia:write({Tab, 1, 1}),
mnesia:write({Tab, 1, 2}),
R3 = mnesia:read({Tab, 1}),
{R1, R2, R3}
end,
DelObj = fun() ->
R1 = mnesia:read({Tab, 2}),
mnesia:delete_object({Tab, 2, 2}),
R2 = mnesia:read({Tab, 2}),
mnesia:write({Tab, 2, 1}),
mnesia:write({Tab, 2, 2}),
R3 = mnesia:read({Tab, 2}),
{R1, R2, R3}
end,
Both1 = [{Tab, 1, 1}, {Tab, 1, 2}],
Both2 = [{Tab, 2, 1}, {Tab, 2, 2}],
?match({atomic, {[], [], Both1}}, mnesia:transaction(Del)),
?match({atomic, {Both1, [], Both1}}, mnesia:transaction(Del)),
?match({atomic, Both1}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
?match({atomic, {[], [], Both2}}, mnesia:transaction(DelObj)),
?match({atomic, {Both2, [{Tab, 2, 1}], Both2}}, mnesia:transaction(DelObj)),
?match({atomic, Both2}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)),
?verify_mnesia([Node1], []).
shadow_search(doc) ->
["Verifies that ordered_set tables are ordered, and the order is kept"
"even when table is shadowed by transaction updates"];
shadow_search(suite) -> [];
shadow_search(Config) when is_list(Config) ->
[Node1] = ?acquire_nodes(1, Config),
Tab1 = ss_oset,
Tab2 = ss_set,
Tab3 = ss_bag,
Tabs = [Tab1,Tab2,Tab3],
RecName = ss,
?match({atomic, ok}, mnesia:create_table([{name, Tab1},
{ram_copies, [Node1]},
{record_name, RecName},
{type, ordered_set}])),
?match({atomic, ok}, mnesia:create_table([{name, Tab2},
{record_name, RecName},
{ram_copies, [Node1]},
{type, set}])),
?match({atomic, ok}, mnesia:create_table([{name, Tab3},
{record_name, RecName},
{ram_copies, [Node1]},
{type, bag}])),
Recs = [{RecName, K, K} || K <- [1,3,5]],
[mnesia:dirty_write(Tab1, R) || R <- Recs],
[mnesia:dirty_write(Tab2, R) || R <- Recs],
[mnesia:dirty_write(Tab3, R) || R <- Recs],
Match = fun(Tab) -> mnesia:match_object(Tab, {'_','_','_'}, write) end,
Select = fun(Tab) -> mnesia:select(Tab, [{'_', [], ['$_']}]) end,
% Trans = fun(Fun,Args) -> mnesia:transaction(Fun,Args) end,
LoopHelp = fun('$end_of_table',_) -> [];
({Res,Cont},Fun) ->
Sel = mnesia:select(Cont),
Res ++ Fun(Sel, Fun)
end,
SelLoop = fun(Table) ->
Sel = mnesia:select(Table, [{'_', [], ['$_']}], 1, read),
LoopHelp(Sel,LoopHelp)
end,
R1 = {RecName, 2, 2}, R2 = {RecName, 4, 4},
R3 = {RecName, 2, 3}, R4 = {RecName, 3, 1},
R5 = {RecName, 104, 104},
W1 = fun(Tab,Search) -> mnesia:write(Tab,R1,write),
mnesia:write(Tab,R2,write),
Search(Tab)
end,
S1 = lists:sort([R1,R2|Recs]),
?match({atomic,S1}, mnesia:transaction(W1, [Tab1,Select])),
?match({atomic,S1}, mnesia:transaction(W1, [Tab1,Match])),
?match({atomic,S1}, mnesia:transaction(W1, [Tab1,SelLoop])),
?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,Select]))),
?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,SelLoop]))),
?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,Match]))),
?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,Select]))),
?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,SelLoop]))),
?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,Match]))),
[mnesia:dirty_delete_object(Tab,R) || R <- [R1,R2], Tab <- Tabs],
W2 = fun(Tab,Search) ->
mnesia:write(Tab,R3,write),
mnesia:write(Tab,R1,write),
Search(Tab)
end,
S2 = lists:sort([R1|Recs]),
S2Bag = lists:sort([R1,R3|Recs]),
?match({atomic,S2}, mnesia:transaction(W2, [Tab1,Select])),
?match({atomic,S2}, mnesia:transaction(W2, [Tab1,SelLoop])),
?match({atomic,S2}, mnesia:transaction(W2, [Tab1,Match])),
?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,Select]))),
?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,SelLoop]))),
?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,Match]))),
?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,Select]))),
?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,SelLoop]))),
?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,Match]))),
%% [mnesia:dirty_delete_object(Tab,R) || R <- [R1,R3], Tab <- Tabs],
W3 = fun(Tab,Search) ->
mnesia:write(Tab,R4,write),
mnesia:delete(Tab,element(2,R1),write),
Search(Tab)
end,
S3Bag = lists:sort([R4|lists:delete(R1,Recs)]),
S3 = lists:delete({RecName,3,3},S3Bag),
?match({atomic,S3}, mnesia:transaction(W3, [Tab1,Select])),
?match({atomic,S3}, mnesia:transaction(W3, [Tab1,SelLoop])),
?match({atomic,S3}, mnesia:transaction(W3, [Tab1,Match])),
?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,SelLoop]))),
?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,Select]))),
?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,Match]))),
?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,Select]))),
?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,SelLoop]))),
?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,Match]))),
W4 = fun(Tab,Search) ->
mnesia:delete(Tab,-1,write),
mnesia:delete(Tab,4 ,write),
mnesia:delete(Tab,17,write),
mnesia:delete_object(Tab,{RecName, -1, x},write),
mnesia:delete_object(Tab,{RecName, 4, x},write),
mnesia:delete_object(Tab,{RecName, 42, x},write),
mnesia:delete_object(Tab,R2,write),
mnesia:write(Tab, R5, write),
Search(Tab)
end,
S4Bag = lists:sort([R5|S3Bag]),
S4 = lists:sort([R5|S3]),
?match({atomic,S4}, mnesia:transaction(W4, [Tab1,Select])),
?match({atomic,S4}, mnesia:transaction(W4, [Tab1,SelLoop])),
?match({atomic,S4}, mnesia:transaction(W4, [Tab1,Match])),
?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,Select]))),
?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,SelLoop]))),
?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,Match]))),
?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,Select]))),
?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,SelLoop]))),
?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,Match]))),
[mnesia:dirty_delete_object(Tab,R) || R <- [{RecName,3,3},R5], Tab <- Tabs],
%% hmmm anything more??
?verify_mnesia([Node1], []).
rr_kill_copy(suite) -> [];
rr_kill_copy(Config) when is_list(Config) ->
Ns = ?acquire_nodes(3,Config ++ [{tc_timeout, 60000}]),
DeleteMe = fun(_Tab,Where2read) ->
?match([], mnesia_test_lib:kill_mnesia([Where2read]))
end,
Del = removed_resources(Ns, DeleteMe),
?verify_mnesia(Ns -- [Del], []).
removed_resources([_N1,N2,N3], DeleteRes) ->
Tab = del_res,
?match({atomic, ok}, mnesia:create_table(Tab,[{ram_copies, [N2,N3]}])),
Init = fun() -> [mnesia:write({Tab,Key,Key}) || Key <- lists:seq(0,99)] end,
?match([], [Bad || Bad <- mnesia:sync_dirty(Init), Bad /= ok]),
Where2Read = mnesia:table_info(Tab, where_to_read),
[Keep] = [N2,N3] -- [Where2Read],
Tester = self(),
Conflict = fun() ->
%% Read a value..
[{Tab,1,Val}] = mnesia:read({Tab,1}),
case get(restart) of
undefined ->
Tester ! {pid_1, self()},
%% Wait for sync, the read value have been
%% updated and this function should be restarted.
receive {Tester,sync} -> ok end,
put(restart, restarted);
restarted ->
ok
end,
mnesia:write({Tab,1,Val+10})
end,
Lucky = fun() ->
[{Tab,1,Val}] = mnesia:read({Tab,1}),
mnesia:write({Tab,1,Val+100})
end,
CPid = spawn_link(fun() -> Tester ! {self(), mnesia:transaction(Conflict)} end),
%% sync first transaction
receive {pid_1, CPid} -> synced end,
DeleteRes(Tab, Where2Read),
?match(Keep, mnesia:table_info(Tab, where_to_read)),
%% Run the other/Lucky transaction, this should work since
%% it won't grab a lock on the conflicting transactions Where2Read node.
LPid = spawn_link(Keep, fun() -> Tester ! {self(),mnesia:transaction(Lucky)} end),
?match_receive({LPid,{atomic,ok}}),
%% Continue Transaction no 1
CPid ! {self(), sync},
?match(ok, receive {CPid,{atomic,ok}} -> ok after 2000 -> process_info(self()) end),
?match({atomic,[{del_res,1,111}]}, mnesia:transaction(fun() -> mnesia:read({Tab,1}) end)),
Where2Read.
nasty(suite) -> [];
nasty(doc) ->
["Tries to fullfill a rather nasty locking scenario, where we have had "
"bugs, the testcase tries a combination of locks in locker queue"];
%% This testcase no longer works as it was intended to show errors when
%% tablelocks was allowed to be placed in the queue though locks existed
%% in the queue with less Tid's. This is no longer allowed and the testcase
%% has been update.
nasty(Config) ->
?acquire_nodes(1, Config),
Tab = nasty,
?match({atomic, ok}, mnesia:create_table(Tab, [])),
Coord = self(),
Write = fun(Key) ->
mnesia:write({Tab, Key, write}),
Coord ! {write, Key, self(), mnesia:get_activity_id()},
receive
continue ->
ok
end,
Coord ! {done, {write, Key}, self()}
end,
Update = fun(Key) ->
Coord ! {update, Key, self(), mnesia:get_activity_id()},
receive
continue ->
ok
end,
mnesia:read({Tab, Key}),
mnesia:write({Tab, Key, update}),
receive
continue ->
ok
end,
Coord ! {done, {update, Key}, self()}
end,
TabLock = fun() ->
Coord ! {tablock, Tab, self(), mnesia:get_activity_id()},
receive
continue ->
ok
end,
mnesia:lock({table, Tab}, write),
Coord ! {done, {tablock, Tab}, self()}
end,
Up = spawn_link(mnesia, transaction, [Update, [0]]),
?match_receive({update, 0, Up, _Tid}),
TL = spawn_link(mnesia, transaction, [TabLock]),
?match_receive({tablock, Tab, _Tl, _Tid}),
W0 = spawn_link(mnesia, transaction, [Write, [0]]),
?match_receive({write, 0, W0, _Tid}),
W1 = spawn_link(mnesia, transaction, [Write, [1]]),
?match_receive({write, 1, W1, _Tid}),
%% Nothing should be in msg queue!
?match(timeout, receive A -> A after 1000 -> timeout end),
Up ! continue, %% Should be queued
?match(timeout, receive A -> A after 1000 -> timeout end),
TL ! continue, %% Should be restarted
% ?match({tablock, _, _, _}, receive A -> A after 1000 -> timeout end),
?match(timeout, receive A -> A after 1000 -> timeout end),
LQ1 = mnesia_locker:get_lock_queue(),
?match({2, _}, {length(LQ1), LQ1}),
W0 ! continue, % Up should be in queue
?match_receive({done, {write, 0}, W0}),
?match_receive({'EXIT', W0, normal}),
TL ! continue, % Should stay in queue W1
?match(timeout, receive A -> A after 1000 -> timeout end),
Up ! continue, % Should stay in queue (TL got higher tid)
?match(timeout, receive A -> A after 1000 -> timeout end),
LQ2 = mnesia_locker:get_lock_queue(),
?match({2, _}, {length(LQ2), LQ2}),
W1 ! continue,
?match_receive({done, {write, 1}, W1}),
get_exit(W1),
get_exit(TL),
?match_receive({done, {tablock,Tab}, TL}),
get_exit(Up),
?match_receive({done, {update, 0}, Up}),
ok.
get_exit(Pid) ->
receive
{'EXIT', Pid, normal} ->
ok
after 10000 ->
?error("Timeout EXIT ~p~n", [Pid])
end.
foldl(doc) ->
[""];
foldl(suite) ->
[];
foldl(Config) when is_list(Config) ->
Nodes = [_,N2] = ?acquire_nodes(2, Config),
Tab1 = foldl_local,
Tab2 = foldl_remote,
Tab3 = foldl_ordered,
Tab11 = foldr_local,
Tab21 = foldr_remote,
Tab31 = foldr_ordered,
?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])),
?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}, {type, bag}])),
?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes},
{type, ordered_set}])),
?match({atomic, ok}, mnesia:create_table(Tab11, [{ram_copies, Nodes}])),
?match({atomic, ok}, mnesia:create_table(Tab21, [{ram_copies, [N2]}, {type, bag}])),
?match({atomic, ok}, mnesia:create_table(Tab31, [{ram_copies, Nodes},
{type, ordered_set}])),
Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 10)],
Tab2Els = [{Tab2, 1, 2} | [{Tab2, N, N} || N <- lists:seq(1, 10)]],
Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 10)],
Tab11Els = [{Tab11, N, N} || N <- lists:seq(1, 10)],
Tab21Els = [{Tab21, 1, 2} | [{Tab21, N, N} || N <- lists:seq(1, 10)]],
Tab31Els = [{Tab31, N, N} || N <- lists:seq(1, 10)],
[mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab1Els],
[mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab2Els],
[mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab3Els],
[mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab11Els],
[mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab21Els],
[mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab31Els],
Get = fun(E, A) -> [E | A] end,
%% Before
AddB = fun(Tab, Func) ->
mnesia:write({Tab, 0, 0}),
mnesia:write({Tab, 1, 0}),
mnesia:write({Tab, 11, 0}),
mnesia:Func(Get, [], Tab)
end,
AddT1 = [{Tab1, 0, 0}, {Tab1, 1, 0}] ++ tl(Tab1Els) ++ [{Tab1, 11, 0}],
AddT2 = lists:sort([{Tab2, 0, 0}, {Tab2, 1, 0}] ++ Tab2Els ++ [{Tab2, 11, 0}]),
AddT3 = [{Tab3, 0, 0}, {Tab3, 1, 0}] ++ tl(Tab3Els) ++ [{Tab3, 11, 0}],
AddT11 = [{Tab11, 0, 0}, {Tab11, 1, 0}] ++ tl(Tab11Els) ++ [{Tab11, 11, 0}],
AddT21 = lists:sort([{Tab21, 0, 0}, {Tab21, 1, 0}] ++ Tab21Els ++ [{Tab21, 11, 0}]),
AddT31 = [{Tab31, 0, 0}, {Tab31, 1, 0}] ++ tl(Tab31Els) ++ [{Tab31, 11, 0}],
?match({atomic, AddT1}, sort_res(mnesia:transaction(AddB, [Tab1, foldl]))),
?match({atomic, AddT2}, sort_res(mnesia:transaction(AddB, [Tab2, foldl]))),
?match({atomic, AddT3}, rev_res(mnesia:transaction(AddB, [Tab3, foldl]))),
?match({atomic, AddT11}, sort_res(mnesia:transaction(AddB, [Tab11, foldr]))),
?match({atomic, AddT21}, sort_res(mnesia:transaction(AddB, [Tab21, foldr]))),
?match({atomic, AddT31}, mnesia:transaction(AddB, [Tab31, foldr])),
?match({atomic, ok}, mnesia:create_table(copy, [{ram_copies, [N2]},
{record_name, Tab1}])),
CopyRec = fun(NewRec, Acc) ->
%% OTP-5495
W = fun() -> mnesia:write(copy, NewRec, write), [NewRec| Acc] end,
{atomic,Res} = sort_res(mnesia:transaction(W)),
Res
end,
Copy = fun() ->
AddT1 = mnesia:foldl(CopyRec, [], Tab1),
AddT1 = sort_res(mnesia:foldl(Get, [], copy))
end,
?match({atomic, AddT1}, sort_res(mnesia:transaction(Copy))),
Del = fun(E, A) -> mnesia:delete_object(E), [E|A] end,
DelD = fun(Tab) ->
mnesia:write({Tab, 12, 12}),
mnesia:delete({Tab, 0}),
mnesia:foldr(Del, [], Tab),
mnesia:foldl(Get, [], Tab)
end,
?match({atomic, []}, sort_res(mnesia:transaction(DelD, [Tab1]))),
?match({atomic, []}, sort_res(mnesia:transaction(DelD, [Tab2]))),
?match({atomic, []}, rev_res(mnesia:transaction(DelD, [Tab3]))),
ListWrite = fun(Tab) -> %% OTP-3893
mnesia:write({Tab, [12], 12}),
mnesia:foldr(Get, [], Tab)
end,
?match({atomic, [{Tab1, [12], 12}]}, sort_res(mnesia:transaction(ListWrite, [Tab1]))),
?match({atomic, [{Tab2, [12], 12}]}, sort_res(mnesia:transaction(ListWrite, [Tab2]))),
?match({atomic, [{Tab3, [12], 12}]}, rev_res(mnesia:transaction(ListWrite, [Tab3]))),
?verify_mnesia(Nodes, []).
sort_res({atomic, List}) when is_list(List) ->
{atomic, lists:sort(List)};
sort_res(Else) when is_list(Else) ->
lists:sort(Else);
sort_res(Else) ->
Else.
rev_res({atomic, List}) ->
{atomic, lists:reverse(List)};
rev_res(Else) ->
Else.
first_next(doc) -> [""];
first_next(suite) -> [];
first_next(Config) when is_list(Config) ->
Nodes = [_,N2] = ?acquire_nodes(2, Config),
Tab1 = local,
Tab2 = remote,
Tab3 = ordered,
Tab4 = bag,
Tabs = [Tab1,Tab2,Tab3,Tab4],
?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])),
?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}])),
?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes},
{type, ordered_set}])),
?match({atomic, ok}, mnesia:create_table(Tab4, [{ram_copies, Nodes},
{type, bag}])),
%% Some Helpers
Trans = fun(Fun) -> mnesia:transaction(Fun) end,
Continue = fun(first) -> next;
(last) -> prev
end,
LoopHelp = fun('$end_of_table',_,_,_Fun) -> [];
(Key,Tab,Op,Fun) ->
Next = mnesia:Op(Tab,Key),
[Next |Fun(Next,Tab,Op,Fun)]
end,
Loop = fun(Tab,Start) ->
First = mnesia:Start(Tab),
Res = [First|LoopHelp(First,Tab,Continue(Start),LoopHelp)],
case mnesia:table_info(Tab, type) of
ordered_set when Start == first -> Res;
ordered_set ->
{L1,L2} = lists:split(length(Res)-1,Res),
lists:reverse(L1) ++ L2;
_ -> lists:sort(Res)
end
end,
%% Verify empty tables
[?match({atomic, ['$end_of_table']},
Trans(fun() -> Loop(Tab,first) end))
|| Tab <- Tabs],
[?match({atomic, ['$end_of_table']},
Trans(fun() -> Loop(Tab,last) end))
|| Tab <- Tabs],
%% Verify that trans write is visible inside trans
[?match({atomic, [0,10,'$end_of_table']},
Trans(fun() ->
mnesia:write({Tab,0,0}),
mnesia:write({Tab,10,10}),
Loop(Tab,first) end))
|| Tab <- Tabs],
[?match({atomic, ['$end_of_table']},
Trans(fun() ->
mnesia:delete({Tab,0}),
mnesia:delete({Tab,10}),
Loop(Tab,first) end))
|| Tab <- Tabs],
[?match({atomic, [0,10,'$end_of_table']},
Trans(fun() ->
mnesia:write({Tab,0,0}),
mnesia:write({Tab,10,10}),
Loop(Tab,last) end))
|| Tab <- Tabs],
[?match({atomic, ['$end_of_table']},
Trans(fun() ->
mnesia:delete({Tab,0}),
mnesia:delete({Tab,10}),
Loop(Tab,last) end))
|| Tab <- Tabs],
Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 5)],
Tab2Els = [{Tab2, N, N} || N <- lists:seq(1, 5)],
Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 5)],
Tab4Els = [{Tab4, 1, 2} | [{Tab4, N, N} || N <- lists:seq(1, 5)]],
[mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab1Els],
[mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab2Els],
[mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab3Els],
[mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab4Els],
Keys = lists:sort(mnesia:dirty_all_keys(Tab1)),
R1 = Keys++ ['$end_of_table'],
[?match({atomic, R1}, Trans(fun() -> Loop(Tab,first) end))
|| Tab <- Tabs],
[?match({atomic, R1}, Trans(fun() -> Loop(Tab,last) end))
|| Tab <- Tabs],
R2 = R1 -- [3],
[?match({atomic, R2}, Trans(fun() -> mnesia:delete({Tab,3}),Loop(Tab,first) end))
|| Tab <- Tabs],
[?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,3,3}),Loop(Tab,first) end))
|| Tab <- Tabs],
[?match({atomic, R2}, Trans(fun() -> mnesia:delete({Tab,3}),Loop(Tab,last) end))
|| Tab <- Tabs],
[?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,3,3}),Loop(Tab,last) end))
|| Tab <- Tabs],
[?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,4,19}),Loop(Tab,first) end))
|| Tab <- Tabs],
[?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,4,4}),Loop(Tab,last) end))
|| Tab <- Tabs],
?verify_mnesia(Nodes, []).
snmp_shadows(doc) -> [""];
snmp_shadows(suite) -> [];
snmp_shadows(Config) when is_list(Config) ->
Nodes = ?acquire_nodes(1, Config),
Tab = snmp_shadows,
io:format("With fixstring~n", []),
?match({atomic, ok}, mnesia:create_table(Tab,[{snmp,[{key,{fix_string,integer}}]}])),
snmp_shadows_test(Tab),
?match({atomic, ok}, mnesia:delete_table(Tab)),
io:format("Without fixstring~n", []),
?match({atomic, ok}, mnesia:create_table(Tab,[{snmp,[{key,{string,integer}}]}])),
snmp_shadows_test(Tab),
?verify_mnesia(Nodes, []).
snmp_shadows_test(Tab) ->
[mnesia:dirty_write({Tab, {"string", N}, {N, init}}) || N <- lists:seq(2,8,2)],
CheckOrder = fun(A={_,_,{_,_,State}}, Prev) ->
?match({true, A, Prev}, {Prev < A, A, Prev}),
{State,A}
end,
R1 = mnesia:sync_dirty(fun() -> loop_snmp(Tab, []) end),
lists:mapfoldl(CheckOrder, {[],foo,foo}, R1),
R2 = mnesia:transaction(fun() -> loop_snmp(Tab, []) end),
?match({atomic, R1}, R2),
Shadow = fun() ->
ok = mnesia:write({Tab, {"string",1}, {1,update}}),
ok = mnesia:write({Tab, {"string",4}, {4,update}}),
ok = mnesia:write({Tab, {"string",6}, {6,update}}),
ok = mnesia:delete({Tab, {"string",6}}),
ok = mnesia:write({Tab, {"string",9}, {9,update}}),
ok = mnesia:write({Tab, {"string",3}, {3,update}}),
ok = mnesia:write({Tab, {"string",5}, {5,update}}),
[Row5] = mnesia:read({Tab, {"string",5}}),
ok = mnesia:delete_object(Row5),
loop_snmp(Tab, [])
end,
R3 = mnesia:sync_dirty(Shadow),
{L3,_} = lists:mapfoldl(CheckOrder, {[],foo,foo}, R3),
?match([{1,update},{2,init},{3,update},{4,update},{8,init},{9,update}], L3),
?match({atomic, ok}, mnesia:clear_table(Tab)),
[mnesia:dirty_write({Tab, {"string", N}, {N, init}}) || N <- lists:seq(2,8,2)],
{atomic, R3} = mnesia:transaction(Shadow),
{L4,_} = lists:mapfoldl(CheckOrder, {[],foo,foo}, R3),
?match([{1,update},{2,init},{3,update},{4,update},{8,init},{9,update}], L4),
ok.
loop_snmp(Tab,Prev) ->
case mnesia:snmp_get_next_index(Tab,Prev) of
{ok, SKey} ->
{{ok,Row},_} = {mnesia:snmp_get_row(Tab, SKey),{?LINE,Prev,SKey}},
{{ok,MKey},_} = {mnesia:snmp_get_mnesia_key(Tab,SKey),{?LINE,Prev,SKey}},
?match({[Row],Row,SKey,MKey}, {mnesia:read({Tab,MKey}),Row,SKey,MKey}),
[{SKey, MKey, Row} | loop_snmp(Tab, SKey)];
endOfTable ->
[]
end.