summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rabbit_feature_flags.erl93
1 files changed, 70 insertions, 23 deletions
diff --git a/src/rabbit_feature_flags.erl b/src/rabbit_feature_flags.erl
index 0cddd1b66e..0c3f2e182b 100644
--- a/src/rabbit_feature_flags.erl
+++ b/src/rabbit_feature_flags.erl
@@ -236,6 +236,8 @@
-type migration_fun_context() :: enable | is_enabled.
+-type registry_vsn() :: term().
+
-export_type([feature_flag_modattr/0,
feature_props/0,
feature_name/0,
@@ -832,6 +834,26 @@ list_of_enabled_feature_flags_to_feature_states(FeatureNames) ->
initialize_registry(NewSupportedFeatureFlags,
NewFeatureStates,
WrittenToDisk) ->
+ Ret = maybe_initialize_registry(NewSupportedFeatureFlags,
+ NewFeatureStates,
+ WrittenToDisk),
+ case Ret of
+ ok -> ok;
+ restart -> initialize_registry(NewSupportedFeatureFlags,
+ NewFeatureStates,
+ WrittenToDisk);
+ Error -> Error
+ end.
+
+maybe_initialize_registry(NewSupportedFeatureFlags,
+ NewFeatureStates,
+ WrittenToDisk) ->
+ %% We save the version of the current registry before computing
+ %% the new one. This is used when we do the actual reload: if the
+ %% current registry was reloaded in the meantime, we need to restart
+ %% the computation to make sure we don't loose data.
+ RegistryVsn = registry_vsn(),
+
%% We take the feature flags already registered.
RegistryInitialized = rabbit_ff_registry:is_registry_initialized(),
KnownFeatureFlags1 = case RegistryInitialized of
@@ -887,9 +909,16 @@ initialize_registry(NewSupportedFeatureFlags,
rabbit_log_feature_flags:debug(
"Feature flags: (re)initialize registry (~p)",
[self()]),
- do_initialize_registry(AllFeatureFlags,
- FeatureStates,
- WrittenToDisk);
+ T0 = erlang:timestamp(),
+ Ret = do_initialize_registry(RegistryVsn,
+ AllFeatureFlags,
+ FeatureStates,
+ WrittenToDisk),
+ T1 = erlang:timestamp(),
+ rabbit_log_feature_flags:debug(
+ "Feature flags: time to regen registry: ~p µs",
+ [timer:now_diff(T1, T0)]),
+ Ret;
false ->
rabbit_log_feature_flags:debug(
"Feature flags: registry already up-to-date, skipping init"),
@@ -942,13 +971,15 @@ does_registry_need_refresh(AllFeatureFlags,
true
end.
--spec do_initialize_registry(feature_flags(),
+-spec do_initialize_registry(registry_vsn(),
+ feature_flags(),
feature_states(),
boolean()) ->
ok | {error, any()} | no_return().
%% @private
-do_initialize_registry(AllFeatureFlags,
+do_initialize_registry(RegistryVsn,
+ AllFeatureFlags,
FeatureStates,
WrittenToDisk) ->
%% We log the state of those feature flags.
@@ -978,15 +1009,10 @@ do_initialize_registry(AllFeatureFlags,
%% We request the registry to be regenerated and reloaded with the
%% new state.
- T0 = erlang:timestamp(),
- Ret = regen_registry_mod(AllFeatureFlags,
- FeatureStates,
- WrittenToDisk),
- T1 = erlang:timestamp(),
- rabbit_log_feature_flags:debug(
- "Feature flags: time to regen registry: ~p µs",
- [timer:now_diff(T1, T0)]),
- Ret.
+ regen_registry_mod(RegistryVsn,
+ AllFeatureFlags,
+ FeatureStates,
+ WrittenToDisk).
-spec query_supported_feature_flags() -> feature_flags().
%% @private
@@ -1064,12 +1090,15 @@ merge_new_feature_flags(AllFeatureFlags, App, FeatureName, FeatureProps)
maps:merge(AllFeatureFlags,
#{FeatureName => FeatureProps1}).
--spec regen_registry_mod(feature_flags(),
+-spec regen_registry_mod(registry_vsn(),
+ feature_flags(),
feature_states(),
- boolean()) -> ok | {error, any()} | no_return().
+ boolean()) ->
+ ok | restart | {error, any()} | no_return().
%% @private
-regen_registry_mod(AllFeatureFlags,
+regen_registry_mod(RegistryVsn,
+ AllFeatureFlags,
FeatureStates,
WrittenToDisk) ->
%% Here, we recreate the source code of the `rabbit_ff_registry`
@@ -1246,7 +1275,7 @@ regen_registry_mod(AllFeatureFlags,
return_warnings],
case compile:forms(Forms, CompileOpts) of
{ok, Mod, Bin, _} ->
- load_registry_mod(Mod, Bin);
+ load_registry_mod(RegistryVsn, Mod, Bin);
{error, Errors, Warnings} ->
rabbit_log_feature_flags:error(
"Feature flags: registry compilation:~n"
@@ -1272,11 +1301,11 @@ maybe_log_registry_source_code(Forms) ->
registry_loading_lock() -> ?FF_REGISTRY_LOADING_LOCK.
-endif.
--spec load_registry_mod(atom(), binary()) ->
- ok | {error, any()} | no_return().
+-spec load_registry_mod(registry_vsn(), atom(), binary()) ->
+ ok | restart | no_return().
%% @private
-load_registry_mod(Mod, Bin) ->
+load_registry_mod(RegistryVsn, Mod, Bin) ->
rabbit_log_feature_flags:debug(
"Feature flags: registry module ready, loading it (~p)...",
[self()]),
@@ -1301,7 +1330,10 @@ load_registry_mod(Mod, Bin) ->
%% Therefore there is no chance of a window where there is no
%% registry module available, causing the one on disk to be
%% reloaded.
- Ret = code:load_binary(Mod, FakeFilename, Bin),
+ Ret = case registry_vsn() of
+ RegistryVsn -> code:load_binary(Mod, FakeFilename, Bin);
+ OtherVsn -> {error, {restart, RegistryVsn, OtherVsn}}
+ end,
rabbit_log_feature_flags:debug(
"Feature flags: releasing lock after reloading registry module (~p)",
[self()]),
@@ -1309,8 +1341,16 @@ load_registry_mod(Mod, Bin) ->
case Ret of
{module, _} ->
rabbit_log_feature_flags:debug(
- "Feature flags: registry module loaded"),
+ "Feature flags: registry module loaded (vsn: ~p -> ~p)",
+ [RegistryVsn, registry_vsn()]),
ok;
+ {error, {restart, Expected, Current}} ->
+ rabbit_log_feature_flags:error(
+ "Feature flags: another registry module was loaded in the "
+ "meantime (expected old vsn: ~p, current vsn: ~p); "
+ "restarting the regen",
+ [Expected, Current]),
+ restart;
{error, Reason} ->
rabbit_log_feature_flags:error(
"Feature flags: failed to load registry module: ~p",
@@ -1318,6 +1358,13 @@ load_registry_mod(Mod, Bin) ->
throw({feature_flag_registry_reload_failure, Reason})
end.
+-spec registry_vsn() -> registry_vsn().
+%% @private
+
+registry_vsn() ->
+ Attrs = rabbit_ff_registry:module_info(attributes),
+ proplists:get_value(vsn, Attrs, undefined).
+
purge_old_registry(Mod) ->
case code:is_loaded(Mod) of
{file, _} -> do_purge_old_registry(Mod);