File 0410-Do-NOT-disc_load-from-ram_copies-when-master_node-is.patch of Package erlang
From 9818fcc0b899c7d19f017dc3059a5c5f4b0b450c Mon Sep 17 00:00:00 2001
From: Dan Gudmundsson <dgud@erlang.org>
Date: Wed, 8 Aug 2018 12:14:40 +0200
Subject: [PATCH] Do NOT disc_load from ram_copies when master_node is set
Setting master_nodes to a node with ram_copies replica and
that node had not loaded the table, could cause it load an
empty table, even though (non master) nodes had disc_replicas.
This meant that tables where unexpected empty after multiple failures
happened. When this happen do not load the table and wait for user
to force_load it on some node, preferably with a disk copy.
---
lib/mnesia/src/mnesia_controller.erl | 3 +-
lib/mnesia/test/mnesia_durability_test.erl | 107 ++++++++++++++++++++++++++++-
2 files changed, 107 insertions(+), 3 deletions(-)
diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl
index 77013489b3..7a592f25f9 100644
--- a/lib/mnesia/src/mnesia_controller.erl
+++ b/lib/mnesia/src/mnesia_controller.erl
@@ -1456,7 +1456,8 @@ orphan_tables([Tab | Tabs], Node, Ns, Local, Remote) ->
L = [Tab | Local],
orphan_tables(Tabs, Node, Ns, L, Remote);
Masters ->
- R = [{Tab, Masters} | Remote],
+ %% Do not disc_load table from RamCopyHolders
+ R = [{Tab, Masters -- RamCopyHolders} | Remote],
orphan_tables(Tabs, Node, Ns, Local, R)
end;
_ ->
diff --git a/lib/mnesia/test/mnesia_durability_test.erl b/lib/mnesia/test/mnesia_durability_test.erl
index 2fac5cac82..c186f7330b 100644
--- a/lib/mnesia/test/mnesia_durability_test.erl
+++ b/lib/mnesia/test/mnesia_durability_test.erl
@@ -91,7 +92,8 @@ groups() ->
{load_tables_with_master_tables, [],
[master_nodes, starting_master_nodes,
master_on_non_local_tables,
- remote_force_load_with_local_master_node]},
+ remote_force_load_with_local_master_node,
+ master_node_with_ram_copy_2, master_node_with_ram_copy_3]},
{durability_of_dump_tables, [],
[dump_ram_copies, dump_disc_copies, dump_disc_only]}].
@@ -1165,6 +1167,107 @@ remote_force_load_with_local_master_node(Config) when is_list(Config) ->
?verify_mnesia(Nodes, []).
+master_node_with_ram_copy_2(Config) when is_list(Config) ->
+ [A, B] = Nodes = ?acquire_nodes(2, Config),
+ Tab = ?FUNCTION_NAME,
+ ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, [A]}, {ram_copies, [B]}])),
+ ?match({atomic,ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+
+ %% Test that we don't load from ram_copies
+ ?match(stopped, rpc:call(A, mnesia, stop, [])),
+ ?match(stopped, rpc:call(B, mnesia, stop, [])),
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 1000])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, init}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+
+ %% Test that master_nodes set to ram_copy node require force_load
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [[B]])),
+ ?match(stopped, rpc:call(A, mnesia, stop, [])),
+ ?match(stopped, rpc:call(B, mnesia, stop, [])),
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 1000])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 1000])),
+
+ ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 1000])),
+ ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 1000])),
+ ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, init}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+
+ ?verify_mnesia(Nodes, []).
+
+
+master_node_with_ram_copy_3(Config) when is_list(Config) ->
+ [A, B, C] = Nodes = ?acquire_nodes(3, Config),
+ Tab = ?FUNCTION_NAME,
+ ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, [A,C]}, {ram_copies, [B]}])),
+ ?match({atomic,ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+
+ %% Test that we don't load from ram_copies
+ ?match(stopped, rpc:call(A, mnesia, stop, [])),
+ ?match(stopped, rpc:call(C, mnesia, stop, [])),
+ ?match(stopped, rpc:call(B, mnesia, stop, [])),
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 1000])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match(ok, rpc:call(C, mnesia, start, [])),
+ ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, init}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, init}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])),
+
+ %% Test that master_nodes set to ram_copy node will wait until loaded
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [[B]])),
+ ?match(stopped, rpc:call(A, mnesia, stop, [])),
+ ?match({atomic,ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, update})])),
+ ?match(stopped, rpc:call(C, mnesia, stop, [])),
+ ?match({atomic,ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, ram_copies})])),
+ ?match(stopped, rpc:call(B, mnesia, stop, [])),
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 500])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 500])),
+ ?match(ok, rpc:call(C, mnesia, start, [])),
+ ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match([{Tab, 1, update}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, update}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, update}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])),
+
+ %% Test that master_nodes set to ram_copy node requires force load
+ ?match({atomic,ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [[B]])),
+ ?match(ok, rpc:call(C, mnesia, set_master_nodes, [[B]])),
+
+ ?match(stopped, rpc:call(A, mnesia, stop, [])),
+ ?match({atomic,ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, update})])),
+ ?match(stopped, rpc:call(C, mnesia, stop, [])),
+ ?match({atomic,ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, ram_copies})])),
+ ?match(stopped, rpc:call(B, mnesia, stop, [])),
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 500])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 500])),
+ ?match(ok, rpc:call(C, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 500])),
+ ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 500])),
+ ?match({timeout, [Tab]}, rpc:call(B, mnesia, wait_for_tables, [[Tab], 500])),
+ ?match(yes, rpc:call(C, mnesia, force_load_table, [Tab])),
+
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match(ok, rpc:call(C, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match([{Tab, 1, update}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, update}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, update}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])),
+
+ ?verify_mnesia(Nodes, []).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1415,7 +1518,7 @@ do_disc_durability(Config,CopyType) ->
[{Tab_bag, 22, a_2222}], [{Tab_bag, 33, a_3333}],
[{Tab_set, counter, 10}]]),
- timer:sleep(1000), %% Debugging strange msgs..
+ timer:sleep(500), %% Debugging strange msgs..
?log("Flushed ~p ~n", [mnesia_test_lib:flush()]),
?verify_mnesia(Nodes, []).
--
2.16.4