%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-2018. 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('hakan@erix.ericsson.se'). -export([init_per_testcase/2, end_per_testcase/2, init_per_group/2, end_per_group/2, all/0, groups/0]). -export([no_conflict/1, simple_queue_conflict/1, advanced_queue_conflict/1, simple_deadlock_conflict/1, advanced_deadlock_conflict/1, schema_deadlock/1, lock_burst/1, nasty/1, basic_sticky_functionality/1, unbound1/1, unbound2/1, create_table/1, delete_table/1, move_table_copy/1, add_table_index/1, del_table_index/1, transform_table/1, snmp_open_table/1, snmp_close_table/1, change_table_copy_type/1, change_table_access/1, add_table_copy/1, del_table_copy/1, dump_tables/1, del_table_copy_1/1, del_table_copy_2/1, del_table_copy_3/1, add_table_copy_1/1, add_table_copy_2/1, add_table_copy_3/1, add_table_copy_4/1, move_table_copy_1/1, move_table_copy_2/1, move_table_copy_3/1, move_table_copy_4/1, dirty_updates_visible_direct/1, dirty_reads_regardless_of_trans/1, trans_update_invisibible_outside_trans/1, trans_update_visible_inside_trans/1, write_shadows/1, delete_shadows/1, write_delete_shadows_bag/1, write_delete_shadows_bag2/1, shadow_search/1, snmp_shadows/1, rr_kill_copy/1, foldl/1, first_next/1]). -export([do_fun/4, burst_counter/3, burst_incr/2, get_held/0, get_info/1, get_sticky/0, op/4, update_own/3, update_shared/3]). -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. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 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}, RecA2 = {Tab, a, 2}, 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(RecA2)), ?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)), ?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.