Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/ekka.appup.src
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
{VSN,
[{"0.8.1.17",
[{load_module,ekka_cluster,brutal_purge,soft_purge,[]},
{load_module,ekka_mnesia,brutal_purge,soft_purge,[]},
{load_module,ekka_autoheal,brutal_purge,soft_purge,[]}]},
{"0.8.1.16",
[{load_module,ekka_cluster,brutal_purge,soft_purge,[]},
Expand Down Expand Up @@ -142,6 +143,7 @@
{load_module,ekka_dist,brutal_purge,soft_purge,[]}]}],
[{"0.8.1.17",
[{load_module,ekka_cluster,brutal_purge,soft_purge,[]},
{load_module,ekka_mnesia,brutal_purge,soft_purge,[]},
{load_module,ekka_autoheal,brutal_purge,soft_purge,[]}]},
{"0.8.1.16",
[{load_module,ekka_cluster,brutal_purge,soft_purge,[]},
Expand Down
114 changes: 1 addition & 113 deletions src/ekka_mnesia.erl
Original file line number Diff line number Diff line change
Expand Up @@ -310,119 +310,7 @@ wait_for(stop) ->

wait_for(tables) ->
Tables = mnesia:system_info(local_tables),
case do_wait_for_tables(Tables) of
{error, _} = Error ->
Error;
ok ->
case mnesia:system_info(db_nodes) -- [node()] of
[] -> ok;
_OtherDBNodes ->
case ekka:env(skip_table_load_check, false) of
true ->
logger:warning("Skipping table load origin check because "
"'skip_table_load_check' is set to true. "
"This may hide data inconsistency issues, "
"please unset this option after the node is up."),
ok;
false ->
%% Make sure we have loaded tables from a remote node,
%% or force-loaded from local copies by the user.
RemoteActiveReplicas = get_remote_active_replicas(),
case check_tables_load_from(Tables, RemoteActiveReplicas) of
ok -> ok;
{error, _} = Error -> Error
end
end
end
end.

check_tables_load_from([], _) ->
ok;
check_tables_load_from([schema | Rest], RemoteActiveReplicas) ->
check_tables_load_from(Rest, RemoteActiveReplicas);
check_tables_load_from([Tab | Rest], RemoteActiveReplicas) ->
LoadFrom = mnesia_lib:val({Tab, load_node}),
LoadReason = mnesia_lib:val({Tab, load_reason}),
case mnesia:table_info(Tab, local_content) of
true ->
check_tables_load_from(Rest, RemoteActiveReplicas);
false ->
Self = node(),
RemoteReplicas = get_remote_replicas(Tab),
case {LoadFrom, RemoteReplicas, RemoteActiveReplicas} of
{Self, [], _} ->
%% we have no remote replica
check_tables_load_from(Rest, RemoteActiveReplicas);
{Self, [_ | _], []} ->
%% we have one or more remote replicas but we are loaded from local,
%% maybe force loaded by the user, as we have no active replicas other than current node.
logger:warning(
"Mnesia loaded table ~p from local copy while remote replicas ~p "
"exist but none are active (load_reason=~p). "
"If you have intentionally force-loaded this table (e.g. after removing "
"remote nodes from the cluster), this warning can be safely ignored. "
"Otherwise, please check the network connectivity between cluster nodes, "
"ensure remote nodes are reachable, and restart EMQX.",
[Tab, RemoteReplicas, LoadReason]),
check_tables_load_from(Rest, RemoteActiveReplicas);
{Self, [_ | _], [_ | _]} ->
%% prevent the current node from starting as the table is loaded from local and there
%% are other active replicas in the cluster
logger:error(
"Mnesia loaded table ~p from local copy while remote active replicas ~p "
"are available (load_reason=~p). "
"This is likely caused by a network partition or misconfiguration. "
"The node refuses to start to prevent data inconsistency. "
"Please fix the network connectivity between cluster nodes and restart EMQX.",
[Tab, RemoteActiveReplicas, LoadReason]),
{error, {tab_loaded_from_local_copy, Tab, LoadReason}};
{Self, unknown, _} ->
logger:error(
"Mnesia loaded table ~p from local copy but this table only resides "
"on remote nodes (load_reason=~p). "
"This is likely caused by a network partition during startup. "
"Please fix the network connectivity between cluster nodes and restart EMQX.",
[Tab, LoadReason]),
{error, {tab_loaded_from_local_copy, Tab, LoadReason}};
{_, _, _} ->
check_tables_load_from(Rest, RemoteActiveReplicas)
end
end.

%% @doc Get the remote nodes that are running emqx.
%%
%% Q: Why not use mnesia:table_info(Tab, active_replicas) -- [Self]?
%% A: Because it will cause a deadlock when all other nodes are restarting and waiting for
%% this node to start.
get_remote_active_replicas() ->
RunningNodes = ekka_mnesia:running_nodes() -- [node()],
Result = erpc:multicall(RunningNodes, application, which_applications, [], 5000),
Zip = lists:zip(RunningNodes, Result),
lists:filtermap(
fun
({Node, {ok, Applications}}) ->
case lists:keymember(emqx, 1, Applications) of
true ->
{true, Node};
false ->
false
end;
({_, {error, _}}) ->
false
end,
Zip
).

get_remote_replicas(Tab) ->
case mnesia:table_info(Tab, storage_type) of
unknown ->
unknown;
_ ->
RamCopies = mnesia:table_info(Tab, ram_copies),
DiscCopies = mnesia:table_info(Tab, disc_copies),
DiscOnlyCopies = mnesia:table_info(Tab, disc_only_copies),
lists:usort((RamCopies ++ DiscCopies ++ DiscOnlyCopies) -- [node()])
end.
do_wait_for_tables(Tables).

do_wait_for_tables(Tables) ->
case mnesia:wait_for_tables(Tables, 30000) of
Expand Down
6 changes: 1 addition & 5 deletions test/ekka_mnesia_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,7 @@ t_diagnosis_tab(_)->
%% Start mnesia on N2.
ok = rpc:call(N2, ekka_mnesia, start, []),

%% Restart mnesia on N1 so it loads the table from N2
%% instead of keeping the stale local copy.
%% (check_tables_load_from rejects locally-loaded tables
%% when remote active replicas exist.)
stopped = rpc:call(N1, mnesia, stop, []),
%% Start ekka_mnesia on N1, no blocking
ok = rpc:call(N1, ekka_mnesia, start, []),

%% Check tables are loaded on two
Expand Down
Loading