diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl index def9d411b1e6..f95c752854e6 100644 --- a/lib/mnesia/src/mnesia_controller.erl +++ b/lib/mnesia/src/mnesia_controller.erl @@ -1487,6 +1487,33 @@ orphan_tables([Tab | Tabs], Node, Ns, Local, Remote) -> false when Active == [], DiscCopyHolders == [], RamCopyHoldersOnDiscNodes == [] -> %% Special case when all replicas resides on disc less nodes orphan_tables(Tabs, Node, Ns, [Tab | Local], Remote); + false when Active == [], DiscCopyHolders == [], + RamCopyHolders =/= [], not (LocalContent == true) -> + case RamCopyHolders -- Ns of + [] -> + %% We're last up and the other nodes have not + %% loaded the table. Lets load it if we are + %% the smallest node. + case lists:min(RamCopyHolders) of + Min when Min == node() -> + %% This is safe for ram copies because if all nodes are + %% waiting that means the table is empty on all nodes. + %% here we just need to elect a bootstrap node to break + %% the waiting loop. + case mnesia_recover:get_master_nodes(Tab) of + [] -> + L = [Tab | Local], + orphan_tables(Tabs, Node, Ns, L, Remote); + Masters -> + R = [{Tab, Masters} | Remote], + orphan_tables(Tabs, Node, Ns, Local, R) + end; + _ -> + orphan_tables(Tabs, Node, Ns, Local, Remote) + end; + _ -> + orphan_tables(Tabs, Node, Ns, Local, Remote) + end; _ when LocalContent == true -> orphan_tables(Tabs, Node, Ns, [Tab | Local], Remote); _ -> @@ -1595,21 +1622,11 @@ update_whereabouts(Tab, Node, State) -> end. initial_safe_loads() -> - case val({schema, storage_type}) of - ram_copies -> - Downs = [], - Tabs = val({schema, local_tables}) -- [schema], - LastC = fun(T) -> last_consistent_replica(T, Downs) end, - lists:zf(LastC, Tabs); - - disc_copies -> - Downs = mnesia_recover:get_mnesia_downs(), - dbg_out("mnesia_downs = ~p~n", [Downs]), - - Tabs = val({schema, local_tables}) -- [schema], - LastC = fun(T) -> last_consistent_replica(T, Downs) end, - lists:zf(LastC, Tabs) - end. + Downs = mnesia_recover:get_mnesia_downs(), + dbg_out("mnesia_downs = ~p~n", [Downs]), + Tabs = val({schema, local_tables}) -- [schema], + LastC = fun(T) -> last_consistent_replica(T, Downs) end, + lists:zf(LastC, Tabs). last_consistent_replica(Tab, Downs) -> case ?catch_val({Tab, cstruct}) of @@ -1656,12 +1673,14 @@ last_consistent_replica(Cs, Tab, Downs) -> %% Wait for remote master copy false; Storage == ram_copies -> - if - Disc == [], DiscOnly == [], Ext == [] -> - %% Nobody has copy on disc + if + Disc == [], DiscOnly == [], Ext == [], BetterCopies0 == [] -> + %% RAM-only table and no non-down remote copy holder. + %% Safe to load locally. {true, {Tab, ram_only}}; true -> - %% Some other node has copy on disc + %% Either a disc-resident copy exists or a non-down + %% remote copy holder may have a better copy. false end; AccessMode == read_only -> diff --git a/lib/mnesia/test/mnesia_durability_test.erl b/lib/mnesia/test/mnesia_durability_test.erl index e03225388f6f..ada2bfe0a83a 100644 --- a/lib/mnesia/test/mnesia_durability_test.erl +++ b/lib/mnesia/test/mnesia_durability_test.erl @@ -221,10 +221,10 @@ load_local_contents_directly(Config) when is_list(Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% load_directly_when_all_are_ram_copiesA(doc) -> - ["Tables that are RAM copies only shall also be loaded directly. ", + ["Tables that are RAM copies only shall NOT be loaded directly. ", "1. N1 and N2 has RAM copies of a table, stop N1 before N2. ", - "2. When N1 starts he shall have access to the table ", - " without having to start N2" ]; + "2. When N1 starts he shall NOT have access to the table ", + " without loading the better copy from N2" ]; load_directly_when_all_are_ram_copiesA(suite) -> []; load_directly_when_all_are_ram_copiesA(Config) when is_list(Config) -> [N1, N2] = Nodes = ?acquire_nodes(2, Config), @@ -251,20 +251,21 @@ load_directly_when_all_are_ram_copiesA(Config) when is_list(Config) -> rpc:call(N2,mnesia,transaction,[Read_one]) ), %%Stop Mnesia on N2 ?match([], mnesia_test_lib:kill_mnesia([N2])), - %%Restart Mnesia on N1 verify that we can access test_rec from - %%N1 without starting Mnesia on N2. + %%Restart Mnesia on N1 verify that we CANNOT access test_rec from + %%N1 without starting Mnesia on N2 because N2 is not in down_nodes of N1 node. ?match(ok, rpc:call(N1, mnesia, start, [])), - ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), - ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one])), - ?match({atomic,ok}, rpc:call(N1,mnesia,transaction,[Write_one,[33]])), - ?match({atomic,[#test_rec{key=2,val=33}]}, + ?match({timeout, [test_rec]}, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])), + ?match({aborted, {no_exists, test_rec}}, rpc:call(N1,mnesia,transaction,[Read_one])), + ?match({aborted, {no_exists, test_rec}}, rpc:call(N1,mnesia,transaction,[Write_one,[33]])), + ?match({aborted,{no_exists,test_rec}}, rpc:call(N1,mnesia,transaction,[Read_one])), %%Restart Mnesia on N2 and verify the contents there. ?match([], mnesia_test_lib:start_mnesia([N2], [test_rec])), - ?match( {atomic,[#test_rec{key=2,val=33}]}, + ?match({atomic, ok}, rpc:call(N1,mnesia,transaction,[Write_one,[44]])), + ?match( {atomic,[#test_rec{key=2,val=44}]}, rpc:call(N2, mnesia, transaction, [Read_one] ) ), %%Check that the start of Mnesai on N2 did not affect the contents on N1 - ?match( {atomic,[#test_rec{key=2,val=33}]}, + ?match( {atomic,[#test_rec{key=2,val=44}]}, rpc:call(N1, mnesia, transaction, [Read_one] ) ), ?verify_mnesia(Nodes, []). @@ -1350,15 +1351,27 @@ dump_ram_copies(Config) when is_list(Config) -> %% test_lib:mnesia_start doesn't work, because it waits %% for the schema on all nodes ... ??? ?match(ok,rpc:call(Node3,mnesia,start,[]) ), - ?match(ok,rpc:call(Node3,mnesia,wait_for_tables, - [[Tab],timer:seconds(30)] ) ), - - %% node3 shall have the contents of the dump - cross_check_tables([C],Tab,{[{Tab,1,4711}],[{Tab,2,42}],[{Tab,3,256}]}), - %% start Mnesia on the other 2 nodes, too + %% node3 is the first-killed node, thus it should wait for better copy + %% from late-killed node to ensure consistency in the cluster + %% to get the better copy, node3 needs to contact the node which has better copy. + ?match({timeout, [Tab]}, + rpc:call(Node3, mnesia,wait_for_tables, + [[Tab],timer:seconds(3)])), + + %% node3 shall NOT serve for this table before it get connected to the better copy. + ?match({badrpc,{'EXIT',{aborted,{no_exists,[Tab,1]}}}}, + rpc:call(Node3, mnesia, dirty_read, [Tab,1])), + + %% start Mnesia on the other 2 nodes now mnesia_test_lib:start_mnesia([Node1,Node2],[Tab]), + + + %% node3 should load the better copy and ready to serve. + ?match(ok, rpc:call(Node3, mnesia,wait_for_tables, + [[Tab],timer:seconds(30)])), + %% Since all nodes are up cross_check_tables([A,B,C],Tab, {[{Tab,1,4711}],[{Tab,2,42}],[{Tab,3,256}]}), ?verify_mnesia(Nodes, []).