Skip to content

Commit b907d34

Browse files
Merge pull request #1042 from rabbitmq/rabbitmq-server-1040
Add background GC settings to new config format
2 parents b2141bd + a31bb2e commit b907d34

File tree

11 files changed

+278
-65
lines changed

11 files changed

+278
-65
lines changed

docs/rabbitmq.conf.example

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,21 @@
380380
##
381381
# queue_index_embed_msgs_below = 4kb
382382

383+
## Whether or not to enable background periodic GC of all
384+
## Erlang processes in "waiting" state.
385+
##
386+
## Disabling background GC may reduce latency for client operations,
387+
## keeping it enabled may reduce median RAM usage.
388+
##
389+
# background_gc_enabled = true
390+
391+
## Target (desired) interval (in milliseconds) at which we run background GC.
392+
## The actual interval will vary depending on how long it takes to execute
393+
## the operation (can be higher than this interval). Values less than
394+
## 30000 milliseconds are not recommended.
395+
##
396+
# background_gc_target_interval = 60000
397+
383398
## ----------------------------------------------------------------------------
384399
## Advanced Erlang Networking/Clustering Options.
385400
##
@@ -615,7 +630,7 @@
615630

616631
## File rotation config. No rotation by defualt.
617632
## DO NOT SET rotation date to ''. Leave unset if require "" value
618-
# log.file.rotation.date = $D0
633+
# log.file.rotation.date = $D0
619634
# log.file.rotation.size = 0
620635

621636

docs/rabbitmq.config.example

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,22 @@
331331
%% Size in bytes below which to embed messages in the queue index. See
332332
%% http://www.rabbitmq.com/persistence-conf.html
333333
%%
334-
%% {queue_index_embed_msgs_below, 4096}
334+
%% {queue_index_embed_msgs_below, 4096},
335+
336+
%% Whether or not to enable background periodic GC of all
337+
%% Erlang processes in "waiting" state.
338+
%%
339+
%% Disabling background GC may reduce latency for client operations,
340+
%% keeping it enabled may reduce median RAM usage.
341+
%%
342+
%% {background_gc_enabled, true},
343+
%%
344+
%% Target (desired) interval (in milliseconds) at which we run background GC.
345+
%% The actual interval will vary depending on how long it takes to execute
346+
%% the operation (can be higher than this interval). Values less than
347+
%% 30000 milliseconds are not recommended.
348+
%%
349+
%% {background_gc_target_interval, 60000}
335350

336351
]},
337352

priv/schema/rabbitmq.schema

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,20 @@ end}.
910910
{mapping, "queue_index_embed_msgs_below", "rabbit.queue_index_embed_msgs_below",
911911
[{datatype, bytesize}]}.
912912

913+
%% Whether or not to enable background GC.
914+
%%
915+
%% {background_gc_enabled, true}
916+
917+
{mapping, "background_gc_enabled", "rabbit.background_gc_enabled",
918+
[{datatype, {enum, [true, false]}}]}.
919+
920+
%% Interval (in milliseconds) at which we run background GC.
921+
%%
922+
%% {background_gc_target_interval, 60000}
923+
924+
{mapping, "background_gc_target_interval", "rabbit.background_gc_target_interval",
925+
[{datatype, integer}]}.
926+
913927
% ==========================
914928
% Lager section
915929
% ==========================

scripts/rabbitmq-defaults

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,15 @@ SCHEMA_DIR=${SYS_PREFIX}/var/lib/rabbitmq/schema
4444

4545
PLUGINS_DIR="${RABBITMQ_HOME}/plugins"
4646

47+
# RABBIT_HOME can contain a version number, so default plugins
48+
# directory can be hard to find if we want to package some plugin
49+
# separately. When RABBITMQ_HOME points to a standard location where
50+
# it's usally being installed by package managers, we add
51+
# "/usr/lib/rabbitmq/plugins" to plugin search path.
52+
case "$RABBITMQ_HOME" in
53+
/usr/lib/rabbitmq/*)
54+
PLUGINS_DIR="/usr/lib/rabbitmq/plugins:$PLUGINS_DIR"
55+
;;
56+
esac
57+
4758
CONF_ENV_FILE=${SYS_PREFIX}/etc/rabbitmq/rabbitmq-env.conf

scripts/rabbitmq-env

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ rmq_realpath() {
5757
fi
5858
}
5959

60+
path_contains_existing_directory() {
61+
local path="${1:?}"
62+
local dir
63+
local rc
64+
local IFS="
65+
"
66+
for dir in $(echo "$path" | tr ':' '\n'); do
67+
if [ -d "$dir" ]; then
68+
return 0
69+
fi
70+
done
71+
return 1
72+
}
73+
6074
RABBITMQ_HOME="$(rmq_realpath "${RABBITMQ_SCRIPTS_DIR}/..")"
6175
ESCRIPT_DIR="${RABBITMQ_HOME}/escript"
6276

@@ -108,8 +122,9 @@ fi
108122
rmq_normalize_path() {
109123
local path=$1
110124

111-
# Remove redundant slashes and strip a trailing slash
112-
echo "$path" | sed -e 's#/\{2,\}#/#g' -e 's#/$##'
125+
# Remove redundant slashes and strip a trailing slash for a
126+
# PATH-like vars - ':' is the delimiter
127+
echo "$path" | sed -e 's#/\{2,\}#/#g' -e 's#/$##' -e 's#/:#:#g'
113128
}
114129

115130
rmq_normalize_path_var() {
@@ -265,9 +280,8 @@ if [ "${RABBITMQ_DEV_ENV}" ]; then
265280
RABBITMQ_ENABLED_PLUGINS_FILE="${enabled_plugins_file}"
266281
fi
267282
fi
268-
269-
270-
if [ -d "${RABBITMQ_PLUGINS_DIR}" ]; then
283+
284+
if path_contains_existing_directory "${RABBITMQ_PLUGINS_DIR}" ; then
271285
# RabbitMQ was started with "make run-broker" from its own
272286
# source tree. Take rabbit_common from the plugins directory.
273287
ERL_LIBS="${RABBITMQ_PLUGINS_DIR}:${ERL_LIBS}"
@@ -291,7 +305,7 @@ if [ "${RABBITMQ_DEV_ENV}" ]; then
291305
ERL_LIBS="${DEPS_DIR_norm}:${ERL_LIBS}"
292306
fi
293307
else
294-
if [ -d "${RABBITMQ_PLUGINS_DIR}" ]; then
308+
if path_contains_existing_directory "${RABBITMQ_PLUGINS_DIR}" ; then
295309
# RabbitMQ was started from its install directory. Take
296310
# rabbit_common from the plugins directory.
297311
ERL_LIBS="${RABBITMQ_PLUGINS_DIR}:${ERL_LIBS}"

src/background_gc.erl

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
terminate/2, code_change/3]).
2626

2727
-define(MAX_RATIO, 0.01).
28-
-define(IDEAL_INTERVAL, 60000).
2928
-define(MAX_INTERVAL, 240000).
3029

3130
-record(state, {last_interval}).
@@ -45,7 +44,9 @@ run() -> gen_server2:cast(?MODULE, run).
4544

4645
%%----------------------------------------------------------------------------
4746

48-
init([]) -> {ok, interval_gc(#state{last_interval = ?IDEAL_INTERVAL})}.
47+
init([]) ->
48+
{ok, IdealInterval} = application:get_env(rabbit, background_gc_target_interval),
49+
{ok, interval_gc(#state{last_interval = IdealInterval})}.
4950

5051
handle_call(Msg, _From, State) ->
5152
{stop, {unexpected_call, Msg}, {unexpected_call, Msg}, State}.
@@ -65,14 +66,22 @@ terminate(_Reason, State) -> State.
6566
%%----------------------------------------------------------------------------
6667

6768
interval_gc(State = #state{last_interval = LastInterval}) ->
69+
{ok, IdealInterval} = application:get_env(rabbit, background_gc_target_interval),
6870
{ok, Interval} = rabbit_misc:interval_operation(
6971
{?MODULE, gc, []},
70-
?MAX_RATIO, ?MAX_INTERVAL, ?IDEAL_INTERVAL, LastInterval),
72+
?MAX_RATIO, ?MAX_INTERVAL, IdealInterval, LastInterval),
7173
erlang:send_after(Interval, self(), run),
7274
State#state{last_interval = Interval}.
7375

7476
gc() ->
75-
[garbage_collect(P) || P <- processes(),
76-
{status, waiting} == process_info(P, status)],
77-
garbage_collect(), %% since we will never be waiting...
77+
Enabled = rabbit_misc:get_env(rabbit, background_gc_enabled, true),
78+
case Enabled of
79+
true ->
80+
[garbage_collect(P) || P <- processes(),
81+
{status, waiting} == process_info(P, status)],
82+
%% since we will never be waiting...
83+
garbage_collect();
84+
false ->
85+
ok
86+
end,
7887
ok.

src/rabbit.app.src

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,5 +117,7 @@
117117
]},
118118

119119
%% rabbitmq-server-973
120-
{lazy_queue_explicit_gc_run_operation_threshold, 250}
120+
{lazy_queue_explicit_gc_run_operation_threshold, 250},
121+
{background_gc_enabled, true},
122+
{background_gc_target_interval, 60000}
121123
]}]}.

src/rabbit_plugins.erl

Lines changed: 102 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -134,46 +134,16 @@ active() ->
134134
lists:member(App, InstalledPlugins)].
135135

136136
%% @doc Get the list of plugins which are ready to be enabled.
137-
list(PluginsDir) ->
138-
list(PluginsDir, false).
137+
list(PluginsPath) ->
138+
list(PluginsPath, false).
139139

140-
list(PluginsDir, IncludeRequiredDeps) ->
141-
EZs = [{ez, EZ} || EZ <- filelib:wildcard("*.ez", PluginsDir)],
142-
FreeApps = [{app, App} ||
143-
App <- filelib:wildcard("*/ebin/*.app", PluginsDir)],
144-
%% We load the "rabbit" application to be sure we can get the
145-
%% "applications" key. This is required for rabbitmq-plugins for
146-
%% instance.
147-
application:load(rabbit),
148-
{ok, RabbitDeps} = application:get_key(rabbit, applications),
149-
AllPlugins = [plugin_info(PluginsDir, Plug) || Plug <- EZs ++ FreeApps],
150-
{AvailablePlugins, Problems} =
151-
lists:foldl(
152-
fun ({error, EZ, Reason}, {Plugins1, Problems1}) ->
153-
{Plugins1, [{EZ, Reason} | Problems1]};
154-
(Plugin = #plugin{name = Name},
155-
{Plugins1, Problems1}) ->
156-
%% Applications RabbitMQ depends on (eg.
157-
%% "rabbit_common") can't be considered
158-
%% plugins, otherwise rabbitmq-plugins would
159-
%% list them and the user may believe he can
160-
%% disable them.
161-
case IncludeRequiredDeps orelse
162-
not lists:member(Name, RabbitDeps) of
163-
true -> {[Plugin|Plugins1], Problems1};
164-
false -> {Plugins1, Problems1}
165-
end
166-
end, {[], []},
167-
AllPlugins),
168-
case Problems of
169-
[] -> ok;
170-
_ -> rabbit_log:warning(
171-
"Problem reading some plugins: ~p~n", [Problems])
172-
end,
173-
174-
Plugins = lists:filter(fun(P) -> not plugin_provided_by_otp(P) end,
175-
AvailablePlugins),
176-
ensure_dependencies(Plugins).
140+
list(PluginsPath, IncludeRequiredDeps) ->
141+
{AllPlugins, LoadingProblems} = discover_plugins(split_path(PluginsPath)),
142+
{UniquePlugins, DuplicateProblems} = remove_duplicate_plugins(AllPlugins),
143+
Plugins1 = maybe_keep_required_deps(IncludeRequiredDeps, UniquePlugins),
144+
Plugins2 = remove_otp_overrideable_plugins(Plugins1),
145+
maybe_report_plugin_loading_problems(LoadingProblems ++ DuplicateProblems),
146+
ensure_dependencies(Plugins2).
177147

178148
%% @doc Read the list of enabled plugins from the supplied term file.
179149
read_enabled(PluginsFile) ->
@@ -425,14 +395,12 @@ prepare_plugin(#plugin{type = dir, name = Name, location = Location},
425395
ExpandDir) ->
426396
rabbit_file:recursive_copy(Location, filename:join([ExpandDir, Name])).
427397

428-
plugin_info(Base, {ez, EZ0}) ->
429-
EZ = filename:join([Base, EZ0]),
398+
plugin_info({ez, EZ}) ->
430399
case read_app_file(EZ) of
431400
{application, Name, Props} -> mkplugin(Name, Props, ez, EZ);
432401
{error, Reason} -> {error, EZ, Reason}
433402
end;
434-
plugin_info(Base, {app, App0}) ->
435-
App = filename:join([Base, App0]),
403+
plugin_info({app, App}) ->
436404
case rabbit_file:read_term_file(App) of
437405
{ok, [{application, Name, Props}]} ->
438406
mkplugin(Name, Props, dir,
@@ -486,9 +454,99 @@ plugin_names(Plugins) ->
486454
[Name || #plugin{name = Name} <- Plugins].
487455

488456
lookup_plugins(Names, AllPlugins) ->
489-
% Preserve order of Names
457+
%% Preserve order of Names
490458
lists:map(
491459
fun(Name) ->
492460
lists:keyfind(Name, #plugin.name, AllPlugins)
493461
end,
494462
Names).
463+
464+
%% Split PATH-like value into its components.
465+
split_path(PathString) ->
466+
Delimiters = case os:type() of
467+
{unix, _} -> ":";
468+
{win32, _} -> ";"
469+
end,
470+
string:tokens(PathString, Delimiters).
471+
472+
%% Search for files using glob in a given dir. Returns full filenames of those files.
473+
full_path_wildcard(Glob, Dir) ->
474+
[filename:join([Dir, File]) || File <- filelib:wildcard(Glob, Dir)].
475+
476+
%% Returns list off all .ez files in a given set of directories
477+
list_ezs([]) ->
478+
[];
479+
list_ezs([Dir|Rest]) ->
480+
[{ez, EZ} || EZ <- full_path_wildcard("*.ez", Dir)] ++ list_ezs(Rest).
481+
482+
%% Returns list of all files that look like OTP applications in a
483+
%% given set of directories.
484+
list_free_apps([]) ->
485+
[];
486+
list_free_apps([Dir|Rest]) ->
487+
[{app, App} || App <- full_path_wildcard("*/ebin/*.app", Dir)]
488+
++ list_free_apps(Rest).
489+
490+
compare_by_name_and_version(#plugin{name = Name, version = VersionA},
491+
#plugin{name = Name, version = VersionB}) ->
492+
ec_semver:lte(VersionA, VersionB);
493+
compare_by_name_and_version(#plugin{name = NameA},
494+
#plugin{name = NameB}) ->
495+
NameA =< NameB.
496+
497+
-spec discover_plugins([Directory]) -> {[#plugin{}], [Problem]} when
498+
Directory :: file:name(),
499+
Problem :: {file:name(), term()}.
500+
discover_plugins(PluginsDirs) ->
501+
EZs = list_ezs(PluginsDirs),
502+
FreeApps = list_free_apps(PluginsDirs),
503+
read_plugins_info(EZs ++ FreeApps, {[], []}).
504+
505+
read_plugins_info([], Acc) ->
506+
Acc;
507+
read_plugins_info([Path|Paths], {Plugins, Problems}) ->
508+
case plugin_info(Path) of
509+
#plugin{} = Plugin ->
510+
read_plugins_info(Paths, {[Plugin|Plugins], Problems});
511+
{error, Location, Reason} ->
512+
read_plugins_info(Paths, {Plugins, [{Location, Reason}|Problems]})
513+
end.
514+
515+
remove_duplicate_plugins(Plugins) ->
516+
%% Reverse order ensures that if there are several versions of the
517+
%% same plugin, the most recent one comes first.
518+
Sorted = lists:reverse(
519+
lists:sort(fun compare_by_name_and_version/2, Plugins)),
520+
remove_duplicate_plugins(Sorted, {[], []}).
521+
522+
remove_duplicate_plugins([], Acc) ->
523+
Acc;
524+
remove_duplicate_plugins([Best = #plugin{name = Name}, Offender = #plugin{name = Name} | Rest],
525+
{Plugins0, Problems0}) ->
526+
Problems1 = [{Offender#plugin.location, duplicate_plugin}|Problems0],
527+
remove_duplicate_plugins([Best|Rest], {Plugins0, Problems1});
528+
remove_duplicate_plugins([Plugin|Rest], {Plugins0, Problems0}) ->
529+
Plugins1 = [Plugin|Plugins0],
530+
remove_duplicate_plugins(Rest, {Plugins1, Problems0}).
531+
532+
maybe_keep_required_deps(true, Plugins) ->
533+
Plugins;
534+
maybe_keep_required_deps(false, Plugins) ->
535+
%% We load the "rabbit" application to be sure we can get the
536+
%% "applications" key. This is required for rabbitmq-plugins for
537+
%% instance.
538+
application:load(rabbit),
539+
{ok, RabbitDeps} = application:get_key(rabbit, applications),
540+
lists:filter(fun(#plugin{name = Name}) ->
541+
not lists:member(Name, RabbitDeps)
542+
end,
543+
Plugins).
544+
545+
remove_otp_overrideable_plugins(Plugins) ->
546+
lists:filter(fun(P) -> not plugin_provided_by_otp(P) end,
547+
Plugins).
548+
549+
maybe_report_plugin_loading_problems([]) ->
550+
ok;
551+
maybe_report_plugin_loading_problems(Problems) ->
552+
rabbit_log:warning("Problem reading some plugins: ~p~n", [Problems]).

0 commit comments

Comments
 (0)