%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2009. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% %% -module(mnesia_isolation_test). -author('hakan@erix.ericsson.se'). -compile([export_all]). -include("mnesia_test_lib.hrl"). init_per_testcase(Func, Conf) -> mnesia_test_lib:init_per_testcase(Func, Conf). fin_per_testcase(Func, Conf) -> mnesia_test_lib:fin_per_testcase(Func, Conf). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% all(doc) -> ["Verify the isolation property.", "Operations of concurrent transactions must yield results which", "are indistinguishable from the results which would be obtained by", "forcing each transaction to be serially executed to completion in", "some order. This means that repeated reads of the same records", "within any committed transaction must have returned identical", "data when run concurrently with any mix of arbitary transactions.", "Updates in one transaction must not be visible in any other", "transaction before the transaction has been committed."]; all(suite) -> [ locking, visibility ]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% locking(doc) -> ["Verify locking semantics for various configurations", " NoLock = lock_funs(no_lock, any_granularity)", " SharedLock = lock_funs(shared_lock, any_granularity)", " ExclusiveLock = lock_funs(exclusive_lock, any_granularity)", " AnyLock = lock_funs(any_lock, any_granularity)"]; locking(suite) -> [no_conflict, simple_queue_conflict, advanced_queue_conflict, simple_deadlock_conflict, advanced_deadlock_conflict, lock_burst, sticky_locks, unbound_locking, admin_conflict, %% removed_resources, nasty ]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 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, 0) -> 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, N) -> rpc:multicall(Nodes, sys, get_status, [mnesia_locker]), List = [rpc:call(Node, mnesia, system_info, [lock_queue]) || Node <- Nodes], Q = lists:append(List), check_q(Pid, Q, Nodes, N). check_q(Pid, [{_Oid, _Op, Pid, _Tid, _WFT} | _Tail], _N, _Count) -> ok; check_q(Pid, [_ | Tail], N, Count) -> check_q(Pid, Tail, N, Count); check_q(Pid, [], N, Count) -> timer:sleep(500), wait_for_lock(Pid, N, Count - 1). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 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. 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. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% sticky_locks(doc) -> ["Simple Tests of sticky locks"]; sticky_locks(suite) -> [ basic_sticky_functionality %% Needs to be expandand a little bit further ]. 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. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% unbound_locking(suite) -> [unbound1, unbound2]; unbound_locking(doc) -> ["Check that mnesia handles unbound key variables, GPRS bug." "Ticket id: OTP-3342"]. 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_receive({A, {atomic, ok}}), ?match_receive({B, {atomic, [{ul,{key,{17,42}},val}]}}), ok. receiver() -> receive {_Pid, begin_trans} -> receiver(); Else -> Else after 10000 -> timeout end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% admin_conflict(doc) -> ["Provoke lock conflicts with schema transactions and checkpoints."]; admin_conflict(suite) -> [ 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, extra_admin_tests ]. 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). extra_admin_tests(suite) -> [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]. 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:now(),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], []). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% visibility(doc) -> ["Verify the visibility semantics for various configurations"]; visibility(suite) -> [ dirty_updates_visible_direct, dirty_reads_regardless_of_trans, trans_update_invisibible_outside_trans, trans_update_visible_inside_trans, write_shadows, delete_shadows, %% delete_shadows2, write_delete_shadows_bag, write_delete_shadows_bag2, iteration, shadow_search, snmp_shadows ]. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 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([RecA2], mnesia:match_object(PatA2)), %% delete shadow old but not new write - 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([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], []). removed_resources(suite) -> [rr_kill_copy]; removed_resources(doc) -> ["Verify that the locking behave when resources are removed"]. 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. iteration(doc) -> ["Verify that the updates before/during iteration are visable " "and that the order is preserved for ordered_set tables"]; iteration(suite) -> [foldl,first_next]. 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.