diff options
Diffstat (limited to 'lib/mnesia/test/mnesia_atomicity_test.erl')
-rw-r--r-- | lib/mnesia/test/mnesia_atomicity_test.erl | 839 |
1 files changed, 839 insertions, 0 deletions
diff --git a/lib/mnesia/test/mnesia_atomicity_test.erl b/lib/mnesia/test/mnesia_atomicity_test.erl new file mode 100644 index 0000000000..645c203a91 --- /dev/null +++ b/lib/mnesia/test/mnesia_atomicity_test.erl @@ -0,0 +1,839 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2010. 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_atomicity_test). +-author('[email protected]'). +-author('[email protected]'). +-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 atomicity of transactions", + "Verify that transactions are atomic, i.e. either all operations", + "in a transaction will be performed or none of them. It must be", + "assured that no partitially completed operations leaves any", + "effects in the database."]; +all(suite) -> + [ + explicit_abort_in_middle_of_trans, + runtime_error_in_middle_of_trans, + kill_self_in_middle_of_trans, + throw_in_middle_of_trans, + mnesia_down_in_middle_of_trans + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +explicit_abort_in_middle_of_trans(suite) -> []; +explicit_abort_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = explicit_abort_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + exit(abort_by_purpose) %does that stop the process A ??? + end, + ?match_receive({A, {aborted, abort_by_purpose}}), + + + %?match_receive({A, {'EXIT', Pid, normal}}), % A died and sends EXIT + + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +runtime_error_in_middle_of_trans(suite) -> []; +runtime_error_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = runtime_error_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + erlang:error(foo), % that should provoke a runtime error + mnesia:write(Rec1C) + end, + ?match_receive({A, {aborted, _Reason}}), + + %?match_receive({A, {'EXIT', Msg1}), % A died and sends EXIT + + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +kill_self_in_middle_of_trans(suite) -> []; +kill_self_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = kill_self_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + exit(self(), kill), % that should kill the process himself + % - poor guy ! + mnesia:write(Rec1C) + end, + %% + %% exit(.., kill) : the transaction can't trap this error - thus no + %% proper result can be send by the test server + + % ?match_receive({A, {aborted, Reason}}), + + ?match_receive({'EXIT', _Pid, killed}), % A is killed and sends EXIT + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +throw_in_middle_of_trans(suite) -> []; +throw_in_middle_of_trans(Config) when is_list(Config) -> + [Node1] = Nodes = ?acquire_nodes(1, Config), + Tab = throw_in_middle_of_trans, + + Rec1A = {Tab, 1, a}, + Rec1B = {Tab, 1, b}, + Rec1C = {Tab, 1, c}, + + ?match({atomic, ok}, mnesia:create_table([{name, Tab}, + {ram_copies, [Node1]}])), + %% Start a transaction on one node + {success, [A]} = ?start_activities([Node1]), + + %% store an object in the Tab - first tranaction + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1A) % returns ok when successful + end, + ?match_receive({A, ok}), + A ! end_trans, + ?match_receive({A, {atomic, end_trans}}), + + %% second transaction: store some new objects and abort before the + %% transaction is finished -> the new changes should be invisable + ?start_transactions([A]), + A ! fun() -> + mnesia:write(Rec1B), + throw(exit_transactian_by_a_throw), + mnesia:write(Rec1C) + end, + ?match_receive({A, {aborted, {throw, exit_transactian_by_a_throw}}}), + % A ! end_trans, % is A still alive ? + % ?match_receive({A, {atomic, end_trans}}), % {'EXIT', Pid, normal} + + %?match_receive({A, {'EXIT', Pid, normal}}), % A died and sends EXIT + + %% Start a second transactionprocess, after the first failed + {success, [B]} = ?start_activities([Node1]), + + %% check, whether the interupted transaction had no influence on the db + ?start_transactions([B]), + B ! fun() -> + ?match([Rec1A], mnesia:read({Tab, 1})), + ok + end, + ?match_receive({B, ok}), + B ! end_trans, + ?match_receive({B, {atomic, end_trans}}), + + ?verify_mnesia(Nodes, []). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_in_middle_of_trans(suite) -> + [ + mnesia_down_during_infinite_trans, + lock_waiter, + restart_check + ]. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mnesia_down_during_infinite_trans(suite) -> []; +mnesia_down_during_infinite_trans(Config) when is_list(Config) -> + [Node1, Node2] = ?acquire_nodes(2, Config), + Tab = mnesia_down_during_infinite_trans, + + ?match({atomic, ok}, + mnesia:create_table([{name, Tab}, {ram_copies, [Node1, Node2]}])), + %% Start a transaction on one node + {success, [A2, A1]} = ?start_activities([Node2, Node1]), + %% Start order of the transactions are important + %% We also needs to sync the tid counter + ?match({atomic, ok}, + mnesia:transaction(fun() -> mnesia:write({Tab, 1, test_ok}) end)), + mnesia_test_lib:start_sync_transactions([A2, A1]), + + %% Obtain a write lock and wait forever + RecA = {Tab, 1, test_not_ok}, + A1 ! fun() -> mnesia:write(RecA) end, + ?match_receive({A1, ok}), + + A1 ! fun() -> process_flag(trap_exit, true), timer:sleep(infinity) end, + ?match_receive(timeout), + + %% Try to get read lock, but gets queued + A2 ! fun() -> mnesia:read({Tab, 1}) end, + ?match_receive(timeout), + + %% Kill Mnesia on other node + mnesia_test_lib:kill_mnesia([Node1]), + + %% Second transaction gets the read lock + ?match_receive({A2, [{Tab, 1, test_ok}]}), + exit(A1, kill), % Needed since we trap exit + + ?verify_mnesia([Node2], [Node1]). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +lock_waiter(doc) -> + ["The purpose of this test case is to test the following situation:", + "process B locks an object, process A accesses that object as", + "well, but A has to wait for the lock to be released. Then", + "mnesia of B goes down. Question: will A get the lock ?", + "important: the transaction of A is the oldest one !!! (= a little tricky)", + "", + "several different access operations shall be tested", + "rt = read_lock_table, wt = write_lock_table, r = read,", + "sw = s_write, w = write, wr = wread"]; +lock_waiter(suite) -> + [ + lock_waiter_sw_r, + lock_waiter_sw_rt, + lock_waiter_sw_wt, + lock_waiter_wr_r, + lock_waiter_srw_r, + lock_waiter_sw_sw, + lock_waiter_sw_w, + lock_waiter_sw_wr, + lock_waiter_sw_srw, + lock_waiter_wr_wt, + lock_waiter_srw_wt, + lock_waiter_wr_sw, + lock_waiter_srw_sw, + lock_waiter_wr_w, + lock_waiter_srw_w, + lock_waiter_r_sw, + lock_waiter_r_w, + lock_waiter_r_wt, + lock_waiter_rt_sw, + lock_waiter_rt_w, + lock_waiter_rt_wt, + lock_waiter_wr_wr, + lock_waiter_srw_srw, + lock_waiter_wt_r, + lock_waiter_wt_w, + lock_waiter_wt_rt, + lock_waiter_wt_wt, + lock_waiter_wt_wr, + lock_waiter_wt_srw, + lock_waiter_wt_sw, + lock_waiter_w_wr, + lock_waiter_w_srw, + lock_waiter_w_sw, + lock_waiter_w_r, + lock_waiter_w_w, + lock_waiter_w_rt, + lock_waiter_w_wt + ]. + +lock_waiter_sw_r(suite) -> []; +lock_waiter_sw_r(Config) when is_list(Config) -> + start_lock_waiter(sw, r, Config). + +lock_waiter_sw_rt(suite) -> []; +lock_waiter_sw_rt(Config) when is_list(Config) -> + start_lock_waiter(sw, rt, Config). + +lock_waiter_sw_wt(suite) -> []; +lock_waiter_sw_wt(Config) when is_list(Config) -> + start_lock_waiter(sw, wt,Config). + +lock_waiter_wr_r(suite) -> []; +lock_waiter_wr_r(Config) when is_list(Config) -> + start_lock_waiter(wr, r, Config). + +lock_waiter_srw_r(suite) -> []; +lock_waiter_srw_r(Config) when is_list(Config) -> + start_lock_waiter(srw, r, Config). + +lock_waiter_sw_sw(suite) -> []; +lock_waiter_sw_sw(Config) when is_list(Config) -> + start_lock_waiter(sw, sw,Config). + +lock_waiter_srw_srw(suite) -> []; +lock_waiter_srw_srw(Config) when is_list(Config) -> + start_lock_waiter(srw, srw,Config). + +lock_waiter_wr_wr(suite) -> []; +lock_waiter_wr_wr(Config) when is_list(Config) -> + start_lock_waiter(wr, wr,Config). + +lock_waiter_sw_w(suite) -> []; +lock_waiter_sw_w(Config) when is_list(Config) -> + start_lock_waiter(sw, w,Config). + +lock_waiter_sw_wr(suite) -> []; +lock_waiter_sw_wr(Config) when is_list(Config) -> + start_lock_waiter(sw, wr,Config). + +lock_waiter_sw_srw(suite) -> []; +lock_waiter_sw_srw(Config) when is_list(Config) -> + start_lock_waiter(sw, srw,Config). + +lock_waiter_wr_wt(suite) -> []; +lock_waiter_wr_wt(Config) when is_list(Config) -> + start_lock_waiter(wr, wt,Config). + +lock_waiter_srw_wt(suite) -> []; +lock_waiter_srw_wt(Config) when is_list(Config) -> + start_lock_waiter(srw, wt,Config). + +lock_waiter_wr_sw(suite) -> []; +lock_waiter_wr_sw(Config) when is_list(Config) -> + start_lock_waiter(wr, sw,Config). + +lock_waiter_srw_sw(suite) -> []; +lock_waiter_srw_sw(Config) when is_list(Config) -> + start_lock_waiter(srw, sw,Config). + +lock_waiter_wr_w(suite) -> []; +lock_waiter_wr_w(Config) when is_list(Config) -> + start_lock_waiter(wr, w,Config). + +lock_waiter_srw_w(suite) -> []; +lock_waiter_srw_w(Config) when is_list(Config) -> + start_lock_waiter(srw, w,Config). + +lock_waiter_r_sw(suite) -> []; +lock_waiter_r_sw(Config) when is_list(Config) -> + start_lock_waiter(r, sw,Config). + +lock_waiter_r_w(suite) -> []; +lock_waiter_r_w(Config) when is_list(Config) -> + start_lock_waiter(r, w,Config). + +lock_waiter_r_wt(suite) -> []; +lock_waiter_r_wt(Config) when is_list(Config) -> + start_lock_waiter(r, wt,Config). + +lock_waiter_rt_sw(suite) -> []; +lock_waiter_rt_sw(Config) when is_list(Config) -> + start_lock_waiter(rt, sw,Config). + +lock_waiter_rt_w(suite) -> []; +lock_waiter_rt_w(Config) when is_list(Config) -> + start_lock_waiter(rt, w,Config). + +lock_waiter_rt_wt(suite) -> []; +lock_waiter_rt_wt(Config) when is_list(Config) -> + start_lock_waiter(rt, wt,Config). + +lock_waiter_wt_r(suite) -> []; +lock_waiter_wt_r(Config) when is_list(Config) -> + start_lock_waiter(wt, r,Config). + +lock_waiter_wt_w(suite) -> []; +lock_waiter_wt_w(Config) when is_list(Config) -> + start_lock_waiter(wt, w,Config). + +lock_waiter_wt_rt(suite) -> []; +lock_waiter_wt_rt(Config) when is_list(Config) -> + start_lock_waiter(wt, rt,Config). + +lock_waiter_wt_wt(suite) -> []; +lock_waiter_wt_wt(Config) when is_list(Config) -> + start_lock_waiter(wt, wt,Config). + +lock_waiter_wt_wr(suite) -> []; +lock_waiter_wt_wr(Config) when is_list(Config) -> + start_lock_waiter(wt, wr,Config). + +lock_waiter_wt_srw(suite) -> []; +lock_waiter_wt_srw(Config) when is_list(Config) -> + start_lock_waiter(wt, srw,Config). + +lock_waiter_wt_sw(suite) -> []; +lock_waiter_wt_sw(Config) when is_list(Config) -> + start_lock_waiter(wt, sw,Config). + +lock_waiter_w_wr(suite) -> []; +lock_waiter_w_wr(Config) when is_list(Config) -> + start_lock_waiter(w, wr, Config). + +lock_waiter_w_srw(suite) -> []; +lock_waiter_w_srw(Config) when is_list(Config) -> + start_lock_waiter(w, srw, Config). + +lock_waiter_w_sw(suite) -> []; +lock_waiter_w_sw(Config) when is_list(Config) -> + start_lock_waiter(w, sw, Config). + +lock_waiter_w_r(suite) -> []; +lock_waiter_w_r(Config) when is_list(Config) -> + start_lock_waiter(w, r, Config). + +lock_waiter_w_w(suite) -> []; +lock_waiter_w_w(Config) when is_list(Config) -> + start_lock_waiter(w, w, Config). + +lock_waiter_w_rt(suite) -> []; +lock_waiter_w_rt(Config) when is_list(Config) -> + start_lock_waiter(w, rt, Config). + +lock_waiter_w_wt(suite) -> []; +lock_waiter_w_wt(Config) when is_list(Config) -> + start_lock_waiter(w, wt, Config). + +start_lock_waiter(BlockOpA, BlockOpB, Config) -> + [N1, N2] = Nodes = ?acquire_nodes(2, Config), + + TabName = mk_tab_name(lock_waiter_), + ?match({atomic, ok}, mnesia:create_table(TabName, + [{ram_copies, [N1, N2]}])), + + %% initialize the table with object {1, c} - when there + %% is a read transaction, the read will find that value + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({TabName, 1, c}) end)), + rpc:call(N2, ?MODULE, sync_tid_release, []), + + Tester = self(), + Fun_A =fun() -> + NewCounter = incr_restart_counter(), + if + NewCounter == 1 -> + Tester ! go_ahead_test, + receive go_ahead -> ok end; + true -> ok + end, + lock_waiter_fun(BlockOpA, TabName, a), + NewCounter + end, + + %% it's not possible to just spawn the transaction, because + %% the result shall be evaluated + A = spawn_link(N1, ?MODULE, perform_restarted_transaction, [Fun_A]), + + ?match(ok, receive go_ahead_test -> ok after 10000 -> timeout end), + + mnesia_test_lib:sync_trans_tid_serial([N1, N2]), + + Fun_B = fun() -> + lock_waiter_fun(BlockOpB, TabName, b), + A ! go_ahead, + wait(infinity) + end, + + B = spawn_link(N2, mnesia, transaction, [Fun_B, 100]), + + io:format("waiting for A (~p on ~p) to be in the queue ~n", [A, [N1, N2]]), + wait_for_a(A, [N1, N2]), + + io:format("Queus ~p~n", + [[{N,rpc:call(N, mnesia, system_info, [lock_queue])} || N <- Nodes]]), + + KillNode = node(B), + io:format("A was in the queue, time to kill Mnesia on B's node (~p on ~p)~n", + [B, KillNode]), + + mnesia_test_lib:kill_mnesia([KillNode]), % kill mnesia of fun B + + %% Read Ops does not need to be restarted + ExpectedCounter = + if + BlockOpA == sw, BlockOpB == w -> 1; + BlockOpA == sw, BlockOpB == wt -> 1; + BlockOpA == sw, BlockOpB == wr -> 1; + BlockOpA == srw, BlockOpB == w -> 1; + BlockOpA == srw, BlockOpB == wt -> 1; + BlockOpA == srw, BlockOpB == wr -> 1; + BlockOpA == r, BlockOpB /= sw -> 1; + BlockOpA == rt, BlockOpB /= sw -> 1; + true -> 2 + end, + ?match_multi_receive([{'EXIT', A, {atomic, ExpectedCounter}}, + {'EXIT', B, killed}]), + + %% the expected result depends on the transaction of + %% fun A - when that doesn't change the object in the + %% table (e.g. it is a read) then the predefined + %% value {Tabname, 1, c} is expected to be the result here + ExpectedResult = + case BlockOpA of + w -> {TabName, 1, a}; + sw ->{TabName, 1, a}; + _all_other -> {TabName, 1, c} + end, + + ?match({atomic, [ExpectedResult]}, + mnesia:transaction(fun() -> mnesia:read({TabName, 1}) end, 100)), + ?verify_mnesia([N1], [N2]). + +mk_tab_name(Prefix) -> + {Mega, Sec, Micro} = erlang:now(), + list_to_atom(lists:concat([Prefix , Mega, '_', Sec, '_', Micro])). + +lock_waiter_fun(Op, TabName, Val) -> + case Op of + rt -> mnesia:read_lock_table(TabName); + wt -> mnesia:write_lock_table(TabName); + r -> mnesia:read({TabName, 1}); + w -> mnesia:write({TabName, 1, Val}); + wr -> mnesia:wread({TabName, 1}); + srw -> mnesia:read(TabName, 1, sticky_write); + sw -> mnesia:s_write({TabName, 1, Val}) + end. + +wait_for_a(Pid, Nodes) -> + wait_for_a(Pid, Nodes, 5). + +wait_for_a(_P, _N, 0) -> + ?error("Timeout while waiting for lock on a~n", []); + +wait_for_a(Pid, Nodes, Count) -> + %% io:format("WAIT_FOR_A ~p ON ~w ~n", [Pid, Nodes]), + List = [rpc:call(N, mnesia, system_info, [lock_queue]) || N <- Nodes], + Q = lists:append(List), + check_q(Pid, Q, Nodes, Count). + +check_q(Pid, [{{_Oid,_Tid}, _Op, Pid, _WFT} | _Tail], _N, _Count) -> + ok; +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_a(Pid, N, Count - 1). + +perform_restarted_transaction (Fun_Trans) -> + %% the result of the transaction shall be: + %% - undefined (if the transaction was never executed) + %% - Times ( number of times that the transaction has been executed) + + Result = mnesia:transaction(Fun_Trans, 100), + exit(Result). + +%% Returns new val +incr_restart_counter() -> + NewCount = + case get(count_restart_of_transaction) of + undefined -> 1; + OldCount -> OldCount + 1 + end, + put(count_restart_of_transaction, NewCount), + NewCount. + +wait(Mseconds) -> + receive + after Mseconds -> ok + end. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +restart_check (doc) -> + [ + "test case:'A' performs a transaction on a table which", + "is only replicated on node B. During that transaction", + "mnesia on node B is killed. The transaction of A should", + "be stopped, since there is no further replica", + "rt = read_lock_table, wt = write_lock_table, r = read,", + "sw = s_write, w = write, wr = wread,"]; +restart_check(suite) -> + [ + restart_r_one, + restart_w_one, + restart_rt_one, + restart_wt_one, + restart_wr_one, + restart_sw_one, + restart_r_two, + restart_w_two, + restart_rt_two, + restart_wt_two, + restart_wr_two, + restart_sw_two + ]. + +restart_r_one(suite) -> []; +restart_r_one(Config) when is_list(Config) -> + start_restart_check(r, one, Config). + +restart_w_one(suite) -> []; +restart_w_one(Config) when is_list(Config) -> + start_restart_check(w, one, Config). + +restart_rt_one(suite) -> []; +restart_rt_one(Config) when is_list(Config) -> + start_restart_check(rt, one, Config). + +restart_wt_one(suite) -> []; +restart_wt_one(Config) when is_list(Config) -> + start_restart_check(wt, one, Config). + +restart_wr_one(suite) -> []; +restart_wr_one(Config) when is_list(Config) -> + start_restart_check(wr, one, Config). + +restart_sw_one(suite) -> []; +restart_sw_one(Config) when is_list(Config) -> + start_restart_check(sw, one, Config). + +restart_r_two(suite) -> []; +restart_r_two(Config) when is_list(Config) -> + start_restart_check(r, two, Config). + +restart_w_two(suite) -> []; +restart_w_two(Config) when is_list(Config) -> + start_restart_check(w, two, Config). + +restart_rt_two(suite) -> []; +restart_rt_two(Config) when is_list(Config) -> + start_restart_check(rt, two, Config). + +restart_wt_two(suite) -> []; +restart_wt_two(Config) when is_list(Config) -> + start_restart_check(wt, two, Config). + +restart_wr_two(suite) -> []; +restart_wr_two(Config) when is_list(Config) -> + start_restart_check(wr, two, Config). + +restart_sw_two(suite) -> []; +restart_sw_two(Config) when is_list(Config) -> + start_restart_check(sw, two, Config). + +start_restart_check(RestartOp, ReplicaNeed, Config) -> + [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config), + + {TabName, _TabNodes} = create_restart_table(ReplicaNeed, Nodes), + + %% initialize the table with object {1, c} - when there + %% is a read transaction, the read will find that value + ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({TabName, 1, c}) end)), + + %% Really sync tid_release + rpc:multicall([N2,N3], ?MODULE, sync_tid_release, []), + Coord = self(), + + Fun_A = fun() -> + NewCounter = incr_restart_counter(), + case NewCounter of + 1 -> + mnesia:write({TabName, 1, d}), + %% send a message to the test proc + Coord ! {self(),fun_a_is_blocked}, + receive go_ahead -> ok end; + _ -> + %% the fun will NOT be blocked here + restart_fun_A(RestartOp, TabName) + end, + NewCounter + end, + + A = spawn_link(N1, ?MODULE, perform_restarted_transaction, [Fun_A]), + ?match_receive({A,fun_a_is_blocked}), + + %% mnesia shall be killed at that node, where A is reading + %% the information from + kill_where_to_read(TabName, N1, [N2, N3]), + + %% wait some time to let mnesia go down and spread those news around + %% fun A shall be able to finish its job before being restarted + wait(500), + A ! go_ahead, + + %% the sticky write doesnt work on remote nodes !!! + ExpectedMsg = + case RestartOp of + sw when ReplicaNeed == two -> + {'EXIT',A,{aborted, {not_local, TabName}}}; + _all_other -> + case ReplicaNeed of + one -> + {'EXIT',A,{aborted, {no_exists, TabName}}}; + two -> + {'EXIT',A,{atomic, 2}} + end + end, + + ?match_receive(ExpectedMsg), + + %% now mnesia has to be started again on the node KillNode + %% because the next test suite will need it + ?match([], mnesia_test_lib:start_mnesia(Nodes, [TabName])), + + + %% the expected result depends on the transaction of + %% fun A - when that doesnt change the object in the + %% table (e.g. it is a read) then the predefined + %% value {Tabname, 1, c} is expected to be the result here + + ExpectedResult = + case ReplicaNeed of + one -> + []; + two -> + case RestartOp of + w -> [{TabName, 1, a}]; + _ ->[ {TabName, 1, c}] + end + end, + + ?match({atomic, ExpectedResult}, + mnesia:transaction(fun() -> mnesia:read({TabName, 1}) end,100)), + ?verify_mnesia(Nodes, []). + +create_restart_table(ReplicaNeed, [_N1, N2, N3]) -> + TabNodes = + case ReplicaNeed of + one -> [N2]; + two -> [N2, N3] + end, + TabName = mk_tab_name(restart_check_), + ?match({atomic, ok}, mnesia:create_table(TabName, [{ram_copies, TabNodes}])), + {TabName, TabNodes}. + +restart_fun_A(Op, TabName) -> + case Op of + rt -> mnesia:read_lock_table(TabName); + wt -> mnesia:write_lock_table(TabName); + r -> mnesia:read( {TabName, 1}); + w -> mnesia:write({TabName, 1, a}); + wr -> mnesia:wread({TabName, 1}); + sw -> mnesia:s_write({TabName, 1, a}) + end. + +kill_where_to_read(TabName, N1, Nodes) -> + Read = rpc:call(N1,mnesia,table_info, [TabName, where_to_read]), + case lists:member(Read, Nodes) of + true -> + mnesia_test_lib:kill_mnesia([Read]); + false -> + ?error("Fault while killing Mnesia: ~p~n", [Read]), + mnesia_test_lib:kill_mnesia(Nodes) + end. + +sync_tid_release() -> + sys:get_status(whereis(mnesia_tm)), + sys:get_status(whereis(mnesia_locker)), + ok. + |