Skip to content

Commit 2efc3e7

Browse files
authored
Merge pull request #8155 from rabbitmq/fix-feature-flags-init-vs-code_server-deadlock-take2
rabbit_feature_flags: Fix possible deadlock when calling the Code server (take 2)
2 parents e78427b + aacfa19 commit 2efc3e7

8 files changed

+475
-117
lines changed

deps/rabbit/app.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def all_beam_files(name = "all_beam_files"):
114114
"src/rabbit_ff_extra.erl",
115115
"src/rabbit_ff_registry.erl",
116116
"src/rabbit_ff_registry_factory.erl",
117+
"src/rabbit_ff_registry_wrapper.erl",
117118
"src/rabbit_fhc_helpers.erl",
118119
"src/rabbit_fifo.erl",
119120
"src/rabbit_fifo_client.erl",
@@ -355,6 +356,7 @@ def all_test_beam_files(name = "all_test_beam_files"):
355356
"src/rabbit_ff_extra.erl",
356357
"src/rabbit_ff_registry.erl",
357358
"src/rabbit_ff_registry_factory.erl",
359+
"src/rabbit_ff_registry_wrapper.erl",
358360
"src/rabbit_fhc_helpers.erl",
359361
"src/rabbit_fifo.erl",
360362
"src/rabbit_fifo_client.erl",
@@ -609,6 +611,7 @@ def all_srcs(name = "all_srcs"):
609611
"src/rabbit_ff_extra.erl",
610612
"src/rabbit_ff_registry.erl",
611613
"src/rabbit_ff_registry_factory.erl",
614+
"src/rabbit_ff_registry_wrapper.erl",
612615
"src/rabbit_fhc_helpers.erl",
613616
"src/rabbit_fifo.erl",
614617
"src/rabbit_fifo_client.erl",

deps/rabbit/src/rabbit_feature_flags.erl

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
remote_nodes/0,
114114
running_remote_nodes/0,
115115
does_node_support/3,
116-
inject_test_feature_flags/1,
116+
inject_test_feature_flags/1, inject_test_feature_flags/2,
117117
query_supported_feature_flags/0,
118118
does_enabled_feature_flags_list_file_exist/0,
119119
read_enabled_feature_flags_list/0,
@@ -336,8 +336,8 @@ list() -> list(all).
336336
%% `disabled'.
337337
%% @returns A map of selected feature flags.
338338

339-
list(all) -> rabbit_ff_registry:list(all);
340-
list(enabled) -> rabbit_ff_registry:list(enabled);
339+
list(all) -> rabbit_ff_registry_wrapper:list(all);
340+
list(enabled) -> rabbit_ff_registry_wrapper:list(enabled);
341341
list(disabled) -> maps:filter(
342342
fun(FeatureName, _) -> is_disabled(FeatureName) end,
343343
list(all)).
@@ -381,7 +381,7 @@ enable(FeatureNames) when is_list(FeatureNames) ->
381381
with_feature_flags(FeatureNames, fun enable/1).
382382

383383
uses_callbacks(FeatureName) when is_atom(FeatureName) ->
384-
case rabbit_ff_registry:get(FeatureName) of
384+
case rabbit_ff_registry_wrapper:get(FeatureName) of
385385
undefined -> false;
386386
FeatureProps -> uses_callbacks(FeatureProps)
387387
end;
@@ -504,9 +504,11 @@ is_supported(FeatureNames, Timeout) ->
504504
%% `false' if one of them is not.
505505

506506
is_supported_locally(FeatureName) when is_atom(FeatureName) ->
507-
rabbit_ff_registry:is_supported(FeatureName);
507+
rabbit_ff_registry_wrapper:is_supported(FeatureName);
508508
is_supported_locally(FeatureNames) when is_list(FeatureNames) ->
509-
lists:all(fun(F) -> rabbit_ff_registry:is_supported(F) end, FeatureNames).
509+
lists:all(
510+
fun(F) -> rabbit_ff_registry_wrapper:is_supported(F) end,
511+
FeatureNames).
510512

511513
-spec is_enabled(feature_name() | [feature_name()]) -> boolean().
512514
%% @doc
@@ -563,19 +565,19 @@ is_enabled(FeatureNames, blocking) ->
563565
end.
564566

565567
is_enabled_nb(FeatureName) when is_atom(FeatureName) ->
566-
rabbit_ff_registry:is_enabled(FeatureName);
568+
rabbit_ff_registry_wrapper:is_enabled(FeatureName);
567569
is_enabled_nb(FeatureNames) when is_list(FeatureNames) ->
568570
lists:foldl(
569571
fun
570572
(_F, state_changing = Acc) ->
571573
Acc;
572574
(F, false = Acc) ->
573-
case rabbit_ff_registry:is_enabled(F) of
575+
case rabbit_ff_registry_wrapper:is_enabled(F) of
574576
state_changing -> state_changing;
575577
_ -> Acc
576578
end;
577579
(F, _) ->
578-
rabbit_ff_registry:is_enabled(F)
580+
rabbit_ff_registry_wrapper:is_enabled(F)
579581
end,
580582
true, FeatureNames).
581583

@@ -710,7 +712,7 @@ get_state(FeatureName) when is_atom(FeatureName) ->
710712
%% given feature flag name doesn't correspond to a known feature flag.
711713

712714
get_stability(FeatureName) when is_atom(FeatureName) ->
713-
case rabbit_ff_registry:get(FeatureName) of
715+
case rabbit_ff_registry_wrapper:get(FeatureName) of
714716
undefined -> undefined;
715717
FeatureProps -> get_stability(FeatureProps)
716718
end;
@@ -738,6 +740,9 @@ init() ->
738740
-define(PT_TESTSUITE_ATTRS, {?MODULE, testsuite_feature_flags_attrs}).
739741

740742
inject_test_feature_flags(FeatureFlags) ->
743+
inject_test_feature_flags(FeatureFlags, true).
744+
745+
inject_test_feature_flags(FeatureFlags, InitReg) ->
741746
ExistingAppAttrs = module_attributes_from_testsuite(),
742747
FeatureFlagsPerApp0 = lists:foldl(
743748
fun({Origin, Origin, FFlags}, Acc) ->
@@ -768,7 +773,10 @@ inject_test_feature_flags(FeatureFlags) ->
768773
[FeatureFlags, AttributesFromTestsuite],
769774
#{domain => ?RMQLOG_DOMAIN_FEAT_FLAGS}),
770775
ok = persistent_term:put(?PT_TESTSUITE_ATTRS, AttributesFromTestsuite),
771-
rabbit_ff_registry_factory:initialize_registry().
776+
case InitReg of
777+
true -> rabbit_ff_registry_factory:initialize_registry();
778+
false -> ok
779+
end.
772780

773781
module_attributes_from_testsuite() ->
774782
persistent_term:get(?PT_TESTSUITE_ATTRS, []).

deps/rabbit/src/rabbit_ff_controller.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1387,7 +1387,7 @@ enable_dependencies1(
13871387
Rets :: #{node() => term()}.
13881388

13891389
run_callback(Nodes, FeatureName, Command, Extra, Timeout) ->
1390-
FeatureProps = rabbit_ff_registry:get(FeatureName),
1390+
FeatureProps = rabbit_ff_registry_wrapper:get(FeatureName),
13911391
Callbacks = maps:get(callbacks, FeatureProps, #{}),
13921392
case Callbacks of
13931393
#{Command := {CallbackMod, CallbackFun}}

deps/rabbit/src/rabbit_ff_registry.erl

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,26 @@
3737
-on_load(on_load/0).
3838
-endif.
3939

40-
%% Initially, is_registry_initialized/0 always returns false and this `Call'
41-
%% is always called. The case statement is here to convince Dialyzer that the
42-
%% function could return values of type `__ReturnedIfUninitialized' or
40+
%% In this registry stub, most functions want to return `init_required' to let
41+
%% {@link rabbit_ff_registry_wrapper} do the first time initialization.
42+
%%
43+
%% The inner case statement is here to convince Dialyzer that the function
44+
%% could return values of type `__ReturnedIfUninitialized' or
4345
%% `__NeverReturned'.
4446
%%
4547
%% If the function was only calling itself (`Call'), Dialyzer would consider
46-
%% that it would never return.
48+
%% that it would never return. With the outer case, Dialyzer would conclude
49+
%% that `__ReturnedIfUninitialized' is always returned and other values will
50+
%% never be returned and there is no point in expecting them.
4751
%%
48-
%% With just `is_registry_initialized()' case, Dialyzer would conclude that
49-
%% `__ReturnedIfUninitialized' is always returned and other values will never
50-
%% be returned and there is no point in expecting them.
52+
%% In the end, `Call' is never executed because {@link
53+
%% rabbit_ff_registry_wrapper} is responsible for calling the registry
54+
%% function again after initialization.
5155
%%
5256
%% With both cases in place, it seems that we can convince Dialyzer that the
5357
%% function returns values matching its spec.
5458
-define(convince_dialyzer(__Call, __ReturnedIfUninitialized, __NeverReturned),
55-
case is_registry_initialized() of
59+
case always_return_true() of
5660
false ->
5761
__Call;
5862
true ->
@@ -62,8 +66,11 @@
6266
end
6367
end).
6468

65-
-spec get(rabbit_feature_flags:feature_name()) ->
66-
rabbit_feature_flags:feature_props_extended() | undefined.
69+
-spec get(FeatureName) -> Ret when
70+
FeatureName :: rabbit_feature_flags:feature_name(),
71+
Ret :: FeatureProps | init_required,
72+
FeatureProps :: rabbit_feature_flags:feature_props_extended() |
73+
undefined.
6774
%% @doc
6875
%% Returns the properties of a feature flag.
6976
%%
@@ -74,13 +81,15 @@
7481
%% @returns the properties of the specified feature flag.
7582

7683
get(FeatureName) ->
77-
_ = rabbit_ff_registry_factory:initialize_registry(),
7884
?convince_dialyzer(
7985
?MODULE:get(FeatureName),
80-
undefined,
86+
init_required,
8187
#{provided_by => rabbit}).
8288

83-
-spec list(all | enabled | disabled) -> rabbit_feature_flags:feature_flags().
89+
-spec list(Which) -> Ret when
90+
Which :: all | enabled | disabled,
91+
Ret :: FeatureFlags | init_required,
92+
FeatureFlags :: rabbit_feature_flags:feature_flags().
8493
%% @doc
8594
%% Lists all, enabled or disabled feature flags, depending on the argument.
8695
%%
@@ -92,10 +101,11 @@ get(FeatureName) ->
92101
%% @returns A map of selected feature flags.
93102

94103
list(Which) ->
95-
_ = rabbit_ff_registry_factory:initialize_registry(),
96-
?convince_dialyzer(?MODULE:list(Which), #{}, #{}).
104+
?convince_dialyzer(?MODULE:list(Which), init_required, #{}).
97105

98-
-spec states() -> rabbit_feature_flags:feature_states().
106+
-spec states() -> Ret when
107+
Ret :: FeatureStates | init_required,
108+
FeatureStates :: rabbit_feature_flags:feature_states().
99109
%% @doc
100110
%% Returns the states of supported feature flags.
101111
%%
@@ -105,10 +115,12 @@ list(Which) ->
105115
%% @returns A map of feature flag states.
106116

107117
states() ->
108-
_ = rabbit_ff_registry_factory:initialize_registry(),
109-
?convince_dialyzer(?MODULE:states(), #{}, #{}).
118+
?convince_dialyzer(?MODULE:states(), init_required, #{}).
110119

111-
-spec is_supported(rabbit_feature_flags:feature_name()) -> boolean().
120+
-spec is_supported(FeatureName) -> Ret when
121+
FeatureName :: rabbit_feature_flags:feature_name(),
122+
Ret :: Supported | init_required,
123+
Supported :: boolean().
112124
%% @doc
113125
%% Returns if a feature flag is supported.
114126
%%
@@ -120,10 +132,12 @@ states() ->
120132
%% otherwise.
121133

122134
is_supported(FeatureName) ->
123-
_ = rabbit_ff_registry_factory:initialize_registry(),
124-
?convince_dialyzer(?MODULE:is_supported(FeatureName), false, true).
135+
?convince_dialyzer(?MODULE:is_supported(FeatureName), init_required, true).
125136

126-
-spec is_enabled(rabbit_feature_flags:feature_name()) -> boolean() | state_changing.
137+
-spec is_enabled(FeatureName) -> Ret when
138+
FeatureName :: rabbit_feature_flags:feature_name(),
139+
Ret :: Enabled | init_required,
140+
Enabled :: boolean() | state_changing.
127141
%% @doc
128142
%% Returns if a feature flag is enabled or if its state is changing.
129143
%%
@@ -135,8 +149,7 @@ is_supported(FeatureName) ->
135149
%% its state is transient, or `false' otherwise.
136150

137151
is_enabled(FeatureName) ->
138-
_ = rabbit_ff_registry_factory:initialize_registry(),
139-
?convince_dialyzer(?MODULE:is_enabled(FeatureName), false, true).
152+
?convince_dialyzer(?MODULE:is_enabled(FeatureName), init_required, true).
140153

141154
-spec is_registry_initialized() -> boolean().
142155
%% @doc
@@ -169,7 +182,9 @@ is_registry_initialized() ->
169182
is_registry_written_to_disk() ->
170183
always_return_true().
171184

172-
-spec inventory() -> rabbit_feature_flags:inventory().
185+
-spec inventory() -> Ret when
186+
Ret :: Inventory | init_required,
187+
Inventory :: rabbit_feature_flags:inventory().
173188

174189
inventory() ->
175190
_ = rabbit_ff_registry_factory:initialize_registry(),

deps/rabbit/src/rabbit_ff_registry_factory.erl

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -735,14 +735,9 @@ registry_vsn() ->
735735
proplists:get_value(vsn, Attrs, undefined).
736736

737737
purge_old_registry(Mod) ->
738-
case erlang:check_process_code(self(), rabbit_ff_registry) of
739-
false ->
740-
case code:is_loaded(Mod) of
741-
{file, _} -> do_purge_old_registry(Mod);
742-
false -> ok
743-
end;
744-
true ->
745-
ok
738+
case code:is_loaded(Mod) of
739+
{file, _} -> do_purge_old_registry(Mod);
740+
false -> ok
746741
end.
747742

748743
do_purge_old_registry(Mod) ->

0 commit comments

Comments
 (0)