%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 1997-2026. 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_atomicity_test).
-author('hakan@erix.ericsson.se').
-author('rossi@erix.ericsson.se').
-include("mnesia_test_lib.hrl").

-compile([{nowarn_possibly_unsafe_function, {erlang, list_to_atom, 1}}]).

-export([init_per_testcase/2, end_per_testcase/2,
         init_per_group/2, end_per_group/2,
         all/0, groups/0]).
-export([explicit_abort_in_middle_of_trans/1,
         runtime_error_in_middle_of_trans/1,
         mnesia_down_during_infinite_trans/1,
         kill_self_in_middle_of_trans/1, throw_in_middle_of_trans/1,
         fixtable_released_when_coordinator_dies/1,
         lock_waiter_sw_r/1, lock_waiter_sw_rt/1, lock_waiter_sw_wt/1,
         lock_waiter_wr_r/1, lock_waiter_srw_r/1, lock_waiter_sw_sw/1,
         lock_waiter_sw_w/1, lock_waiter_sw_wr/1, lock_waiter_sw_srw/1,
         lock_waiter_wr_wt/1, lock_waiter_srw_wt/1,
         lock_waiter_wr_sw/1, lock_waiter_srw_sw/1, lock_waiter_wr_w/1,
         lock_waiter_srw_w/1, lock_waiter_r_sw/1, lock_waiter_r_w/1,
         lock_waiter_r_wt/1, lock_waiter_rt_sw/1, lock_waiter_rt_w/1,
         lock_waiter_rt_wt/1, lock_waiter_wr_wr/1,
         lock_waiter_srw_srw/1, lock_waiter_wt_r/1, lock_waiter_wt_w/1,
         lock_waiter_wt_rt/1, lock_waiter_wt_wt/1, lock_waiter_wt_wr/1,
         lock_waiter_wt_srw/1, lock_waiter_wt_sw/1, lock_waiter_w_wr/1,
         lock_waiter_w_srw/1, lock_waiter_w_sw/1, lock_waiter_w_r/1,
         lock_waiter_w_w/1, lock_waiter_w_rt/1, lock_waiter_w_wt/1,
         restart_r_one/1, restart_w_one/1, restart_rt_one/1,
         restart_wt_one/1, restart_wr_one/1, restart_sw_one/1,
         restart_r_two/1, restart_w_two/1, restart_rt_two/1,
         restart_wt_two/1, restart_wr_two/1, restart_sw_two/1
        ]
       ).

-export([perform_restarted_transaction/1, sync_tid_release/0]).

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() ->
    [explicit_abort_in_middle_of_trans,
     runtime_error_in_middle_of_trans,
     kill_self_in_middle_of_trans, throw_in_middle_of_trans,
     fixtable_released_when_coordinator_dies,
     {group, mnesia_down_in_middle_of_trans}].

groups() ->
    [{mnesia_down_in_middle_of_trans, [],
      [mnesia_down_during_infinite_trans,
       {group, lock_waiter}, {group, restart_check}]},
     {lock_waiter, [],
      [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]},
     {restart_check, [],
      [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]}].

init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
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 transaction
    ?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 interrupted 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 transaction
    ?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 interrupted 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 transaction
    ?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 interrupted 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, []).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
fixtable_released_when_coordinator_dies(doc) ->
    ["A table fixed by a transaction is unfixed when the coordinator dies"];
fixtable_released_when_coordinator_dies(suite) -> [];
fixtable_released_when_coordinator_dies(Config) when is_list(Config) ->
    [Node1] = Nodes = ?acquire_nodes(1, Config),
    Ram = fixtable_released_ram,
    Disc = fixtable_released_disc,

    ?match({atomic, ok}, mnesia:create_table([{name, Ram}, {type, set},
                                              {ram_copies, [Node1]}])),
    ?match({atomic, ok}, mnesia:create_table([{name, Disc}, {type, set},
                                              {disc_only_copies, [Node1]}])),

    check_fixtable_release(Ram),
    check_fixtable_release(Disc),

    ?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 transaction
    ?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 interrupted 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_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_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("Queues ~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,
    receive {'EXIT', B, _} -> ok
    after 3000 -> ?error("Timeout~n", []) end,
    receive {'EXIT', A, Exp1} -> ?match({atomic, ExpectedCounter}, Exp1)
    after 3000 -> ?error("Timeout~n", []) end,

    %% 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) ->
    Count = erlang:unique_integer([monotonic,positive]),
    list_to_atom(lists:concat([Prefix , '_', Count])).

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_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
    Read = 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 = fun(Loop) ->
		   wait(300),
		   sys:get_status(mnesia_monitor),
		   case lists:member(Read, mnesia_lib:val({current, db_nodes})) of
		       true -> Loop(Loop);
		       false -> ok
		   end
	   end,
    Wait(Wait),
    A ! go_ahead,

    %% the sticky write doesn't 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 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 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]),
	    Read;
	false ->
	    ?error("Fault while killing Mnesia: ~p~n", [Read]),
	    mnesia_test_lib:kill_mnesia(Nodes),
	    Read
    end.

sync_tid_release() ->
    sys:get_status(whereis(mnesia_tm)),
    sys:get_status(whereis(mnesia_locker)),
    ok.

check_fixtable_release(Tab) ->
    ?match(ok, mnesia:dirty_write({Tab, 1, a})),

    %% a transaction that ends by itself releases its fixes
    ?match({atomic, ok},
           mnesia:transaction(fun() -> mnesia:first(Tab), ok end)),
    ?match({Tab, false}, {Tab, wait_until_fixed(Tab, false)}),

    %% and so must a coordinator that is killed while holding one
    Coord = spawn(fun() ->
                          mnesia:transaction(
                            fun() ->
                                    mnesia:first(Tab),
                                    timer:sleep(infinity)
                            end)
                  end),
    ?match({Tab, true}, {Tab, wait_until_fixed(Tab, true)}),
    exit(Coord, kill),
    ?match({Tab, false}, {Tab, wait_until_fixed(Tab, false)}).

%% The fix is released asynchronously
wait_until_fixed(Tab, Expected) ->
    wait_until_fixed(Tab, Expected, 100).

wait_until_fixed(Tab, _Expected, 0) ->
    is_fixed(Tab, mnesia:table_info(Tab, storage_type));
wait_until_fixed(Tab, Expected, N) ->
    case is_fixed(Tab, mnesia:table_info(Tab, storage_type)) of
        Expected ->
            Expected;
        _ ->
            timer:sleep(50),
            wait_until_fixed(Tab, Expected, N - 1)
    end.

is_fixed(Tab, disc_only_copies) ->
    dets:info(Tab, safe_fixed_monotonic_time) =/= false;
is_fixed(Tab, _Storage) ->
    ets:info(Tab, safe_fixed_monotonic_time) =/= false.
