summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMichael Klishin <mklishin@pivotal.io>2019-02-01 20:18:19 +0300
committerMichael Klishin <mklishin@pivotal.io>2019-02-01 20:18:19 +0300
commit160d70cc7840c3be60e0ae6786a182d67707140a (patch)
tree02b2892828de63fce66699a0755e82835e4cd654 /src
parentabd9a2d997b66a38ba0f4febdfa778708feb5c22 (diff)
parented3dd4d6257df0925ce78ad94de099304346da4c (diff)
downloadrabbitmq-server-git-160d70cc7840c3be60e0ae6786a182d67707140a.tar.gz
Merge branch 'master' into dead-letter-testing
Conflicts: src/rabbit_quorum_queue.erl
Diffstat (limited to 'src')
-rw-r--r--src/amqqueue.erl682
-rw-r--r--src/amqqueue_v1.erl403
-rw-r--r--src/background_gc.erl8
-rw-r--r--src/dtree.erl40
-rw-r--r--src/gatherer.erl24
-rw-r--r--src/gm.erl27
-rw-r--r--src/lqueue.erl42
-rw-r--r--src/pg_local.erl23
-rw-r--r--src/rabbit.erl109
-rw-r--r--src/rabbit_access_control.erl34
-rw-r--r--src/rabbit_alarm.erl31
-rw-r--r--src/rabbit_amqqueue.erl1007
-rw-r--r--src/rabbit_amqqueue_process.erl195
-rw-r--r--src/rabbit_amqqueue_sup.erl4
-rw-r--r--src/rabbit_amqqueue_sup_sup.erl11
-rw-r--r--src/rabbit_auth_backend_internal.erl105
-rw-r--r--src/rabbit_backing_queue.erl273
-rw-r--r--src/rabbit_basic.erl103
-rw-r--r--src/rabbit_binding.erl128
-rw-r--r--src/rabbit_channel.erl155
-rw-r--r--src/rabbit_channel_sup.erl4
-rw-r--r--src/rabbit_channel_sup_sup.erl7
-rw-r--r--src/rabbit_client_sup.erl12
-rw-r--r--src/rabbit_connection_helper_sup.erl10
-rw-r--r--src/rabbit_connection_sup.erl5
-rw-r--r--src/rabbit_core_ff.erl97
-rw-r--r--src/rabbit_core_metrics_gc.erl4
-rw-r--r--src/rabbit_credential_validation.erl4
-rw-r--r--src/rabbit_dead_letter.erl4
-rw-r--r--src/rabbit_direct.erl52
-rw-r--r--src/rabbit_disk_monitor.erl24
-rw-r--r--src/rabbit_epmd_monitor.erl6
-rw-r--r--src/rabbit_exchange.erl164
-rw-r--r--src/rabbit_exchange_type_headers.erl9
-rw-r--r--src/rabbit_feature_flags.erl1797
-rw-r--r--src/rabbit_ff_extra.erl236
-rw-r--r--src/rabbit_ff_registry.erl167
-rw-r--r--src/rabbit_fhc_helpers.erl6
-rw-r--r--src/rabbit_file.erl65
-rw-r--r--src/rabbit_guid.erl23
-rw-r--r--src/rabbit_health_check.erl7
-rw-r--r--src/rabbit_limiter.erl76
-rw-r--r--src/rabbit_memory_monitor.erl20
-rw-r--r--src/rabbit_metrics.erl5
-rw-r--r--src/rabbit_mirror_queue_coordinator.erl45
-rw-r--r--src/rabbit_mirror_queue_master.erl120
-rw-r--r--src/rabbit_mirror_queue_misc.erl205
-rw-r--r--src/rabbit_mirror_queue_slave.erl169
-rw-r--r--src/rabbit_mirror_queue_sync.erl32
-rw-r--r--src/rabbit_mnesia.erl135
-rw-r--r--src/rabbit_mnesia_rename.erl5
-rw-r--r--src/rabbit_msg_file.erl16
-rw-r--r--src/rabbit_msg_store.erl71
-rw-r--r--src/rabbit_msg_store_gc.erl19
-rw-r--r--src/rabbit_networking.erl118
-rw-r--r--src/rabbit_node_monitor.erl79
-rw-r--r--src/rabbit_nodes.erl32
-rw-r--r--src/rabbit_peer_discovery.erl42
-rw-r--r--src/rabbit_peer_discovery_classic_config.erl9
-rw-r--r--src/rabbit_peer_discovery_dns.erl9
-rw-r--r--src/rabbit_plugins.erl43
-rw-r--r--src/rabbit_policy.erl100
-rw-r--r--src/rabbit_prelaunch.erl7
-rw-r--r--src/rabbit_prequeue.erl29
-rw-r--r--src/rabbit_priority_queue.erl19
-rw-r--r--src/rabbit_queue_collector.erl5
-rw-r--r--src/rabbit_queue_consumers.erl98
-rw-r--r--src/rabbit_queue_decorator.erl36
-rw-r--r--src/rabbit_queue_index.erl78
-rw-r--r--src/rabbit_queue_location_client_local.erl6
-rw-r--r--src/rabbit_queue_location_min_masters.erl5
-rw-r--r--src/rabbit_queue_location_random.erl5
-rw-r--r--src/rabbit_queue_location_validator.erl5
-rw-r--r--src/rabbit_queue_master_location_misc.erl31
-rw-r--r--src/rabbit_queue_master_locator.erl28
-rw-r--r--src/rabbit_quorum_queue.erl308
-rw-r--r--src/rabbit_reader.erl66
-rw-r--r--src/rabbit_recovery_terms.erl26
-rw-r--r--src/rabbit_restartable_sup.erl2
-rw-r--r--src/rabbit_sup.erl40
-rw-r--r--src/rabbit_table.erl53
-rw-r--r--src/rabbit_trace.erl28
-rw-r--r--src/rabbit_upgrade.erl52
-rw-r--r--src/rabbit_upgrade_functions.erl178
-rw-r--r--src/rabbit_variable_queue.erl40
-rw-r--r--src/rabbit_version.erl44
-rw-r--r--src/rabbit_vhost.erl72
-rw-r--r--src/rabbit_vm.erl12
-rw-r--r--src/supervised_lifecycle.erl2
-rw-r--r--src/tcp_listener.erl2
-rw-r--r--src/tcp_listener_sup.erl2
91 files changed, 6756 insertions, 1980 deletions
diff --git a/src/amqqueue.erl b/src/amqqueue.erl
new file mode 100644
index 0000000000..83b65cd048
--- /dev/null
+++ b/src/amqqueue.erl
@@ -0,0 +1,682 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2018-2019 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(amqqueue). %% Could become amqqueue_v2 in the future.
+
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
+
+-export([new/9,
+ new_with_version/10,
+ fields/0,
+ fields/1,
+ field_vhost/0,
+ record_version_to_use/0,
+ upgrade/1,
+ upgrade_to/2,
+ % arguments
+ get_arguments/1,
+ set_arguments/2,
+ % decorators
+ get_decorators/1,
+ set_decorators/2,
+ % exclusive_owner
+ get_exclusive_owner/1,
+ % gm_pids
+ get_gm_pids/1,
+ set_gm_pids/2,
+ get_leader/1,
+ % name (#resource)
+ get_name/1,
+ set_name/2,
+ % operator_policy
+ get_operator_policy/1,
+ set_operator_policy/2,
+ get_options/1,
+ % pid
+ get_pid/1,
+ set_pid/2,
+ % policy
+ get_policy/1,
+ set_policy/2,
+ % policy_version
+ get_policy_version/1,
+ set_policy_version/2,
+ % quorum_nodes
+ get_quorum_nodes/1,
+ set_quorum_nodes/2,
+ % recoverable_slaves
+ get_recoverable_slaves/1,
+ set_recoverable_slaves/2,
+ % slave_pids
+ get_slave_pids/1,
+ set_slave_pids/2,
+ % slave_pids_pending_shutdown
+ get_slave_pids_pending_shutdown/1,
+ set_slave_pids_pending_shutdown/2,
+ % state
+ get_state/1,
+ set_state/2,
+ % sync_slave_pids
+ get_sync_slave_pids/1,
+ set_sync_slave_pids/2,
+ get_type/1,
+ get_vhost/1,
+ is_amqqueue/1,
+ is_auto_delete/1,
+ is_durable/1,
+ is_classic/1,
+ is_quorum/1,
+ pattern_match_all/0,
+ pattern_match_on_name/1,
+ pattern_match_on_type/1,
+ reset_mirroring_and_decorators/1,
+ set_immutable/1,
+ qnode/1,
+ macros/0]).
+
+-define(record_version, amqqueue_v2).
+
+-record(amqqueue, {
+ name :: rabbit_amqqueue:name() | '_', %% immutable
+ durable :: boolean() | '_', %% immutable
+ auto_delete :: boolean() | '_', %% immutable
+ exclusive_owner = none :: pid() | none | '_', %% immutable
+ arguments = [] :: rabbit_framing:amqp_table() | '_', %% immutable
+ pid :: pid() | ra_server_id() | none | '_', %% durable (just so we
+ %% know home node)
+ slave_pids = [] :: [pid()] | none | '_', %% transient
+ sync_slave_pids = [] :: [pid()] | none| '_',%% transient
+ recoverable_slaves = [] :: [atom()] | none | '_', %% durable
+ policy :: binary() | none | undefined | '_', %% durable, implicit
+ %% update as above
+ operator_policy :: binary() | none | undefined | '_', %% durable,
+ %% implicit
+ %% update
+ %% as above
+ gm_pids = [] :: [pid()] | none | '_', %% transient
+ decorators :: [atom()] | none | undefined | '_', %% transient,
+ %% recalculated
+ %% as above
+ state = live :: atom() | none | '_', %% durable (have we crashed?)
+ policy_version = 0 :: non_neg_integer() | '_',
+ slave_pids_pending_shutdown = [] :: [pid()] | '_',
+ vhost :: rabbit_types:vhost() | undefined | '_', %% secondary index
+ options = #{} :: map() | '_',
+ type = ?amqqueue_v1_type :: atom() | '_',
+ quorum_nodes = [] :: [node()] | '_'
+ }).
+
+-type amqqueue() :: amqqueue_v1:amqqueue_v1() | amqqueue_v2().
+-type amqqueue_v2() :: #amqqueue{
+ name :: rabbit_amqqueue:name(),
+ durable :: boolean(),
+ auto_delete :: boolean(),
+ exclusive_owner :: pid() | none,
+ arguments :: rabbit_framing:amqp_table(),
+ pid :: pid() | ra_server_id() | none,
+ slave_pids :: [pid()] | none,
+ sync_slave_pids :: [pid()] | none,
+ recoverable_slaves :: [atom()] | none,
+ policy :: binary() | none | undefined,
+ operator_policy :: binary() | none | undefined,
+ gm_pids :: [pid()] | none,
+ decorators :: [atom()] | none | undefined,
+ state :: atom() | none,
+ policy_version :: non_neg_integer(),
+ slave_pids_pending_shutdown :: [pid()],
+ vhost :: rabbit_types:vhost() | undefined,
+ options :: map(),
+ type :: atom(),
+ quorum_nodes :: [node()]
+ }.
+
+-type ra_server_id() :: {Name :: atom(), Node :: node()}.
+
+-type amqqueue_pattern() :: amqqueue_v1:amqqueue_v1_pattern() |
+ amqqueue_v2_pattern().
+-type amqqueue_v2_pattern() :: #amqqueue{
+ name :: rabbit_amqqueue:name() | '_',
+ durable :: '_',
+ auto_delete :: '_',
+ exclusive_owner :: '_',
+ arguments :: '_',
+ pid :: '_',
+ slave_pids :: '_',
+ sync_slave_pids :: '_',
+ recoverable_slaves :: '_',
+ policy :: '_',
+ operator_policy :: '_',
+ gm_pids :: '_',
+ decorators :: '_',
+ state :: '_',
+ policy_version :: '_',
+ slave_pids_pending_shutdown :: '_',
+ vhost :: '_',
+ options :: '_',
+ type :: atom() | '_',
+ quorum_nodes :: '_'
+ }.
+
+-export_type([amqqueue/0,
+ amqqueue_v2/0,
+ amqqueue_pattern/0,
+ amqqueue_v2_pattern/0,
+ ra_server_id/0]).
+
+-spec new(rabbit_amqqueue:name(),
+ pid() | ra_server_id() | none,
+ boolean(),
+ boolean(),
+ pid() | none,
+ rabbit_framing:amqp_table(),
+ rabbit_types:vhost() | undefined,
+ map(),
+ atom()) -> amqqueue().
+
+new(#resource{kind = queue} = Name,
+ Pid,
+ Durable,
+ AutoDelete,
+ Owner,
+ Args,
+ VHost,
+ Options,
+ Type)
+ when (is_pid(Pid) orelse is_tuple(Pid) orelse Pid =:= none) andalso
+ is_boolean(Durable) andalso
+ is_boolean(AutoDelete) andalso
+ (is_pid(Owner) orelse Owner =:= none) andalso
+ is_list(Args) andalso
+ (is_binary(VHost) orelse VHost =:= undefined) andalso
+ is_map(Options) andalso
+ is_atom(Type) ->
+ case record_version_to_use() of
+ ?record_version ->
+ new_with_version(
+ ?record_version,
+ Name,
+ Pid,
+ Durable,
+ AutoDelete,
+ Owner,
+ Args,
+ VHost,
+ Options,
+ Type);
+ _ ->
+ amqqueue_v1:new(
+ Name,
+ Pid,
+ Durable,
+ AutoDelete,
+ Owner,
+ Args,
+ VHost,
+ Options)
+ end.
+
+-spec new_with_version
+(amqqueue_v1 | amqqueue_v2,
+ rabbit_amqqueue:name(),
+ pid() | ra_server_id() | none,
+ boolean(),
+ boolean(),
+ pid() | none,
+ rabbit_framing:amqp_table(),
+ rabbit_types:vhost() | undefined,
+ map(),
+ atom()) -> amqqueue().
+
+new_with_version(?record_version,
+ #resource{kind = queue} = Name,
+ Pid,
+ Durable,
+ AutoDelete,
+ Owner,
+ Args,
+ VHost,
+ Options,
+ Type)
+ when (is_pid(Pid) orelse is_tuple(Pid) orelse Pid =:= none) andalso
+ is_boolean(Durable) andalso
+ is_boolean(AutoDelete) andalso
+ (is_pid(Owner) orelse Owner =:= none) andalso
+ is_list(Args) andalso
+ (is_binary(VHost) orelse VHost =:= undefined) andalso
+ is_map(Options) andalso
+ is_atom(Type) ->
+ #amqqueue{name = Name,
+ durable = Durable,
+ auto_delete = AutoDelete,
+ arguments = Args,
+ exclusive_owner = Owner,
+ pid = Pid,
+ vhost = VHost,
+ options = Options,
+ type = Type};
+new_with_version(Version,
+ Name,
+ Pid,
+ Durable,
+ AutoDelete,
+ Owner,
+ Args,
+ VHost,
+ Options,
+ ?amqqueue_v1_type) ->
+ amqqueue_v1:new_with_version(
+ Version,
+ Name,
+ Pid,
+ Durable,
+ AutoDelete,
+ Owner,
+ Args,
+ VHost,
+ Options).
+
+-spec is_amqqueue(any()) -> boolean().
+
+is_amqqueue(#amqqueue{}) -> true;
+is_amqqueue(Queue) -> amqqueue_v1:is_amqqueue(Queue).
+
+-spec record_version_to_use() -> amqqueue_v1 | amqqueue_v2.
+
+record_version_to_use() ->
+ case rabbit_feature_flags:is_enabled(quorum_queue) of
+ true -> ?record_version;
+ false -> amqqueue_v1:record_version_to_use()
+ end.
+
+-spec upgrade(amqqueue()) -> amqqueue().
+
+upgrade(#amqqueue{} = Queue) -> Queue;
+upgrade(OldQueue) -> upgrade_to(record_version_to_use(), OldQueue).
+
+-spec upgrade_to
+(amqqueue_v2, amqqueue()) -> amqqueue_v2();
+(amqqueue_v1, amqqueue_v1:amqqueue_v1()) -> amqqueue_v1:amqqueue_v1().
+
+upgrade_to(?record_version, #amqqueue{} = Queue) ->
+ Queue;
+upgrade_to(?record_version, OldQueue) ->
+ Fields = erlang:tuple_to_list(OldQueue) ++ [?amqqueue_v1_type,
+ undefined],
+ #amqqueue{} = erlang:list_to_tuple(Fields);
+upgrade_to(Version, OldQueue) ->
+ amqqueue_v1:upgrade_to(Version, OldQueue).
+
+% arguments
+
+-spec get_arguments(amqqueue()) -> rabbit_framing:amqp_table().
+
+get_arguments(#amqqueue{arguments = Args}) ->
+ Args;
+get_arguments(Queue) ->
+ amqqueue_v1:get_arguments(Queue).
+
+-spec set_arguments(amqqueue(), rabbit_framing:amqp_table()) -> amqqueue().
+
+set_arguments(#amqqueue{} = Queue, Args) ->
+ Queue#amqqueue{arguments = Args};
+set_arguments(Queue, Args) ->
+ amqqueue_v1:set_arguments(Queue, Args).
+
+% decorators
+
+-spec get_decorators(amqqueue()) -> [atom()] | none | undefined.
+
+get_decorators(#amqqueue{decorators = Decorators}) ->
+ Decorators;
+get_decorators(Queue) ->
+ amqqueue_v1:get_decorators(Queue).
+
+-spec set_decorators(amqqueue(), [atom()] | none | undefined) -> amqqueue().
+
+set_decorators(#amqqueue{} = Queue, Decorators) ->
+ Queue#amqqueue{decorators = Decorators};
+set_decorators(Queue, Decorators) ->
+ amqqueue_v1:set_decorators(Queue, Decorators).
+
+-spec get_exclusive_owner(amqqueue()) -> pid() | none.
+
+get_exclusive_owner(#amqqueue{exclusive_owner = Owner}) ->
+ Owner;
+get_exclusive_owner(Queue) ->
+ amqqueue_v1:get_exclusive_owner(Queue).
+
+-spec get_gm_pids(amqqueue()) -> [pid()] | none.
+
+get_gm_pids(#amqqueue{gm_pids = GMPids}) ->
+ GMPids;
+get_gm_pids(Queue) ->
+ amqqueue_v1:get_gm_pids(Queue).
+
+-spec set_gm_pids(amqqueue(), [pid()] | none) -> amqqueue().
+
+set_gm_pids(#amqqueue{} = Queue, GMPids) ->
+ Queue#amqqueue{gm_pids = GMPids};
+set_gm_pids(Queue, GMPids) ->
+ amqqueue_v1:set_gm_pids(Queue, GMPids).
+
+-spec get_leader(amqqueue_v2()) -> node().
+
+get_leader(#amqqueue{type = quorum, pid = {_, Leader}}) -> Leader.
+
+% operator_policy
+
+-spec get_operator_policy(amqqueue()) -> binary() | none | undefined.
+
+get_operator_policy(#amqqueue{operator_policy = OpPolicy}) -> OpPolicy;
+get_operator_policy(Queue) -> amqqueue_v1:get_operator_policy(Queue).
+
+-spec set_operator_policy(amqqueue(), binary() | none | undefined) ->
+ amqqueue().
+
+set_operator_policy(#amqqueue{} = Queue, Policy) ->
+ Queue#amqqueue{operator_policy = Policy};
+set_operator_policy(Queue, Policy) ->
+ amqqueue_v1:set_operator_policy(Queue, Policy).
+
+% name
+
+-spec get_name(amqqueue()) -> rabbit_amqqueue:name().
+
+get_name(#amqqueue{name = Name}) -> Name;
+get_name(Queue) -> amqqueue_v1:get_name(Queue).
+
+-spec set_name(amqqueue(), rabbit_amqqueue:name()) -> amqqueue().
+
+set_name(#amqqueue{} = Queue, Name) ->
+ Queue#amqqueue{name = Name};
+set_name(Queue, Name) ->
+ amqqueue_v1:set_name(Queue, Name).
+
+-spec get_options(amqqueue()) -> map().
+
+get_options(#amqqueue{options = Options}) -> Options;
+get_options(Queue) -> amqqueue_v1:get_options(Queue).
+
+% pid
+
+-spec get_pid
+(amqqueue_v2()) -> pid() | ra_server_id() | none;
+(amqqueue_v1:amqqueue_v1()) -> pid() | none.
+
+get_pid(#amqqueue{pid = Pid}) -> Pid;
+get_pid(Queue) -> amqqueue_v1:get_pid(Queue).
+
+-spec set_pid
+(amqqueue_v2(), pid() | ra_server_id() | none) -> amqqueue_v2();
+(amqqueue_v1:amqqueue_v1(), pid() | none) -> amqqueue_v1:amqqueue_v1().
+
+set_pid(#amqqueue{} = Queue, Pid) ->
+ Queue#amqqueue{pid = Pid};
+set_pid(Queue, Pid) ->
+ amqqueue_v1:set_pid(Queue, Pid).
+
+% policy
+
+-spec get_policy(amqqueue()) -> binary() | none | undefined.
+
+get_policy(#amqqueue{policy = Policy}) -> Policy;
+get_policy(Queue) -> amqqueue_v1:get_policy(Queue).
+
+-spec set_policy(amqqueue(), binary() | none | undefined) -> amqqueue().
+
+set_policy(#amqqueue{} = Queue, Policy) ->
+ Queue#amqqueue{policy = Policy};
+set_policy(Queue, Policy) ->
+ amqqueue_v1:set_policy(Queue, Policy).
+
+% policy_version
+
+-spec get_policy_version(amqqueue()) -> non_neg_integer().
+
+get_policy_version(#amqqueue{policy_version = PV}) ->
+ PV;
+get_policy_version(Queue) ->
+ amqqueue_v1:get_policy_version(Queue).
+
+-spec set_policy_version(amqqueue(), non_neg_integer()) -> amqqueue().
+
+set_policy_version(#amqqueue{} = Queue, PV) ->
+ Queue#amqqueue{policy_version = PV};
+set_policy_version(Queue, PV) ->
+ amqqueue_v1:set_policy_version(Queue, PV).
+
+% recoverable_slaves
+
+-spec get_recoverable_slaves(amqqueue()) -> [atom()] | none.
+
+get_recoverable_slaves(#amqqueue{recoverable_slaves = Slaves}) ->
+ Slaves;
+get_recoverable_slaves(Queue) ->
+ amqqueue_v1:get_recoverable_slaves(Queue).
+
+-spec set_recoverable_slaves(amqqueue(), [atom()] | none) -> amqqueue().
+
+set_recoverable_slaves(#amqqueue{} = Queue, Slaves) ->
+ Queue#amqqueue{recoverable_slaves = Slaves};
+set_recoverable_slaves(Queue, Slaves) ->
+ amqqueue_v1:set_recoverable_slaves(Queue, Slaves).
+
+% quorum_nodes (new in v2)
+
+-spec get_quorum_nodes(amqqueue()) -> [node()].
+
+get_quorum_nodes(#amqqueue{quorum_nodes = Nodes}) -> Nodes;
+get_quorum_nodes(_) -> [].
+
+-spec set_quorum_nodes(amqqueue(), [node()]) -> amqqueue().
+
+set_quorum_nodes(#amqqueue{} = Queue, Nodes) ->
+ Queue#amqqueue{quorum_nodes = Nodes};
+set_quorum_nodes(Queue, _Nodes) ->
+ Queue.
+
+% slave_pids
+
+-spec get_slave_pids(amqqueue()) -> [pid()] | none.
+
+get_slave_pids(#amqqueue{slave_pids = Slaves}) ->
+ Slaves;
+get_slave_pids(Queue) ->
+ amqqueue_v1:get_slave_pids(Queue).
+
+-spec set_slave_pids(amqqueue(), [pid()] | none) -> amqqueue().
+
+set_slave_pids(#amqqueue{} = Queue, SlavePids) ->
+ Queue#amqqueue{slave_pids = SlavePids};
+set_slave_pids(Queue, SlavePids) ->
+ amqqueue_v1:set_slave_pids(Queue, SlavePids).
+
+% slave_pids_pending_shutdown
+
+-spec get_slave_pids_pending_shutdown(amqqueue()) -> [pid()].
+
+get_slave_pids_pending_shutdown(#amqqueue{slave_pids_pending_shutdown = Slaves}) ->
+ Slaves;
+get_slave_pids_pending_shutdown(Queue) ->
+ amqqueue_v1:get_slave_pids_pending_shutdown(Queue).
+
+-spec set_slave_pids_pending_shutdown(amqqueue(), [pid()]) -> amqqueue().
+
+set_slave_pids_pending_shutdown(#amqqueue{} = Queue, SlavePids) ->
+ Queue#amqqueue{slave_pids_pending_shutdown = SlavePids};
+set_slave_pids_pending_shutdown(Queue, SlavePids) ->
+ amqqueue_v1:set_slave_pids_pending_shutdown(Queue, SlavePids).
+
+% state
+
+-spec get_state(amqqueue()) -> atom() | none.
+
+get_state(#amqqueue{state = State}) -> State;
+get_state(Queue) -> amqqueue_v1:get_state(Queue).
+
+-spec set_state(amqqueue(), atom() | none) -> amqqueue().
+
+set_state(#amqqueue{} = Queue, State) ->
+ Queue#amqqueue{state = State};
+set_state(Queue, State) ->
+ amqqueue_v1:set_state(Queue, State).
+
+% sync_slave_pids
+
+-spec get_sync_slave_pids(amqqueue()) -> [pid()] | none.
+
+get_sync_slave_pids(#amqqueue{sync_slave_pids = Pids}) ->
+ Pids;
+get_sync_slave_pids(Queue) ->
+ amqqueue_v1:get_sync_slave_pids(Queue).
+
+-spec set_sync_slave_pids(amqqueue(), [pid()] | none) -> amqqueue().
+
+set_sync_slave_pids(#amqqueue{} = Queue, Pids) ->
+ Queue#amqqueue{sync_slave_pids = Pids};
+set_sync_slave_pids(Queue, Pids) ->
+ amqqueue_v1:set_sync_slave_pids(Queue, Pids).
+
+%% New in v2.
+
+-spec get_type(amqqueue()) -> atom().
+
+get_type(#amqqueue{type = Type}) -> Type;
+get_type(Queue) when ?is_amqqueue(Queue) -> ?amqqueue_v1_type.
+
+-spec get_vhost(amqqueue()) -> rabbit_types:vhost() | undefined.
+
+get_vhost(#amqqueue{vhost = VHost}) -> VHost;
+get_vhost(Queue) -> amqqueue_v1:get_vhost(Queue).
+
+-spec is_auto_delete(amqqueue()) -> boolean().
+
+is_auto_delete(#amqqueue{auto_delete = AutoDelete}) ->
+ AutoDelete;
+is_auto_delete(Queue) ->
+ amqqueue_v1:is_auto_delete(Queue).
+
+-spec is_durable(amqqueue()) -> boolean().
+
+is_durable(#amqqueue{durable = Durable}) -> Durable;
+is_durable(Queue) -> amqqueue_v1:is_durable(Queue).
+
+-spec is_classic(amqqueue()) -> boolean().
+
+is_classic(Queue) ->
+ get_type(Queue) =:= ?amqqueue_v1_type.
+
+-spec is_quorum(amqqueue()) -> boolean().
+
+is_quorum(Queue) ->
+ get_type(Queue) =:= quorum.
+
+fields() ->
+ case record_version_to_use() of
+ ?record_version -> fields(?record_version);
+ _ -> amqqueue_v1:fields()
+ end.
+
+fields(?record_version) -> record_info(fields, amqqueue);
+fields(Version) -> amqqueue_v1:fields(Version).
+
+field_vhost() ->
+ case record_version_to_use() of
+ ?record_version -> #amqqueue.vhost;
+ _ -> amqqueue_v1:field_vhost()
+ end.
+
+-spec pattern_match_all() -> amqqueue_pattern().
+
+pattern_match_all() ->
+ case record_version_to_use() of
+ ?record_version -> #amqqueue{_ = '_'};
+ _ -> amqqueue_v1:pattern_match_all()
+ end.
+
+-spec pattern_match_on_name(rabbit_amqqueue:name()) -> amqqueue_pattern().
+
+pattern_match_on_name(Name) ->
+ case record_version_to_use() of
+ ?record_version -> #amqqueue{name = Name, _ = '_'};
+ _ -> amqqueue_v1:pattern_match_on_name(Name)
+ end.
+
+-spec pattern_match_on_type(atom()) -> amqqueue_pattern().
+
+pattern_match_on_type(Type) ->
+ case record_version_to_use() of
+ ?record_version -> #amqqueue{type = Type, _ = '_'};
+ _ when Type =:= classic -> amqqueue_v1:pattern_match_all();
+ %% FIXME: We try a pattern which should never match when the
+ %% `quorum_queue` feature flag is not enabled yet. Is there
+ %% a better solution?
+ _ -> amqqueue_v1:pattern_match_on_name(
+ rabbit_misc:r(<<0>>, queue, <<0>>))
+ end.
+
+-spec reset_mirroring_and_decorators(amqqueue()) -> amqqueue().
+
+reset_mirroring_and_decorators(#amqqueue{} = Queue) ->
+ Queue#amqqueue{slave_pids = [],
+ sync_slave_pids = [],
+ gm_pids = [],
+ decorators = undefined};
+reset_mirroring_and_decorators(Queue) ->
+ amqqueue_v1:reset_mirroring_and_decorators(Queue).
+
+-spec set_immutable(amqqueue()) -> amqqueue().
+
+set_immutable(#amqqueue{} = Queue) ->
+ Queue#amqqueue{pid = none,
+ slave_pids = [],
+ sync_slave_pids = none,
+ recoverable_slaves = none,
+ gm_pids = none,
+ policy = none,
+ decorators = none,
+ state = none};
+set_immutable(Queue) ->
+ amqqueue_v1:set_immutable(Queue).
+
+-spec qnode(amqqueue() | pid() | ra_server_id()) -> node().
+
+qnode(Queue) when ?is_amqqueue(Queue) ->
+ QPid = get_pid(Queue),
+ qnode(QPid);
+qnode(QPid) when is_pid(QPid) ->
+ node(QPid);
+qnode({_, Node}) ->
+ Node.
+
+% private
+
+macros() ->
+ io:format(
+ "-define(is_~s(Q), is_record(Q, amqqueue, ~b)).~n~n",
+ [?record_version, record_info(size, amqqueue)]),
+ %% The field number starts at 2 because the first element is the
+ %% record name.
+ macros(record_info(fields, amqqueue), 2).
+
+macros([Field | Rest], I) ->
+ io:format(
+ "-define(~s_field_~s(Q), element(~b, Q)).~n",
+ [?record_version, Field, I]),
+ macros(Rest, I + 1);
+macros([], _) ->
+ ok.
diff --git a/src/amqqueue_v1.erl b/src/amqqueue_v1.erl
new file mode 100644
index 0000000000..0b35418d1d
--- /dev/null
+++ b/src/amqqueue_v1.erl
@@ -0,0 +1,403 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2018-2019 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(amqqueue_v1).
+
+-include_lib("rabbit_common/include/resource.hrl").
+
+-export([new/8,
+ new_with_version/9,
+ fields/0,
+ fields/1,
+ field_vhost/0,
+ record_version_to_use/0,
+ upgrade/1,
+ upgrade_to/2,
+ % arguments
+ get_arguments/1,
+ set_arguments/2,
+ % decorators
+ get_decorators/1,
+ set_decorators/2,
+ % exclusive_owner
+ get_exclusive_owner/1,
+ % gm_pids
+ get_gm_pids/1,
+ set_gm_pids/2,
+ % name
+ get_name/1,
+ set_name/2,
+ % operator_policy
+ get_operator_policy/1,
+ set_operator_policy/2,
+ get_options/1,
+ % pid
+ get_pid/1,
+ set_pid/2,
+ % policy
+ get_policy/1,
+ set_policy/2,
+ % policy_version
+ get_policy_version/1,
+ set_policy_version/2,
+ % recoverable_slaves
+ get_recoverable_slaves/1,
+ set_recoverable_slaves/2,
+ % slave_pids
+ get_slave_pids/1,
+ set_slave_pids/2,
+ % slave_pids_pending_shutdown
+ get_slave_pids_pending_shutdown/1,
+ set_slave_pids_pending_shutdown/2,
+ % state
+ get_state/1,
+ set_state/2,
+ % sync_slave_pids
+ get_sync_slave_pids/1,
+ set_sync_slave_pids/2,
+ get_vhost/1,
+ is_amqqueue/1,
+ is_auto_delete/1,
+ is_durable/1,
+ pattern_match_all/0,
+ pattern_match_on_name/1,
+ reset_mirroring_and_decorators/1,
+ set_immutable/1,
+ macros/0]).
+
+-define(record_version, ?MODULE).
+
+-record(amqqueue, {
+ name :: rabbit_amqqueue:name() | '_', %% immutable
+ durable :: boolean() | '_', %% immutable
+ auto_delete :: boolean() | '_', %% immutable
+ exclusive_owner = none :: pid() | none | '_', %% immutable
+ arguments = [] :: rabbit_framing:amqp_table() | '_', %% immutable
+ pid :: pid() | none | '_', %% durable (just so we
+ %% know home node)
+ slave_pids = [] :: [pid()] | none | '_', %% transient
+ sync_slave_pids = [] :: [pid()] | none| '_',%% transient
+ recoverable_slaves = [] :: [atom()] | none | '_', %% durable
+ policy :: binary() | none | undefined | '_', %% durable, implicit
+ %% update as above
+ operator_policy :: binary() | none | undefined | '_', %% durable,
+ %% implicit
+ %% update
+ %% as above
+ gm_pids = [] :: [pid()] | none | '_', %% transient
+ decorators :: [atom()] | none | undefined | '_', %% transient,
+ %% recalculated
+ %% as above
+ state = live :: atom() | none | '_', %% durable (have we crashed?)
+ policy_version = 0 :: non_neg_integer() | '_',
+ slave_pids_pending_shutdown = [] :: [pid()] | '_',
+ vhost :: rabbit_types:vhost() | undefined | '_', %% secondary index
+ options = #{} :: map() | '_'
+ }).
+
+-type amqqueue() :: amqqueue_v1().
+-type amqqueue_v1() :: #amqqueue{
+ name :: rabbit_amqqueue:name(),
+ durable :: boolean(),
+ auto_delete :: boolean(),
+ exclusive_owner :: pid() | none,
+ arguments :: rabbit_framing:amqp_table(),
+ pid :: pid() | none,
+ slave_pids :: [pid()] | none,
+ sync_slave_pids :: [pid()] | none,
+ recoverable_slaves :: [atom()] | none,
+ policy :: binary() | none | undefined,
+ operator_policy :: binary() | none | undefined,
+ gm_pids :: [pid()] | none,
+ decorators :: [atom()] | none | undefined,
+ state :: atom() | none,
+ policy_version :: non_neg_integer(),
+ slave_pids_pending_shutdown :: [pid()],
+ vhost :: rabbit_types:vhost() | undefined,
+ options :: map()
+ }.
+
+-type amqqueue_pattern() :: amqqueue_v1_pattern().
+-type amqqueue_v1_pattern() :: #amqqueue{
+ name :: rabbit_amqqueue:name() | '_',
+ durable :: '_',
+ auto_delete :: '_',
+ exclusive_owner :: '_',
+ arguments :: '_',
+ pid :: '_',
+ slave_pids :: '_',
+ sync_slave_pids :: '_',
+ recoverable_slaves :: '_',
+ policy :: '_',
+ operator_policy :: '_',
+ gm_pids :: '_',
+ decorators :: '_',
+ state :: '_',
+ policy_version :: '_',
+ slave_pids_pending_shutdown :: '_',
+ vhost :: '_',
+ options :: '_'
+ }.
+
+-export_type([amqqueue/0,
+ amqqueue_v1/0,
+ amqqueue_pattern/0,
+ amqqueue_v1_pattern/0]).
+
+-spec new(rabbit_amqqueue:name(),
+ pid() | none,
+ boolean(),
+ boolean(),
+ pid() | none,
+ rabbit_framing:amqp_table(),
+ rabbit_types:vhost() | undefined,
+ map()) -> amqqueue().
+
+new(#resource{kind = queue} = Name,
+ Pid,
+ Durable,
+ AutoDelete,
+ Owner,
+ Args,
+ VHost,
+ Options)
+ when (is_pid(Pid) orelse Pid =:= none) andalso
+ is_boolean(Durable) andalso
+ is_boolean(AutoDelete) andalso
+ (is_pid(Owner) orelse Owner =:= none) andalso
+ is_list(Args) andalso
+ (is_binary(VHost) orelse VHost =:= undefined) andalso
+ is_map(Options) ->
+ new_with_version(
+ ?record_version,
+ Name,
+ Pid,
+ Durable,
+ AutoDelete,
+ Owner,
+ Args,
+ VHost,
+ Options).
+
+-spec new_with_version(amqqueue_v1,
+ rabbit_amqqueue:name(),
+ pid() | none,
+ boolean(),
+ boolean(),
+ pid() | none,
+ rabbit_framing:amqp_table(),
+ rabbit_types:vhost() | undefined,
+ map()) -> amqqueue().
+
+new_with_version(?record_version,
+ #resource{kind = queue} = Name,
+ Pid,
+ Durable,
+ AutoDelete,
+ Owner,
+ Args,
+ VHost,
+ Options)
+ when (is_pid(Pid) orelse Pid =:= none) andalso
+ is_boolean(Durable) andalso
+ is_boolean(AutoDelete) andalso
+ (is_pid(Owner) orelse Owner =:= none) andalso
+ is_list(Args) andalso
+ (is_binary(VHost) orelse VHost =:= undefined) andalso
+ is_map(Options) ->
+ #amqqueue{name = Name,
+ durable = Durable,
+ auto_delete = AutoDelete,
+ arguments = Args,
+ exclusive_owner = Owner,
+ pid = Pid,
+ vhost = VHost,
+ options = Options}.
+
+-spec is_amqqueue(any()) -> boolean().
+
+is_amqqueue(#amqqueue{}) -> true;
+is_amqqueue(_) -> false.
+
+-spec record_version_to_use() -> amqqueue_v1.
+
+record_version_to_use() ->
+ ?record_version.
+
+-spec upgrade(amqqueue()) -> amqqueue().
+
+upgrade(#amqqueue{} = Queue) ->
+ Queue.
+
+-spec upgrade_to(amqqueue_v1, amqqueue()) -> amqqueue().
+
+upgrade_to(?record_version, #amqqueue{} = Queue) ->
+ Queue.
+
+% arguments
+
+-spec get_arguments(amqqueue()) -> rabbit_framing:amqp_table().
+
+get_arguments(#amqqueue{arguments = Args}) -> Args.
+
+-spec set_arguments(amqqueue(), rabbit_framing:amqp_table()) -> amqqueue().
+
+set_arguments(#amqqueue{} = Queue, Args) ->
+ Queue#amqqueue{arguments = Args}.
+
+% decorators
+
+get_decorators(#amqqueue{decorators = Decorators}) -> Decorators.
+
+set_decorators(#amqqueue{} = Queue, Decorators) ->
+ Queue#amqqueue{decorators = Decorators}.
+
+get_exclusive_owner(#amqqueue{exclusive_owner = Owner}) -> Owner.
+
+% gm_pids
+
+get_gm_pids(#amqqueue{gm_pids = GMPids}) -> GMPids.
+
+set_gm_pids(#amqqueue{} = Queue, GMPids) ->
+ Queue#amqqueue{gm_pids = GMPids}.
+
+% name
+
+get_name(#amqqueue{name = Name}) -> Name.
+
+set_name(#amqqueue{} = Queue, Name) ->
+ Queue#amqqueue{name = Name}.
+
+% operator_policy
+
+get_operator_policy(#amqqueue{operator_policy = OpPolicy}) -> OpPolicy.
+
+set_operator_policy(#amqqueue{} = Queue, OpPolicy) ->
+ Queue#amqqueue{operator_policy = OpPolicy}.
+
+get_options(#amqqueue{options = Options}) -> Options.
+
+% pid
+
+get_pid(#amqqueue{pid = Pid}) -> Pid.
+
+set_pid(#amqqueue{} = Queue, Pid) ->
+ Queue#amqqueue{pid = Pid}.
+
+% policy
+
+get_policy(#amqqueue{policy = Policy}) -> Policy.
+
+set_policy(#amqqueue{} = Queue, Policy) ->
+ Queue#amqqueue{policy = Policy}.
+
+% policy_version
+
+get_policy_version(#amqqueue{policy_version = PV}) ->
+ PV.
+
+set_policy_version(#amqqueue{} = Queue, PV) ->
+ Queue#amqqueue{policy_version = PV}.
+
+% recoverable_slaves
+
+get_recoverable_slaves(#amqqueue{recoverable_slaves = Slaves}) ->
+ Slaves.
+
+set_recoverable_slaves(#amqqueue{} = Queue, Slaves) ->
+ Queue#amqqueue{recoverable_slaves = Slaves}.
+
+% slave_pids
+
+get_slave_pids(#amqqueue{slave_pids = Slaves}) ->
+ Slaves.
+
+set_slave_pids(#amqqueue{} = Queue, SlavePids) ->
+ Queue#amqqueue{slave_pids = SlavePids}.
+
+% slave_pids_pending_shutdown
+
+get_slave_pids_pending_shutdown(#amqqueue{slave_pids_pending_shutdown = Slaves}) ->
+ Slaves.
+
+set_slave_pids_pending_shutdown(#amqqueue{} = Queue, SlavePids) ->
+ Queue#amqqueue{slave_pids_pending_shutdown = SlavePids}.
+
+% state
+
+get_state(#amqqueue{state = State}) -> State.
+
+set_state(#amqqueue{} = Queue, State) -> Queue#amqqueue{state = State}.
+
+% sync_slave_pids
+
+get_sync_slave_pids(#amqqueue{sync_slave_pids = Pids}) -> Pids.
+
+set_sync_slave_pids(#amqqueue{} = Queue, Pids) ->
+ Queue#amqqueue{sync_slave_pids = Pids}.
+
+get_vhost(#amqqueue{vhost = VHost}) -> VHost.
+
+is_auto_delete(#amqqueue{auto_delete = AutoDelete}) -> AutoDelete.
+
+is_durable(#amqqueue{durable = Durable}) -> Durable.
+
+fields() -> fields(?record_version).
+
+fields(?record_version) -> record_info(fields, amqqueue).
+
+field_vhost() -> #amqqueue.vhost.
+
+-spec pattern_match_all() -> amqqueue_pattern().
+
+pattern_match_all() -> #amqqueue{_ = '_'}.
+
+-spec pattern_match_on_name(rabbit_amqqueue:name()) ->
+ amqqueue_pattern().
+
+pattern_match_on_name(Name) -> #amqqueue{name = Name, _ = '_'}.
+
+reset_mirroring_and_decorators(#amqqueue{} = Queue) ->
+ Queue#amqqueue{slave_pids = [],
+ sync_slave_pids = [],
+ gm_pids = [],
+ decorators = undefined}.
+
+set_immutable(#amqqueue{} = Queue) ->
+ Queue#amqqueue{pid = none,
+ slave_pids = none,
+ sync_slave_pids = none,
+ recoverable_slaves = none,
+ gm_pids = none,
+ policy = none,
+ decorators = none,
+ state = none}.
+
+macros() ->
+ io:format(
+ "-define(is_~s(Q), is_record(Q, amqqueue, ~b)).~n~n",
+ [?record_version, record_info(size, amqqueue)]),
+ %% The field number starts at 2 because the first element is the
+ %% record name.
+ macros(record_info(fields, amqqueue), 2).
+
+macros([Field | Rest], I) ->
+ io:format(
+ "-define(~s_field_~s(Q), element(~b, Q)).~n",
+ [?record_version, Field, I]),
+ macros(Rest, I + 1);
+macros([], _) ->
+ ok.
diff --git a/src/background_gc.erl b/src/background_gc.erl
index 43c109ee2a..7aea28c1f4 100644
--- a/src/background_gc.erl
+++ b/src/background_gc.erl
@@ -32,14 +32,12 @@
%%----------------------------------------------------------------------------
-spec start_link() -> {'ok', pid()} | {'error', any()}.
--spec run() -> 'ok'.
--spec gc() -> 'ok'.
-
-%%----------------------------------------------------------------------------
start_link() -> gen_server2:start_link({local, ?MODULE}, ?MODULE, [],
[{timeout, infinity}]).
+-spec run() -> 'ok'.
+
run() -> gen_server2:cast(?MODULE, run).
%%----------------------------------------------------------------------------
@@ -73,6 +71,8 @@ interval_gc(State = #state{last_interval = LastInterval}) ->
erlang:send_after(Interval, self(), run),
State#state{last_interval = Interval}.
+-spec gc() -> 'ok'.
+
gc() ->
Enabled = rabbit_misc:get_env(rabbit, background_gc_enabled, false),
case Enabled of
diff --git a/src/dtree.erl b/src/dtree.erl
index 08ddd22532..fd2188de29 100644
--- a/src/dtree.erl
+++ b/src/dtree.erl
@@ -46,24 +46,17 @@
-type val() :: any().
-type kv() :: {pk(), val()}.
--spec empty() -> ?MODULE().
--spec insert(pk(), [sk()], val(), ?MODULE()) -> ?MODULE().
--spec take([pk()], sk(), ?MODULE()) -> {[kv()], ?MODULE()}.
--spec take(sk(), ?MODULE()) -> {[kv()], ?MODULE()}.
--spec take_one(pk(), ?MODULE()) -> {[{pk(), val()}], ?MODULE()}.
--spec take_all(sk(), ?MODULE()) -> {[kv()], ?MODULE()}.
--spec drop(pk(), ?MODULE()) -> ?MODULE().
--spec is_defined(sk(), ?MODULE()) -> boolean().
--spec is_empty(?MODULE()) -> boolean().
--spec smallest(?MODULE()) -> kv().
--spec size(?MODULE()) -> non_neg_integer().
-
%%----------------------------------------------------------------------------
+-spec empty() -> ?MODULE().
+
empty() -> {gb_trees:empty(), gb_trees:empty()}.
%% Insert an entry. Fails if there already is an entry with the given
%% primary key.
+
+-spec insert(pk(), [sk()], val(), ?MODULE()) -> ?MODULE().
+
insert(PK, [], V, {P, S}) ->
%% dummy insert to force error if PK exists
_ = gb_trees:insert(PK, {gb_sets:empty(), V}, P),
@@ -84,6 +77,9 @@ insert(PK, SKs, V, {P, S}) ->
%% that were dropped as the result (i.e. due to their secondary key
%% set becoming empty). It is ok for the given primary keys and/or
%% secondary key to not exist.
+
+-spec take([pk()], sk(), ?MODULE()) -> {[kv()], ?MODULE()}.
+
take(PKs, SK, {P, S}) ->
case gb_trees:lookup(SK, S) of
none -> {[], {P, S}};
@@ -101,6 +97,9 @@ take(PKs, SK, {P, S}) ->
%% primary-key/value pairs of any entries that were dropped as the
%% result (i.e. due to their secondary key set becoming empty). It is
%% ok for the given secondary key to not exist.
+
+-spec take(sk(), ?MODULE()) -> {[kv()], ?MODULE()}.
+
take(SK, {P, S}) ->
case gb_trees:lookup(SK, S) of
none -> {[], {P, S}};
@@ -111,6 +110,9 @@ take(SK, {P, S}) ->
%% Drop an entry with the primary key and clears secondary keys for this key,
%% returning a list with a key-value pair as a result.
%% If the primary key does not exist, returns an empty list.
+
+-spec take_one(pk(), ?MODULE()) -> {[{pk(), val()}], ?MODULE()}.
+
take_one(PK, {P, S}) ->
case gb_trees:lookup(PK, P) of
{value, {SKS, Value}} ->
@@ -131,6 +133,9 @@ take_one(PK, {P, S}) ->
%% Drop all entries which contain the given secondary key, returning
%% the primary-key/value pairs of these entries. It is ok for the
%% given secondary key to not exist.
+
+-spec take_all(sk(), ?MODULE()) -> {[kv()], ?MODULE()}.
+
take_all(SK, {P, S}) ->
case gb_trees:lookup(SK, S) of
none -> {[], {P, S}};
@@ -139,6 +144,9 @@ take_all(SK, {P, S}) ->
end.
%% Drop all entries for the given primary key (which does not have to exist).
+
+-spec drop(pk(), ?MODULE()) -> ?MODULE().
+
drop(PK, {P, S}) ->
case gb_trees:lookup(PK, P) of
none -> {P, S};
@@ -146,13 +154,21 @@ drop(PK, {P, S}) ->
prune(SKS, gb_sets:singleton(PK), S)}
end.
+-spec is_defined(sk(), ?MODULE()) -> boolean().
+
is_defined(SK, {_P, S}) -> gb_trees:is_defined(SK, S).
+-spec is_empty(?MODULE()) -> boolean().
+
is_empty({P, _S}) -> gb_trees:is_empty(P).
+-spec smallest(?MODULE()) -> kv().
+
smallest({P, _S}) -> {K, {_SKS, V}} = gb_trees:smallest(P),
{K, V}.
+-spec size(?MODULE()) -> non_neg_integer().
+
size({P, _S}) -> gb_trees:size(P).
%%----------------------------------------------------------------------------
diff --git a/src/gatherer.erl b/src/gatherer.erl
index a8b55892c1..1625468a52 100644
--- a/src/gatherer.erl
+++ b/src/gatherer.erl
@@ -39,16 +39,6 @@
%%----------------------------------------------------------------------------
--spec start_link() -> rabbit_types:ok_pid_or_error().
--spec stop(pid()) -> 'ok'.
--spec fork(pid()) -> 'ok'.
--spec finish(pid()) -> 'ok'.
--spec in(pid(), any()) -> 'ok'.
--spec sync_in(pid(), any()) -> 'ok'.
--spec out(pid()) -> {'value', any()} | 'empty'.
-
-%%----------------------------------------------------------------------------
-
-define(HIBERNATE_AFTER_MIN, 1000).
-define(DESIRED_HIBERNATE, 10000).
@@ -58,24 +48,38 @@
%%----------------------------------------------------------------------------
+-spec start_link() -> rabbit_types:ok_pid_or_error().
+
start_link() ->
gen_server2:start_link(?MODULE, [], [{timeout, infinity}]).
+-spec stop(pid()) -> 'ok'.
+
stop(Pid) ->
gen_server2:call(Pid, stop, infinity).
+-spec fork(pid()) -> 'ok'.
+
fork(Pid) ->
gen_server2:call(Pid, fork, infinity).
+-spec finish(pid()) -> 'ok'.
+
finish(Pid) ->
gen_server2:cast(Pid, finish).
+-spec in(pid(), any()) -> 'ok'.
+
in(Pid, Value) ->
gen_server2:cast(Pid, {in, Value}).
+-spec sync_in(pid(), any()) -> 'ok'.
+
sync_in(Pid, Value) ->
gen_server2:call(Pid, {in, Value}, infinity).
+-spec out(pid()) -> {'value', any()} | 'empty'.
+
out(Pid) ->
gen_server2:call(Pid, out, infinity).
diff --git a/src/gm.erl b/src/gm.erl
index 427fa78f4e..02ee76cd60 100644
--- a/src/gm.erl
+++ b/src/gm.erl
@@ -436,16 +436,6 @@
-type group_name() :: any().
-type txn_fun() :: fun((fun(() -> any())) -> any()).
--spec create_tables() -> 'ok' | {'aborted', any()}.
--spec start_link(group_name(), atom(), any(), txn_fun()) ->
- rabbit_types:ok_pid_or_error().
--spec leave(pid()) -> 'ok'.
--spec broadcast(pid(), any()) -> 'ok'.
--spec confirmed_broadcast(pid(), any()) -> 'ok'.
--spec info(pid()) -> rabbit_types:infos().
--spec validate_members(pid(), [pid()]) -> 'ok'.
--spec forget_group(group_name()) -> 'ok'.
-
%% The joined, members_changed and handle_msg callbacks can all return
%% any of the following terms:
%%
@@ -490,6 +480,8 @@
-callback handle_terminate(Args :: term(), Reason :: term()) ->
ok | term().
+-spec create_tables() -> 'ok' | {'aborted', any()}.
+
create_tables() ->
create_tables([?TABLE]).
@@ -506,27 +498,42 @@ table_definitions() ->
{Name, Attributes} = ?TABLE,
[{Name, [?TABLE_MATCH | Attributes]}].
+-spec start_link(group_name(), atom(), any(), txn_fun()) ->
+ rabbit_types:ok_pid_or_error().
+
start_link(GroupName, Module, Args, TxnFun) ->
gen_server2:start_link(?MODULE, [GroupName, Module, Args, TxnFun],
[{spawn_opt, [{fullsweep_after, 0}]}]).
+-spec leave(pid()) -> 'ok'.
+
leave(Server) ->
gen_server2:cast(Server, leave).
+-spec broadcast(pid(), any()) -> 'ok'.
+
broadcast(Server, Msg) -> broadcast(Server, Msg, 0).
broadcast(Server, Msg, SizeHint) ->
gen_server2:cast(Server, {broadcast, Msg, SizeHint}).
+-spec confirmed_broadcast(pid(), any()) -> 'ok'.
+
confirmed_broadcast(Server, Msg) ->
gen_server2:call(Server, {confirmed_broadcast, Msg}, infinity).
+-spec info(pid()) -> rabbit_types:infos().
+
info(Server) ->
gen_server2:call(Server, info, infinity).
+-spec validate_members(pid(), [pid()]) -> 'ok'.
+
validate_members(Server, Members) ->
gen_server2:cast(Server, {validate_members, Members}).
+-spec forget_group(group_name()) -> 'ok'.
+
forget_group(GroupName) ->
{atomic, ok} = mnesia:sync_transaction(
fun () ->
diff --git a/src/lqueue.erl b/src/lqueue.erl
index acfdbe79ef..820f9f4a2f 100644
--- a/src/lqueue.erl
+++ b/src/lqueue.erl
@@ -36,64 +36,76 @@
-type result(T) :: 'empty' | {'value', T}.
-spec new() -> ?MODULE(_).
--spec drop(?MODULE(T)) -> ?MODULE(T).
--spec is_empty(?MODULE(_)) -> boolean().
--spec len(?MODULE(_)) -> non_neg_integer().
--spec in(T, ?MODULE(T)) -> ?MODULE(T).
--spec in_r(value(), ?MODULE()) -> ?MODULE().
--spec out(?MODULE(T)) -> {result(T), ?MODULE()}.
--spec out_r(?MODULE(T)) -> {result(T), ?MODULE()}.
--spec join(?MODULE(A), ?MODULE(B)) -> ?MODULE(A | B).
--spec foldl(fun ((T, B) -> B), B, ?MODULE(T)) -> B.
--spec foldr(fun ((T, B) -> B), B, ?MODULE(T)) -> B.
--spec from_list([T]) -> ?MODULE(T).
--spec to_list(?MODULE(T)) -> [T].
-% -spec peek(?MODULE()) -> result().
--spec peek(?MODULE(T)) -> result(T).
--spec peek_r(?MODULE(T)) -> result(T).
new() -> {0, ?QUEUE:new()}.
+-spec drop(?MODULE(T)) -> ?MODULE(T).
+
drop({L, Q}) -> {L - 1, ?QUEUE:drop(Q)}.
+-spec is_empty(?MODULE(_)) -> boolean().
+
is_empty({0, _Q}) -> true;
is_empty(_) -> false.
+-spec in(T, ?MODULE(T)) -> ?MODULE(T).
+
in(V, {L, Q}) -> {L+1, ?QUEUE:in(V, Q)}.
+-spec in_r(value(), ?MODULE()) -> ?MODULE().
+
in_r(V, {L, Q}) -> {L+1, ?QUEUE:in_r(V, Q)}.
+-spec out(?MODULE(T)) -> {result(T), ?MODULE()}.
+
out({0, _Q} = Q) -> {empty, Q};
out({L, Q}) -> {Result, Q1} = ?QUEUE:out(Q),
{Result, {L-1, Q1}}.
+-spec out_r(?MODULE(T)) -> {result(T), ?MODULE()}.
+
out_r({0, _Q} = Q) -> {empty, Q};
out_r({L, Q}) -> {Result, Q1} = ?QUEUE:out_r(Q),
{Result, {L-1, Q1}}.
+-spec join(?MODULE(A), ?MODULE(B)) -> ?MODULE(A | B).
+
join({L1, Q1}, {L2, Q2}) -> {L1 + L2, ?QUEUE:join(Q1, Q2)}.
+-spec to_list(?MODULE(T)) -> [T].
+
to_list({_L, Q}) -> ?QUEUE:to_list(Q).
+-spec from_list([T]) -> ?MODULE(T).
+
from_list(L) -> {length(L), ?QUEUE:from_list(L)}.
+-spec foldl(fun ((T, B) -> B), B, ?MODULE(T)) -> B.
+
foldl(Fun, Init, Q) ->
case out(Q) of
{empty, _Q} -> Init;
{{value, V}, Q1} -> foldl(Fun, Fun(V, Init), Q1)
end.
+-spec foldr(fun ((T, B) -> B), B, ?MODULE(T)) -> B.
+
foldr(Fun, Init, Q) ->
case out_r(Q) of
{empty, _Q} -> Init;
{{value, V}, Q1} -> foldr(Fun, Fun(V, Init), Q1)
end.
+-spec len(?MODULE(_)) -> non_neg_integer().
+
len({L, _}) -> L.
+-spec peek(?MODULE(T)) -> result(T).
peek({ 0, _Q}) -> empty;
peek({_L, Q}) -> ?QUEUE:peek(Q).
+-spec peek_r(?MODULE(T)) -> result(T).
+
peek_r({ 0, _Q}) -> empty;
peek_r({_L, Q}) -> ?QUEUE:peek_r(Q).
diff --git a/src/pg_local.erl b/src/pg_local.erl
index 0ed7e9d85d..3f03c97182 100644
--- a/src/pg_local.erl
+++ b/src/pg_local.erl
@@ -44,15 +44,6 @@
-type name() :: term().
--spec start_link() -> {'ok', pid()} | {'error', any()}.
--spec start() -> {'ok', pid()} | {'error', any()}.
--spec join(name(), pid()) -> 'ok'.
--spec leave(name(), pid()) -> 'ok'.
--spec get_members(name()) -> [pid()].
--spec in_group(name(), pid()) -> boolean().
-
--spec sync() -> 'ok'.
-
%%----------------------------------------------------------------------------
-define(TABLE, pg_local_table).
@@ -61,24 +52,36 @@
%%% Exported functions
%%%
+-spec start_link() -> {'ok', pid()} | {'error', any()}.
+
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+-spec start() -> {'ok', pid()} | {'error', any()}.
+
start() ->
ensure_started().
+-spec join(name(), pid()) -> 'ok'.
+
join(Name, Pid) when is_pid(Pid) ->
_ = ensure_started(),
gen_server:cast(?MODULE, {join, Name, Pid}).
+-spec leave(name(), pid()) -> 'ok'.
+
leave(Name, Pid) when is_pid(Pid) ->
_ = ensure_started(),
gen_server:cast(?MODULE, {leave, Name, Pid}).
+-spec get_members(name()) -> [pid()].
+
get_members(Name) ->
_ = ensure_started(),
group_members(Name).
+-spec in_group(name(), pid()) -> boolean().
+
in_group(Name, Pid) ->
_ = ensure_started(),
%% The join message is a cast and thus can race, but we want to
@@ -89,6 +92,8 @@ in_group(Name, Pid) ->
member_present(Name, Pid)
end.
+-spec sync() -> 'ok'.
+
sync() ->
_ = ensure_started(),
gen_server:call(?MODULE, sync, infinity).
diff --git a/src/rabbit.erl b/src/rabbit.erl
index d40aa5a279..2d16661768 100644
--- a/src/rabbit.erl
+++ b/src/rabbit.erl
@@ -25,7 +25,7 @@
-export([start/0, boot/0, stop/0,
stop_and_halt/0, await_startup/0, await_startup/1, await_startup/3,
status/0, is_running/0, alarms/0,
- is_running/1, environment/0, rotate_logs/0,
+ is_running/1, environment/0, rotate_logs/0, force_event_refresh/1,
start_fhc/0]).
-export([start/2, stop/1, prep_stop/1]).
@@ -33,6 +33,8 @@
-export([log_locations/0, config_files/0, decrypt_config/2]). %% for testing and mgmt-agent
-export([is_booted/1, is_booted/0, is_booting/1, is_booting/0]).
+-deprecated([{force_event_refresh, 1, eventually}]).
+
-ifdef(TEST).
-export([start_logger/0]).
@@ -63,6 +65,12 @@
{requires, pre_boot},
{enables, external_infrastructure}]}).
+-rabbit_boot_step({feature_flags,
+ [{description, "feature flags registry and initial state"},
+ {mfa, {rabbit_feature_flags, init, []}},
+ {requires, pre_boot},
+ {enables, external_infrastructure}]}).
+
-rabbit_boot_step({database,
[{mfa, {rabbit_mnesia, init, []}},
{requires, file_handle_cache},
@@ -251,40 +259,6 @@
-type param() :: atom().
-type app_name() :: atom().
--spec start() -> 'ok'.
--spec boot() -> 'ok'.
--spec stop() -> 'ok'.
--spec stop_and_halt() -> no_return().
-
--spec status
- () -> [{pid, integer()} |
- {running_applications, [{atom(), string(), string()}]} |
- {os, {atom(), atom()}} |
- {erlang_version, string()} |
- {memory, any()}].
--spec is_running() -> boolean().
--spec is_running(node()) -> boolean().
--spec environment() -> [{param(), term()}].
--spec rotate_logs() -> rabbit_types:ok_or_error(any()).
-
--spec log_locations() -> [log_location()].
-
--spec start('normal',[]) ->
- {'error',
- {'erlang_version_too_old',
- {'found',string(),string()},
- {'required',string(),string()}}} |
- {'ok',pid()}.
--spec stop(_) -> 'ok'.
-
--spec maybe_insert_default_data() -> 'ok'.
--spec boot_delegate() -> 'ok'.
--spec recover() -> 'ok'.
--spec start_apps([app_name()]) -> 'ok'.
--spec start_apps([app_name()],
- #{app_name() => restart_type()}) -> 'ok'.
--spec stop_apps([app_name()]) -> 'ok'.
-
%%----------------------------------------------------------------------------
ensure_application_loaded() ->
@@ -296,6 +270,8 @@ ensure_application_loaded() ->
{error, {already_loaded, rabbit}} -> ok
end.
+-spec start() -> 'ok'.
+
start() ->
start_it(fun() ->
%% We do not want to upgrade mnesia after just
@@ -309,6 +285,8 @@ start() ->
broker_start()
end).
+-spec boot() -> 'ok'.
+
boot() ->
start_it(fun() ->
ensure_config(),
@@ -486,6 +464,8 @@ start_it(StartFun) ->
Marker ! stop
end.
+-spec stop() -> 'ok'.
+
stop() ->
case whereis(rabbit_boot) of
undefined -> ok;
@@ -500,6 +480,8 @@ stop() ->
stop_apps(app_utils:app_dependency_order(Apps, true)),
rabbit_log:info("Successfully stopped RabbitMQ and its dependencies~n", []).
+-spec stop_and_halt() -> no_return().
+
stop_and_halt() ->
try
stop()
@@ -525,11 +507,17 @@ stop_and_halt() ->
end,
ok.
+-spec start_apps([app_name()]) -> 'ok'.
+
start_apps(Apps) ->
start_apps(Apps, #{}).
+-spec start_apps([app_name()],
+ #{app_name() => restart_type()}) -> 'ok'.
+
start_apps(Apps, RestartTypes) ->
app_utils:load_applications(Apps),
+ rabbit_feature_flags:initialize_registry(),
ensure_sysmon_handler_app_config(),
%% make Ra use a custom logger that dispatches to lager instead of the
%% default OTP logger
@@ -669,6 +657,8 @@ decrypt_list([{Key, Value}|Tail], Algo, Acc) when Key =/= encrypted ->
decrypt_list([Value|Tail], Algo, Acc) ->
decrypt_list(Tail, Algo, [decrypt(Value, Algo)|Acc]).
+-spec stop_apps([app_name()]) -> 'ok'.
+
stop_apps([]) ->
ok;
stop_apps(Apps) ->
@@ -703,16 +693,19 @@ is_booting(Node) ->
-spec await_startup() -> 'ok' | {'error', 'timeout'}.
+
await_startup() ->
await_startup(node(), false).
-spec await_startup(node() | non_neg_integer()) -> 'ok' | {'error', 'timeout'}.
+
await_startup(Node) when is_atom(Node) ->
await_startup(Node, false);
await_startup(Timeout) when is_integer(Timeout) ->
await_startup(node(), false, Timeout).
-spec await_startup(node(), boolean()) -> 'ok' | {'error', 'timeout'}.
+
await_startup(Node, PrintProgressReports) ->
case is_booting(Node) of
true -> wait_for_boot_to_finish(Node, PrintProgressReports);
@@ -725,6 +718,7 @@ await_startup(Node, PrintProgressReports) ->
end.
-spec await_startup(node(), boolean(), non_neg_integer()) -> 'ok' | {'error', 'timeout'}.
+
await_startup(Node, PrintProgressReports, Timeout) ->
case is_booting(Node) of
true -> wait_for_boot_to_finish(Node, PrintProgressReports, Timeout);
@@ -796,6 +790,13 @@ maybe_print_boot_progress(true, IterationsLeft) ->
_ -> ok
end.
+-spec status
+ () -> [{pid, integer()} |
+ {running_applications, [{atom(), string(), string()}]} |
+ {os, {atom(), atom()}} |
+ {erlang_version, string()} |
+ {memory, any()}].
+
status() ->
S1 = [{pid, list_to_integer(os:getpid())},
%% The timeout value used is twice that of gen_server:call/2.
@@ -851,8 +852,13 @@ listeners() ->
%% TODO this only determines if the rabbit application has started,
%% not if it is running, never mind plugins. It would be nice to have
%% more nuance here.
+
+-spec is_running() -> boolean().
+
is_running() -> is_running(node()).
+-spec is_running(node()) -> boolean().
+
is_running(Node) -> rabbit_nodes:is_process_running(Node, rabbit).
is_booted() -> is_booted(node()).
@@ -864,6 +870,8 @@ is_booted(Node) ->
_ -> false
end.
+-spec environment() -> [{param(), term()}].
+
environment() ->
%% The timeout value is twice that of gen_server:call/2.
[{A, environment(A)} ||
@@ -874,6 +882,8 @@ environment(App) ->
lists:keysort(1, [P || P = {K, _} <- application:get_all_env(App),
not lists:member(K, Ignore)]).
+-spec rotate_logs() -> rabbit_types:ok_or_error(any()).
+
rotate_logs() ->
rabbit_lager:fold_sinks(
fun
@@ -897,6 +907,13 @@ rotate_logs() ->
%%--------------------------------------------------------------------
+-spec start('normal',[]) ->
+ {'error',
+ {'erlang_version_too_old',
+ {'found',string(),string()},
+ {'required',string(),string()}}} |
+ {'ok',pid()}.
+
start(normal, []) ->
case erts_version_check() of
ok ->
@@ -929,6 +946,8 @@ prep_stop(State) ->
rabbit_peer_discovery:maybe_unregister(),
State.
+-spec stop(_) -> 'ok'.
+
stop(_State) ->
ok = rabbit_alarm:stop(),
ok = case rabbit_mnesia:is_clustered() of
@@ -983,14 +1002,20 @@ log_boot_error_and_exit(Reason, Format, Args) ->
%%---------------------------------------------------------------------------
%% boot step functions
+-spec boot_delegate() -> 'ok'.
+
boot_delegate() ->
{ok, Count} = application:get_env(rabbit, delegate_count),
rabbit_sup:start_supervisor_child(delegate_sup, [Count]).
+-spec recover() -> 'ok'.
+
recover() ->
rabbit_policy:recover(),
rabbit_vhost:recover().
+-spec maybe_insert_default_data() -> 'ok'.
+
maybe_insert_default_data() ->
case rabbit_table:needs_default_data() of
true -> insert_default_data();
@@ -1035,9 +1060,23 @@ start_logger() ->
rabbit_lager:start_logger(),
ok.
+-spec log_locations() -> [log_location()].
+
log_locations() ->
rabbit_lager:log_locations().
+%% This feature was used by the management API up-to and including
+%% RabbitMQ 3.7.x. It is unused in 3.8.x and thus deprecated. We keep it
+%% to support in-place upgrades to 3.8.x (i.e. mixed-version clusters).
+
+-spec force_event_refresh(reference()) -> 'ok'.
+
+force_event_refresh(Ref) ->
+ rabbit_direct:force_event_refresh(Ref),
+ rabbit_networking:force_connection_event_refresh(Ref),
+ rabbit_channel:force_event_refresh(Ref),
+ rabbit_amqqueue:force_event_refresh(Ref).
+
%%---------------------------------------------------------------------------
%% misc
diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl
index 4356cb427f..44e72f7ce4 100644
--- a/src/rabbit_access_control.erl
+++ b/src/rabbit_access_control.erl
@@ -27,29 +27,20 @@
-type permission_atom() :: 'configure' | 'read' | 'write'.
+%%----------------------------------------------------------------------------
+
-spec check_user_pass_login
(rabbit_types:username(), rabbit_types:password()) ->
{'ok', rabbit_types:user()} |
{'refused', rabbit_types:username(), string(), [any()]}.
+
+check_user_pass_login(Username, Password) ->
+ check_user_login(Username, [{password, Password}]).
+
-spec check_user_login
(rabbit_types:username(), [{atom(), any()}]) ->
{'ok', rabbit_types:user()} |
{'refused', rabbit_types:username(), string(), [any()]}.
--spec check_user_loopback
- (rabbit_types:username(), rabbit_net:socket() | inet:ip_address()) ->
- 'ok' | 'not_allowed'.
--spec check_vhost_access
- (rabbit_types:user(), rabbit_types:vhost(),
- rabbit_net:socket() | #authz_socket_info{}) ->
- 'ok' | rabbit_types:channel_exit().
--spec check_resource_access
- (rabbit_types:user(), rabbit_types:r(atom()), permission_atom()) ->
- 'ok' | rabbit_types:channel_exit().
-
-%%----------------------------------------------------------------------------
-
-check_user_pass_login(Username, Password) ->
- check_user_login(Username, [{password, Password}]).
check_user_login(Username, AuthProps) ->
{ok, Modules} = application:get_env(rabbit, auth_backends),
@@ -122,6 +113,10 @@ auth_user(#user{username = Username, tags = Tags}, Impl) ->
tags = Tags,
impl = Impl}.
+-spec check_user_loopback
+ (rabbit_types:username(), rabbit_net:socket() | inet:ip_address()) ->
+ 'ok' | 'not_allowed'.
+
check_user_loopback(Username, SockOrAddr) ->
{ok, Users} = application:get_env(rabbit, loopback_users),
case rabbit_net:is_loopback(SockOrAddr)
@@ -130,6 +125,11 @@ check_user_loopback(Username, SockOrAddr) ->
false -> not_allowed
end.
+-spec check_vhost_access
+ (rabbit_types:user(), rabbit_types:vhost(),
+ rabbit_net:socket() | #authz_socket_info{}) ->
+ 'ok' | rabbit_types:channel_exit().
+
check_vhost_access(User = #user{username = Username,
authz_backends = Modules}, VHostPath, Sock) ->
lists:foldl(
@@ -146,6 +146,10 @@ check_vhost_access(User = #user{username = Username,
Else
end, ok, Modules).
+-spec check_resource_access
+ (rabbit_types:user(), rabbit_types:r(atom()), permission_atom()) ->
+ 'ok' | rabbit_types:channel_exit().
+
check_resource_access(User, R = #resource{kind = exchange, name = <<"">>},
Permission) ->
check_resource_access(User, R#resource{name = <<"amq.default">>},
diff --git a/src/rabbit_alarm.erl b/src/rabbit_alarm.erl
index 789992cb3d..6f33e02fe4 100644
--- a/src/rabbit_alarm.erl
+++ b/src/rabbit_alarm.erl
@@ -52,21 +52,15 @@
-type resource_alarm() :: {resource_limit, resource_alarm_source(), node()}.
-type alarm() :: local_alarm() | resource_alarm().
--spec start_link() -> rabbit_types:ok_pid_or_error().
--spec start() -> 'ok'.
--spec stop() -> 'ok'.
--spec register(pid(), rabbit_types:mfargs()) -> [atom()].
--spec set_alarm({alarm(), []}) -> 'ok'.
--spec clear_alarm(alarm()) -> 'ok'.
--spec on_node_up(node()) -> 'ok'.
--spec on_node_down(node()) -> 'ok'.
--spec get_alarms() -> [{alarm(), []}].
-
%%----------------------------------------------------------------------------
+-spec start_link() -> rabbit_types:ok_pid_or_error().
+
start_link() ->
gen_event:start_link({local, ?SERVER}).
+-spec start() -> 'ok'.
+
start() ->
ok = rabbit_sup:start_restartable_child(?MODULE),
ok = gen_event:add_handler(?SERVER, ?MODULE, []),
@@ -84,21 +78,38 @@ start() ->
rabbit_disk_monitor, [DiskLimit]),
ok.
+-spec stop() -> 'ok'.
+
stop() -> ok.
%% Registers a handler that should be called on every resource alarm change.
%% Given a call rabbit_alarm:register(Pid, {M, F, A}), the handler would be
%% called like this: `apply(M, F, A ++ [Pid, Source, Alert])', where `Source'
%% has the type of resource_alarm_source() and `Alert' has the type of resource_alert().
+
+-spec register(pid(), rabbit_types:mfargs()) -> [atom()].
+
register(Pid, AlertMFA) ->
gen_event:call(?SERVER, ?MODULE, {register, Pid, AlertMFA}, infinity).
+-spec set_alarm({alarm(), []}) -> 'ok'.
+
set_alarm(Alarm) -> gen_event:notify(?SERVER, {set_alarm, Alarm}).
+
+-spec clear_alarm(alarm()) -> 'ok'.
+
clear_alarm(Alarm) -> gen_event:notify(?SERVER, {clear_alarm, Alarm}).
+-spec get_alarms() -> [{alarm(), []}].
+
get_alarms() -> gen_event:call(?SERVER, ?MODULE, get_alarms, infinity).
+-spec on_node_up(node()) -> 'ok'.
+
on_node_up(Node) -> gen_event:notify(?SERVER, {node_up, Node}).
+
+-spec on_node_down(node()) -> 'ok'.
+
on_node_down(Node) -> gen_event:notify(?SERVER, {node_down, Node}).
remote_conserve_resources(Pid, Source, {true, _, _}) ->
diff --git a/src/rabbit_amqqueue.erl b/src/rabbit_amqqueue.erl
index 63d89ff4a7..c9c120df77 100644
--- a/src/rabbit_amqqueue.erl
+++ b/src/rabbit_amqqueue.erl
@@ -21,17 +21,19 @@
delete_immediately/1, delete_exclusive/2, delete/4, purge/1,
forget_all_durable/1, delete_crashed/1, delete_crashed/2,
delete_crashed_internal/2]).
--export([pseudo_queue/2, immutable/1]).
+-export([pseudo_queue/2, pseudo_queue/3, immutable/1]).
-export([lookup/1, not_found_or_absent/1, with/2, with/3, with_or_die/2,
assert_equivalence/5,
check_exclusive_access/2, with_exclusive_access_or_die/3,
stat/1, deliver/2, deliver/3, requeue/4, ack/4, reject/5]).
+-export([not_found/1, absent/2]).
-export([list/0, list/1, info_keys/0, info/1, info/2, info_all/1, info_all/2,
emit_info_all/5, list_local/1, info_local/1,
- emit_info_local/4, emit_info_down/4]).
--export([list_down/1, count/1, list_names/0, list_names/1, list_local_names/0]).
+ emit_info_local/4, emit_info_down/4]).
+-export([list_down/1, count/1, list_names/0, list_names/1, list_local_names/0,
+ list_with_possible_retry/1]).
-export([list_by_type/1]).
--export([notify_policy_changed/1]).
+-export([force_event_refresh/1, notify_policy_changed/1]).
-export([consumers/1, consumers_all/1, emit_consumers_all/4, consumer_info_keys/0]).
-export([basic_get/6, basic_consume/12, basic_cancel/6, notify_decorators/1]).
-export([notify_sent/2, notify_sent_queue_down/1, resume/2]).
@@ -49,6 +51,8 @@
-export([pid_of/1, pid_of/2]).
-export([mark_local_durable_queues_stopped/1]).
+-deprecated([{force_event_refresh, 1, eventually}]).
+
%% internal
-export([internal_declare/2, internal_delete/2, run_backing_queue/3,
set_ram_duration_target/2, set_maximum_since_use/2,
@@ -56,6 +60,7 @@
-include_lib("rabbit_common/include/rabbit.hrl").
-include_lib("stdlib/include/qlc.hrl").
+-include("amqqueue.hrl").
-define(INTEGER_ARG_TYPES, [byte, short, signedint, long,
unsignedbyte, unsignedshort, unsignedint]).
@@ -71,154 +76,16 @@
-type name() :: rabbit_types:r('queue').
-type qpids() :: [pid()].
-type qlen() :: rabbit_types:ok(non_neg_integer()).
--type qfun(A) :: fun ((rabbit_types:amqqueue()) -> A | no_return()).
+-type qfun(A) :: fun ((amqqueue:amqqueue()) -> A | no_return()).
-type qmsg() :: {name(), pid(), msg_id(), boolean(), rabbit_types:message()}.
-type msg_id() :: non_neg_integer().
-type ok_or_errors() ::
'ok' | {'error', [{'error' | 'exit' | 'throw', any()}]}.
--type absent_reason() :: 'nodedown' | 'crashed'.
--type queue_or_absent() :: rabbit_types:amqqueue() |
- {'absent', rabbit_types:amqqueue(),absent_reason()}.
--type not_found_or_absent() ::
- 'not_found' | {'absent', rabbit_types:amqqueue(), absent_reason()}.
--spec recover(rabbit_types:vhost()) -> [rabbit_types:amqqueue()].
--spec stop(rabbit_types:vhost()) -> 'ok'.
--spec start([rabbit_types:amqqueue()]) -> 'ok'.
--spec declare
- (name(), boolean(), boolean(), rabbit_framing:amqp_table(),
- rabbit_types:maybe(pid()), rabbit_types:username()) ->
- {'new' | 'existing' | 'absent' | 'owner_died',
- rabbit_types:amqqueue()} |
- {'new', rabbit_types:amqqueue(), rabbit_fifo_client:state()} |
- rabbit_types:channel_exit().
--spec declare
- (name(), boolean(), boolean(), rabbit_framing:amqp_table(),
- rabbit_types:maybe(pid()), rabbit_types:username(), node()) ->
- {'new' | 'existing' | 'owner_died', rabbit_types:amqqueue()} |
- {'new', rabbit_types:amqqueue(), rabbit_fifo_client:state()} |
- {'absent', rabbit_types:amqqueue(), absent_reason()} |
- rabbit_types:channel_exit().
--spec internal_declare(rabbit_types:amqqueue(), boolean()) ->
- queue_or_absent() | rabbit_misc:thunk(queue_or_absent()).
--spec update
- (name(), fun((rabbit_types:amqqueue()) -> rabbit_types:amqqueue())) ->
- 'not_found' | rabbit_types:amqqueue().
--spec lookup
- (name()) ->
- rabbit_types:ok(rabbit_types:amqqueue()) |
- rabbit_types:error('not_found');
- ([name()]) ->
- [rabbit_types:amqqueue()].
--spec not_found_or_absent(name()) -> not_found_or_absent().
--spec with(name(), qfun(A)) ->
- A | rabbit_types:error(not_found_or_absent()).
--spec with(name(), qfun(A), fun((not_found_or_absent()) -> B)) -> A | B.
--spec with_or_die(name(), qfun(A)) -> A | rabbit_types:channel_exit().
--spec assert_equivalence
- (rabbit_types:amqqueue(), boolean(), boolean(),
- rabbit_framing:amqp_table(), rabbit_types:maybe(pid())) ->
- 'ok' | rabbit_types:channel_exit() | rabbit_types:connection_exit().
--spec check_exclusive_access(rabbit_types:amqqueue(), pid()) ->
- 'ok' | rabbit_types:channel_exit().
--spec with_exclusive_access_or_die(name(), pid(), qfun(A)) ->
- A | rabbit_types:channel_exit().
--spec list() -> [rabbit_types:amqqueue()].
--spec list(rabbit_types:vhost()) -> [rabbit_types:amqqueue()].
--spec list_names() -> [rabbit_amqqueue:name()].
--spec list_down(rabbit_types:vhost()) -> [rabbit_types:amqqueue()].
--spec list_by_type(atom()) -> [rabbit_types:amqqueue()].
--spec info_keys() -> rabbit_types:info_keys().
--spec info(rabbit_types:amqqueue()) -> rabbit_types:infos().
--spec info(rabbit_types:amqqueue(), rabbit_types:info_keys()) ->
- rabbit_types:infos().
--spec info_all(rabbit_types:vhost()) -> [rabbit_types:infos()].
--spec info_all(rabbit_types:vhost(), rabbit_types:info_keys()) ->
- [rabbit_types:infos()].
--spec notify_policy_changed(rabbit_types:amqqueue()) -> 'ok'.
--spec consumers(rabbit_types:amqqueue()) ->
- [{pid(), rabbit_types:ctag(), boolean(), non_neg_integer(),
- rabbit_framing:amqp_table()}].
--spec consumer_info_keys() -> rabbit_types:info_keys().
--spec consumers_all(rabbit_types:vhost()) ->
- [{name(), pid(), rabbit_types:ctag(), boolean(),
- non_neg_integer(), rabbit_framing:amqp_table()}].
--spec stat(rabbit_types:amqqueue()) ->
- {'ok', non_neg_integer(), non_neg_integer()}.
--spec delete_immediately(qpids()) -> 'ok'.
--spec delete_exclusive(qpids(), pid()) -> 'ok'.
--spec delete
- (rabbit_types:amqqueue(), 'false', 'false', rabbit_types:username()) ->
- qlen();
- (rabbit_types:amqqueue(), 'true' , 'false', rabbit_types:username()) ->
- qlen() | rabbit_types:error('in_use');
- (rabbit_types:amqqueue(), 'false', 'true', rabbit_types:username()) ->
- qlen() | rabbit_types:error('not_empty');
- (rabbit_types:amqqueue(), 'true' , 'true', rabbit_types:username()) ->
- qlen() |
- rabbit_types:error('in_use') |
- rabbit_types:error('not_empty').
--spec delete_crashed(rabbit_types:amqqueue()) -> 'ok'.
--spec delete_crashed_internal(rabbit_types:amqqueue(), rabbit_types:username()) -> 'ok'.
--spec purge(rabbit_types:amqqueue()) -> {ok, qlen()}.
--spec forget_all_durable(node()) -> 'ok'.
--spec deliver([rabbit_types:amqqueue()], rabbit_types:delivery(), #{Name :: atom() => rabbit_fifo_client:state()} | 'untracked') ->
- {qpids(), #{Name :: atom() => rabbit_fifo_client:state()}}.
--spec deliver([rabbit_types:amqqueue()], rabbit_types:delivery()) -> 'ok'.
--spec requeue(pid(), [msg_id()], pid(), #{Name :: atom() => rabbit_fifo_client:state()}) -> 'ok'.
--spec ack(pid(), [msg_id()], pid(), #{Name :: atom() => rabbit_fifo_client:state()}) -> 'ok'.
--spec reject(pid() | {atom(), node()}, [msg_id()], boolean(), pid(),
- #{Name :: atom() => rabbit_fifo_client:state()}) -> 'ok'.
--spec notify_down_all(qpids(), pid()) -> ok_or_errors().
--spec notify_down_all(qpids(), pid(), non_neg_integer()) ->
- ok_or_errors().
--spec activate_limit_all(qpids(), pid()) -> ok_or_errors().
--spec basic_get(rabbit_types:amqqueue(), pid(), boolean(), pid(), rabbit_types:ctag(),
- #{Name :: atom() => rabbit_fifo_client:state()}) ->
- {'ok', non_neg_integer(), qmsg()} | 'empty'.
--spec credit
- (rabbit_types:amqqueue(), pid(), rabbit_types:ctag(), non_neg_integer(),
- boolean(), #{Name :: atom() => rabbit_fifo_client:state()}) ->
- 'ok'.
--spec basic_consume
- (rabbit_types:amqqueue(), boolean(), pid(), pid(), boolean(),
- non_neg_integer(), rabbit_types:ctag(), boolean(),
- rabbit_framing:amqp_table(), any(), rabbit_types:username(),
- #{Name :: atom() => rabbit_fifo_client:state()}) ->
- rabbit_types:ok_or_error('exclusive_consume_unavailable').
--spec basic_cancel
- (rabbit_types:amqqueue(), pid(), rabbit_types:ctag(), any(),
- rabbit_types:username(), #{Name :: atom() => rabbit_fifo_client:state()}) ->
- 'ok' | {'ok', #{Name :: atom() => rabbit_fifo_client:state()}}.
--spec notify_decorators(rabbit_types:amqqueue()) -> 'ok'.
--spec resume(pid(), pid()) -> 'ok'.
--spec internal_delete(name(), rabbit_types:username()) ->
- 'ok' | rabbit_types:connection_exit() |
- fun (() ->
- 'ok' | rabbit_types:connection_exit()).
--spec run_backing_queue
- (pid(), atom(), (fun ((atom(), A) -> {[rabbit_types:msg_id()], A}))) ->
- 'ok'.
--spec set_ram_duration_target(pid(), number() | 'infinity') -> 'ok'.
--spec set_maximum_since_use(pid(), non_neg_integer()) -> 'ok'.
--spec on_node_up(node()) -> 'ok'.
--spec on_node_down(node()) -> 'ok'.
--spec pseudo_queue(name(), pid()) -> rabbit_types:amqqueue().
--spec immutable(rabbit_types:amqqueue()) -> rabbit_types:amqqueue().
--spec store_queue(rabbit_types:amqqueue()) -> 'ok'.
--spec update_decorators(name()) -> 'ok'.
--spec policy_changed(rabbit_types:amqqueue(), rabbit_types:amqqueue()) ->
- 'ok'.
--spec update_mirroring(pid()) -> 'ok'.
--spec sync_mirrors(rabbit_types:amqqueue() | pid()) ->
- 'ok' | rabbit_types:error('not_mirrored').
--spec cancel_sync_mirrors(rabbit_types:amqqueue() | pid()) ->
- 'ok' | {'ok', 'not_syncing'}.
--spec is_replicated(rabbit_types:amqqueue()) -> boolean().
-
--spec pid_of(rabbit_types:amqqueue()) ->
- {'ok', pid()} | rabbit_types:error('not_found').
--spec pid_of(rabbit_types:vhost(), rabbit_misc:resource_name()) ->
- {'ok', pid()} | rabbit_types:error('not_found').
+-type absent_reason() :: 'nodedown' | 'crashed' | stopped | timeout.
+-type queue_not_found() :: not_found.
+-type queue_absent() :: {'absent', amqqueue:amqqueue(), absent_reason()}.
+-type not_found_or_absent() :: queue_not_found() | queue_absent().
+-type quorum_states() :: #{Name :: atom() => rabbit_fifo_client:state()}.
%%----------------------------------------------------------------------------
@@ -241,6 +108,8 @@ warn_file_limit() ->
ok
end.
+-spec recover(rabbit_types:vhost()) -> [amqqueue:amqqueue()].
+
recover(VHost) ->
Classic = find_local_durable_classic_queues(VHost),
Quorum = find_local_quorum_queues(VHost),
@@ -252,7 +121,7 @@ recover_classic_queues(VHost, Queues) ->
%% order as the supplied queue names, so that we can zip them together
%% for further processing in recover_durable_queues.
{ok, OrderedRecoveryTerms} =
- BQ:start(VHost, [QName || #amqqueue{name = QName} <- Queues]),
+ BQ:start(VHost, [amqqueue:get_name(Q) || Q <- Queues]),
case rabbit_amqqueue_sup_sup:start_for_vhost(VHost) of
{ok, _} ->
recover_durable_queues(lists:zip(Queues, OrderedRecoveryTerms));
@@ -262,18 +131,21 @@ recover_classic_queues(VHost, Queues) ->
end.
filter_per_type(Queues) ->
- lists:partition(fun(#amqqueue{type = Type}) -> Type == classic end, Queues).
+ lists:partition(fun(Q) -> amqqueue:is_classic(Q) end, Queues).
filter_pid_per_type(QPids) ->
lists:partition(fun(QPid) -> ?IS_CLASSIC(QPid) end, QPids).
filter_resource_per_type(Resources) ->
Queues = [begin
- {ok, #amqqueue{pid = QPid}} = lookup(Resource),
+ {ok, Q} = lookup(Resource),
+ QPid = amqqueue:get_pid(Q),
{Resource, QPid}
end || Resource <- Resources],
lists:partition(fun({_Resource, QPid}) -> ?IS_CLASSIC(QPid) end, Queues).
+-spec stop(rabbit_types:vhost()) -> 'ok'.
+
stop(VHost) ->
%% Classic queues
ok = rabbit_amqqueue_sup_sup:stop_for_vhost(VHost),
@@ -281,53 +153,55 @@ stop(VHost) ->
ok = BQ:stop(VHost),
rabbit_quorum_queue:stop(VHost).
+-spec start([amqqueue:amqqueue()]) -> 'ok'.
+
start(Qs) ->
{Classic, _Quorum} = filter_per_type(Qs),
%% At this point all recovered queues and their bindings are
%% visible to routing, so now it is safe for them to complete
%% their initialisation (which may involve interacting with other
%% queues).
- _ = [Pid ! {self(), go} || #amqqueue{pid = Pid} <- Classic],
+ _ = [amqqueue:get_pid(Q) ! {self(), go} || Q <- Classic],
ok.
mark_local_durable_queues_stopped(VHost) ->
+ ?try_mnesia_tx_or_upgrade_amqqueue_and_retry(
+ do_mark_local_durable_queues_stopped(VHost),
+ do_mark_local_durable_queues_stopped(VHost)).
+
+do_mark_local_durable_queues_stopped(VHost) ->
Qs = find_local_durable_classic_queues(VHost),
rabbit_misc:execute_mnesia_transaction(
fun() ->
- [ store_queue(Q#amqqueue{ state = stopped })
- || Q = #amqqueue{ state = State } <- Qs,
- State =/= stopped ]
+ [ store_queue(amqqueue:set_state(Q, stopped))
+ || Q <- Qs,
+ amqqueue:get_state(Q) =/= stopped ]
end).
find_local_quorum_queues(VHost) ->
Node = node(),
mnesia:async_dirty(
fun () ->
- qlc:e(qlc:q([Q || Q = #amqqueue{vhost = VH,
- type = quorum,
- quorum_nodes = QuorumNodes}
- <- mnesia:table(rabbit_durable_queue),
- VH =:= VHost,
- (lists:member(Node, QuorumNodes))]))
+ qlc:e(qlc:q([Q || Q <- mnesia:table(rabbit_durable_queue),
+ amqqueue:get_vhost(Q) =:= VHost,
+ amqqueue:is_quorum(Q) andalso
+ (lists:member(Node, amqqueue:get_quorum_nodes(Q)))]))
end).
find_local_durable_classic_queues(VHost) ->
Node = node(),
mnesia:async_dirty(
fun () ->
- qlc:e(qlc:q([Q || Q = #amqqueue{name = Name,
- vhost = VH,
- pid = Pid,
- type = classic}
- <- mnesia:table(rabbit_durable_queue),
- VH =:= VHost,
- (is_local_to_node(Pid, Node) andalso
+ qlc:e(qlc:q([Q || Q <- mnesia:table(rabbit_durable_queue),
+ amqqueue:get_vhost(Q) =:= VHost,
+ amqqueue:is_classic(Q) andalso
+ (is_local_to_node(amqqueue:get_pid(Q), Node) andalso
%% Terminations on node down will not remove the rabbit_queue
%% record if it is a mirrored queue (such info is now obtained from
%% the policy). Thus, we must check if the local pid is alive
%% - if the record is present - in order to restart.
- (mnesia:read(rabbit_queue, Name, read) =:= []
- orelse not rabbit_mnesia:is_process_alive(Pid)))
+ (mnesia:read(rabbit_queue, amqqueue:get_name(Q), read) =:= []
+ orelse not rabbit_mnesia:is_process_alive(amqqueue:get_pid(Q))))
]))
end).
@@ -335,20 +209,16 @@ find_recoverable_queues() ->
Node = node(),
mnesia:async_dirty(
fun () ->
- qlc:e(qlc:q([Q || Q = #amqqueue{name = Name,
- pid = Pid,
- type = Type,
- quorum_nodes = QuorumNodes}
- <- mnesia:table(rabbit_durable_queue),
- (Type == classic andalso
- (is_local_to_node(Pid, Node) andalso
+ qlc:e(qlc:q([Q || Q <- mnesia:table(rabbit_durable_queue),
+ (amqqueue:is_classic(Q) andalso
+ (is_local_to_node(amqqueue:get_pid(Q), Node) andalso
%% Terminations on node down will not remove the rabbit_queue
%% record if it is a mirrored queue (such info is now obtained from
%% the policy). Thus, we must check if the local pid is alive
%% - if the record is present - in order to restart.
- (mnesia:read(rabbit_queue, Name, read) =:= []
- orelse not rabbit_mnesia:is_process_alive(Pid))))
- orelse (Type == quorum andalso lists:member(Node, QuorumNodes))
+ (mnesia:read(rabbit_queue, amqqueue:get_name(Q), read) =:= []
+ orelse not rabbit_mnesia:is_process_alive(amqqueue:get_pid(Q)))))
+ orelse (amqqueue:is_quorum(Q) andalso lists:member(Node, amqqueue:get_quorum_nodes(Q)))
]))
end).
@@ -361,6 +231,15 @@ recover_durable_queues(QueuesAndRecoveryTerms) ->
[Pid, Error]) || {Pid, Error} <- Failures],
[Q || {_, {new, Q}} <- Results].
+-spec declare(name(),
+ boolean(),
+ boolean(),
+ rabbit_framing:amqp_table(),
+ rabbit_types:maybe(pid()),
+ rabbit_types:username()) ->
+ {'new' | 'existing' | 'absent' | 'owner_died', amqqueue:amqqueue()} |
+ rabbit_types:channel_exit().
+
declare(QueueName, Durable, AutoDelete, Args, Owner, ActingUser) ->
declare(QueueName, Durable, AutoDelete, Args, Owner, ActingUser, node()).
@@ -368,36 +247,56 @@ declare(QueueName, Durable, AutoDelete, Args, Owner, ActingUser) ->
%% The Node argument suggests where the queue (master if mirrored)
%% should be. Note that in some cases (e.g. with "nodes" policy in
%% effect) this might not be possible to satisfy.
+
+-spec declare(name(),
+ boolean(),
+ boolean(),
+ rabbit_framing:amqp_table(),
+ rabbit_types:maybe(pid()),
+ rabbit_types:username(),
+ node()) ->
+ {'new' | 'existing' | 'owner_died', amqqueue:amqqueue()} |
+ {'new', amqqueue:amqqueue(), rabbit_fifo_client:state()} |
+ {'absent', amqqueue:amqqueue(), absent_reason()} |
+ rabbit_types:channel_exit().
+
declare(QueueName = #resource{virtual_host = VHost}, Durable, AutoDelete, Args,
Owner, ActingUser, Node) ->
ok = check_declare_arguments(QueueName, Args),
Type = get_queue_type(Args),
- Q = rabbit_queue_decorator:set(
- rabbit_policy:set(#amqqueue{name = QueueName,
- durable = Durable,
- auto_delete = AutoDelete,
- arguments = Args,
- exclusive_owner = Owner,
- pid = none,
- slave_pids = [],
- sync_slave_pids = [],
- recoverable_slaves = [],
- gm_pids = [],
- state = live,
- policy_version = 0,
- slave_pids_pending_shutdown = [],
- vhost = VHost,
- options = #{user => ActingUser},
- type = Type})),
-
- case Type of
- classic ->
- declare_classic_queue(Q, Node);
- quorum ->
- rabbit_quorum_queue:declare(Q)
+ TypeIsAllowed =
+ Type =:= classic orelse
+ rabbit_feature_flags:is_enabled(quorum_queue),
+ case TypeIsAllowed of
+ true ->
+ Q0 = amqqueue:new(QueueName,
+ none,
+ Durable,
+ AutoDelete,
+ Owner,
+ Args,
+ VHost,
+ #{user => ActingUser},
+ Type),
+ Q = rabbit_queue_decorator:set(
+ rabbit_policy:set(Q0)),
+ do_declare(Q, Node);
+ false ->
+ rabbit_misc:protocol_error(
+ internal_error,
+ "Cannot declare a queue '~s' of type '~s' on node '~s': "
+ "the 'quorum_queue' feature flag is disabled",
+ [rabbit_misc:rs(QueueName), Type, Node])
end.
-declare_classic_queue(#amqqueue{name = QName, vhost = VHost} = Q, Node) ->
+do_declare(Q, Node) when ?amqqueue_is_classic(Q) ->
+ declare_classic_queue(Q, Node);
+do_declare(Q, _Node) when ?amqqueue_is_quorum(Q) ->
+ rabbit_quorum_queue:declare(Q).
+
+declare_classic_queue(Q, Node) ->
+ QName = amqqueue:get_name(Q),
+ VHost = amqqueue:get_vhost(Q),
Node1 = case rabbit_queue_master_location_misc:get_location(Q) of
{ok, Node0} -> Node0;
{error, _} -> Node
@@ -422,20 +321,32 @@ get_queue_type(Args) ->
erlang:binary_to_existing_atom(V, utf8)
end.
-internal_declare(Q, true) ->
+-spec internal_declare(amqqueue:amqqueue(), boolean()) ->
+ {created | existing, amqqueue:amqqueue()} | queue_absent().
+
+internal_declare(Q, Recover) ->
+ ?try_mnesia_tx_or_upgrade_amqqueue_and_retry(
+ do_internal_declare(Q, Recover),
+ begin
+ Q1 = amqqueue:upgrade(Q),
+ do_internal_declare(Q1, Recover)
+ end).
+
+do_internal_declare(Q, true) ->
rabbit_misc:execute_mnesia_tx_with_tail(
fun () ->
- ok = store_queue(Q#amqqueue{state = live}),
+ ok = store_queue(amqqueue:set_state(Q, live)),
rabbit_misc:const({created, Q})
end);
-internal_declare(Q = #amqqueue{name = QueueName}, false) ->
+do_internal_declare(Q, false) ->
+ QueueName = amqqueue:get_name(Q),
rabbit_misc:execute_mnesia_tx_with_tail(
fun () ->
case mnesia:wread({rabbit_queue, QueueName}) of
[] ->
case not_found_or_absent(QueueName) of
not_found -> Q1 = rabbit_policy:set(Q),
- Q2 = Q1#amqqueue{state = live},
+ Q2 = amqqueue:set_state(Q1, live),
ok = store_queue(Q2),
fun () -> {created, Q2} end;
{absent, _Q, _} = R -> rabbit_misc:const(R)
@@ -445,9 +356,14 @@ internal_declare(Q = #amqqueue{name = QueueName}, false) ->
end
end).
+-spec update
+ (name(), fun((amqqueue:amqqueue()) -> amqqueue:amqqueue())) ->
+ 'not_found' | amqqueue:amqqueue().
+
update(Name, Fun) ->
case mnesia:wread({rabbit_queue, Name}) of
- [Q = #amqqueue{durable = Durable}] ->
+ [Q] ->
+ Durable = amqqueue:is_durable(Q),
Q1 = Fun(Q),
ok = mnesia:write(rabbit_queue, Q1, write),
case Durable of
@@ -462,25 +378,34 @@ update(Name, Fun) ->
%% only really used for quorum queues to ensure the rabbit_queue record
%% is initialised
ensure_rabbit_queue_record_is_initialized(Q) ->
+ ?try_mnesia_tx_or_upgrade_amqqueue_and_retry(
+ do_ensure_rabbit_queue_record_is_initialized(Q),
+ begin
+ Q1 = amqqueue:upgrade(Q),
+ do_ensure_rabbit_queue_record_is_initialized(Q1)
+ end).
+
+do_ensure_rabbit_queue_record_is_initialized(Q) ->
rabbit_misc:execute_mnesia_tx_with_tail(
fun () ->
ok = store_queue(Q),
rabbit_misc:const({ok, Q})
end).
-store_queue(Q = #amqqueue{durable = true}) ->
- ok = mnesia:write(rabbit_durable_queue,
- Q#amqqueue{slave_pids = [],
- sync_slave_pids = [],
- gm_pids = [],
- decorators = undefined}, write),
+-spec store_queue(amqqueue:amqqueue()) -> 'ok'.
+
+store_queue(Q) when ?amqqueue_is_durable(Q) ->
+ Q1 = amqqueue:reset_mirroring_and_decorators(Q),
+ ok = mnesia:write(rabbit_durable_queue, Q1, write),
store_queue_ram(Q);
-store_queue(Q = #amqqueue{durable = false}) ->
+store_queue(Q) when not ?amqqueue_is_durable(Q) ->
store_queue_ram(Q).
store_queue_ram(Q) ->
ok = mnesia:write(rabbit_queue, rabbit_queue_decorator:set(Q), write).
+-spec update_decorators(name()) -> 'ok'.
+
update_decorators(Name) ->
rabbit_misc:execute_mnesia_transaction(
fun() ->
@@ -491,8 +416,12 @@ update_decorators(Name) ->
end
end).
-policy_changed(Q1 = #amqqueue{decorators = Decorators1},
- Q2 = #amqqueue{decorators = Decorators2}) ->
+-spec policy_changed(amqqueue:amqqueue(), amqqueue:amqqueue()) ->
+ 'ok'.
+
+policy_changed(Q1, Q2) ->
+ Decorators1 = amqqueue:get_decorators(Q1),
+ Decorators2 = amqqueue:get_decorators(Q2),
rabbit_mirror_queue_misc:update_mirrors(Q1, Q2),
D1 = rabbit_queue_decorator:select(Decorators1),
D2 = rabbit_queue_decorator:select(Decorators2),
@@ -501,6 +430,13 @@ policy_changed(Q1 = #amqqueue{decorators = Decorators1},
%% mirroring-related has changed - the policy may have changed anyway.
notify_policy_changed(Q1).
+-spec lookup
+ (name()) ->
+ rabbit_types:ok(amqqueue:amqqueue()) |
+ rabbit_types:error('not_found');
+ ([name()]) ->
+ [amqqueue:amqqueue()].
+
lookup([]) -> []; %% optimisation
lookup([Name]) -> ets:lookup(rabbit_queue, Name); %% optimisation
lookup(Names) when is_list(Names) ->
@@ -510,6 +446,8 @@ lookup(Names) when is_list(Names) ->
lookup(Name) ->
rabbit_misc:dirty_read({rabbit_queue, Name}).
+-spec not_found_or_absent(name()) -> not_found_or_absent().
+
not_found_or_absent(Name) ->
%% NB: we assume that the caller has already performed a lookup on
%% rabbit_queue and not found anything
@@ -518,6 +456,8 @@ not_found_or_absent(Name) ->
[Q] -> {absent, Q, nodedown} %% Q exists on stopped node
end.
+-spec not_found_or_absent_dirty(name()) -> not_found_or_absent().
+
not_found_or_absent_dirty(Name) ->
%% We should read from both tables inside a tx, to get a
%% consistent view. But the chances of an inconsistency are small,
@@ -527,25 +467,30 @@ not_found_or_absent_dirty(Name) ->
{ok, Q} -> {absent, Q, nodedown}
end.
+-spec with(name(),
+ qfun(A),
+ fun((not_found_or_absent()) -> rabbit_types:channel_exit())) ->
+ A | rabbit_types:channel_exit().
+
with(Name, F, E) ->
with(Name, F, E, 2000).
-with(Name, F, E, RetriesLeft) ->
+with(#resource{} = Name, F, E, RetriesLeft) ->
case lookup(Name) of
- {ok, Q = #amqqueue{state = live}} when RetriesLeft =:= 0 ->
+ {ok, Q} when ?amqqueue_state_is(Q, live) andalso RetriesLeft =:= 0 ->
%% Something bad happened to that queue, we are bailing out
%% on processing current request.
E({absent, Q, timeout});
- {ok, Q = #amqqueue{state = stopped}} when RetriesLeft =:= 0 ->
+ {ok, Q} when ?amqqueue_state_is(Q, stopped) andalso RetriesLeft =:= 0 ->
%% The queue was stopped and not migrated
E({absent, Q, stopped});
%% The queue process has crashed with unknown error
- {ok, Q = #amqqueue{state = crashed}} ->
+ {ok, Q} when ?amqqueue_state_is(Q, crashed) ->
E({absent, Q, crashed});
%% The queue process has been stopped by a supervisor.
%% In that case a synchronised slave can take over
%% so we should retry.
- {ok, Q = #amqqueue{state = stopped}} ->
+ {ok, Q} when ?amqqueue_state_is(Q, stopped) ->
%% The queue process was stopped by the supervisor
rabbit_misc:with_exit_handler(
fun () -> retry_wait(Q, F, E, RetriesLeft) end,
@@ -553,7 +498,7 @@ with(Name, F, E, RetriesLeft) ->
%% The queue is supposed to be active.
%% The master node can go away or queue can be killed
%% so we retry, waiting for a slave to take over.
- {ok, Q = #amqqueue{state = live}} ->
+ {ok, Q} when ?amqqueue_state_is(Q, live) ->
%% We check is_process_alive(QPid) in case we receive a
%% nodedown (for example) in F() that has nothing to do
%% with the QPid. F() should be written s.t. that this
@@ -567,7 +512,16 @@ with(Name, F, E, RetriesLeft) ->
E(not_found_or_absent_dirty(Name))
end.
-retry_wait(Q = #amqqueue{pid = QPid, name = Name, state = QState}, F, E, RetriesLeft) ->
+-spec retry_wait(amqqueue:amqqueue(),
+ qfun(A),
+ fun((not_found_or_absent()) -> rabbit_types:channel_exit()),
+ non_neg_integer()) ->
+ A | rabbit_types:channel_exit().
+
+retry_wait(Q, F, E, RetriesLeft) ->
+ Name = amqqueue:get_name(Q),
+ QPid = amqqueue:get_pid(Q),
+ QState = amqqueue:get_state(Q),
case {QState, is_replicated(Q)} of
%% We don't want to repeat an operation if
%% there are no slaves to migrate to
@@ -589,40 +543,107 @@ retry_wait(Q = #amqqueue{pid = QPid, name = Name, state = QState}, F, E, Retries
with(Name, F, E, RetriesLeft - 1)
end.
+-spec with(name(), qfun(A)) ->
+ A | rabbit_types:error(not_found_or_absent()).
+
with(Name, F) -> with(Name, F, fun (E) -> {error, E} end).
+-spec with_or_die(name(), qfun(A)) -> A | rabbit_types:channel_exit().
+
with_or_die(Name, F) ->
- with(Name, F, fun (not_found) -> rabbit_misc:not_found(Name);
- ({absent, Q, Reason}) -> rabbit_misc:absent(Q, Reason)
- end).
-
-assert_equivalence(#amqqueue{name = QName,
- durable = Durable,
- auto_delete = AD} = Q,
- Durable1, AD1, Args1, Owner) ->
+ with(Name, F, die_fun(Name)).
+
+-spec die_fun(name()) ->
+ fun((not_found_or_absent()) -> rabbit_types:channel_exit()).
+
+die_fun(Name) ->
+ fun (not_found) -> not_found(Name);
+ ({absent, Q, Reason}) -> absent(Q, Reason)
+ end.
+
+-spec not_found(name()) -> rabbit_types:channel_exit().
+
+not_found(R) -> rabbit_misc:protocol_error(not_found, "no ~s", [rabbit_misc:rs(R)]).
+
+-spec absent(amqqueue:amqqueue(), absent_reason()) ->
+ rabbit_types:channel_exit().
+
+absent(Q, AbsentReason) ->
+ QueueName = amqqueue:get_name(Q),
+ QPid = amqqueue:get_pid(Q),
+ IsDurable = amqqueue:is_durable(Q),
+ priv_absent(QueueName, QPid, IsDurable, AbsentReason).
+
+-spec priv_absent(name(), pid(), boolean(), absent_reason()) ->
+ rabbit_types:channel_exit().
+
+priv_absent(QueueName, QPid, true, nodedown) ->
+ %% The assertion of durability is mainly there because we mention
+ %% durability in the error message. That way we will hopefully
+ %% notice if at some future point our logic changes s.t. we get
+ %% here with non-durable queues.
+ rabbit_misc:protocol_error(
+ not_found,
+ "home node '~s' of durable ~s is down or inaccessible",
+ [node(QPid), rabbit_misc:rs(QueueName)]);
+
+priv_absent(QueueName, _QPid, _IsDurable, stopped) ->
+ rabbit_misc:protocol_error(
+ not_found,
+ "~s process is stopped by supervisor", [rabbit_misc:rs(QueueName)]);
+
+priv_absent(QueueName, _QPid, _IsDurable, crashed) ->
+ rabbit_misc:protocol_error(
+ not_found,
+ "~s has crashed and failed to restart", [rabbit_misc:rs(QueueName)]);
+
+priv_absent(QueueName, _QPid, _IsDurable, timeout) ->
+ rabbit_misc:protocol_error(
+ not_found,
+ "failed to perform operation on ~s due to timeout", [rabbit_misc:rs(QueueName)]).
+
+-spec assert_equivalence
+ (amqqueue:amqqueue(), boolean(), boolean(),
+ rabbit_framing:amqp_table(), rabbit_types:maybe(pid())) ->
+ 'ok' | rabbit_types:channel_exit() | rabbit_types:connection_exit().
+
+assert_equivalence(Q, Durable1, AD1, Args1, Owner) ->
+ QName = amqqueue:get_name(Q),
+ Durable = amqqueue:is_durable(Q),
+ AD = amqqueue:is_auto_delete(Q),
rabbit_misc:assert_field_equivalence(Durable, Durable1, QName, durable),
rabbit_misc:assert_field_equivalence(AD, AD1, QName, auto_delete),
assert_args_equivalence(Q, Args1),
check_exclusive_access(Q, Owner, strict).
+-spec check_exclusive_access(amqqueue:amqqueue(), pid()) ->
+ 'ok' | rabbit_types:channel_exit().
+
check_exclusive_access(Q, Owner) -> check_exclusive_access(Q, Owner, lax).
-check_exclusive_access(#amqqueue{exclusive_owner = Owner}, Owner, _MatchType) ->
+check_exclusive_access(Q, Owner, _MatchType)
+ when ?amqqueue_exclusive_owner_is(Q, Owner) ->
ok;
-check_exclusive_access(#amqqueue{exclusive_owner = none}, _ReaderPid, lax) ->
+check_exclusive_access(Q, _ReaderPid, lax)
+ when ?amqqueue_exclusive_owner_is(Q, none) ->
ok;
-check_exclusive_access(#amqqueue{name = QueueName}, _ReaderPid, _MatchType) ->
+check_exclusive_access(Q, _ReaderPid, _MatchType) ->
+ QueueName = amqqueue:get_name(Q),
rabbit_misc:protocol_error(
resource_locked,
"cannot obtain exclusive access to locked ~s",
[rabbit_misc:rs(QueueName)]).
+-spec with_exclusive_access_or_die(name(), pid(), qfun(A)) ->
+ A | rabbit_types:channel_exit().
+
with_exclusive_access_or_die(Name, ReaderPid, F) ->
with_or_die(Name,
fun (Q) -> check_exclusive_access(Q, ReaderPid), F(Q) end).
-assert_args_equivalence(#amqqueue{name = QueueName, arguments = Args},
- RequiredArgs) ->
+assert_args_equivalence(Q, RequiredArgs) ->
+ QueueName = amqqueue:get_name(Q),
+ Args = amqqueue:get_arguments(Q),
rabbit_misc:assert_args_equivalence(Args, RequiredArgs, QueueName,
[Key || {Key, _Fun} <- declare_args()]).
@@ -749,64 +770,109 @@ check_queue_type({longstr, Val}, _Args) ->
check_queue_type({Type, _}, _Args) ->
{error, {unacceptable_type, Type}}.
+-spec list() -> [amqqueue:amqqueue()].
+
+list() ->
+ list_with_possible_retry(fun do_list/0).
-list() -> mnesia:dirty_match_object(rabbit_queue, #amqqueue{_ = '_'}).
+do_list() ->
+ mnesia:dirty_match_object(rabbit_queue, amqqueue:pattern_match_all()).
+
+-spec list_names() -> [rabbit_amqqueue:name()].
list_names() -> mnesia:dirty_all_keys(rabbit_queue).
-list_names(VHost) -> [Q#amqqueue.name || Q <- list(VHost)].
+list_names(VHost) -> [amqqueue:get_name(Q) || Q <- list(VHost)].
list_local_names() ->
- [ Q#amqqueue.name || #amqqueue{state = State, pid = QPid} = Q <- list(),
- State =/= crashed, is_local_to_node(QPid, node())].
+ [ amqqueue:get_name(Q) || Q <- list(),
+ amqqueue:get_state(Q) =/= crashed, is_local_to_node(amqqueue:get_pid(Q), node())].
+
+-spec list_by_type(atom()) -> [amqqueue:amqqueue()].
list_by_type(Type) ->
{atomic, Qs} =
mnesia:sync_transaction(
fun () ->
mnesia:match_object(rabbit_durable_queue,
- #amqqueue{_ = '_', type = Type}, read)
+ amqqueue:pattern_match_on_type(Type),
+ read)
end),
Qs.
list_local_followers() ->
- [ Q#amqqueue.name
- || #amqqueue{state = State, type = quorum, pid = {_, Leader},
- quorum_nodes = Nodes} = Q <- list(),
- State =/= crashed, Leader =/= node(), lists:member(node(), Nodes)].
+ [ amqqueue:get_name(Q)
+ || Q <- list(),
+ amqqueue:is_quorum(Q),
+ amqqueue:get_state(Q) =/= crashed, amqqueue:get_leader(Q) =/= node(), lists:member(node(), amqqueue:get_quorum_nodes(Q))].
is_local_to_node(QPid, Node) when ?IS_CLASSIC(QPid) ->
Node =:= node(QPid);
is_local_to_node({_, Leader} = QPid, Node) when ?IS_QUORUM(QPid) ->
Node =:= Leader.
-qnode(QPid) when ?IS_CLASSIC(QPid) ->
- node(QPid);
-qnode({_, Node} = QPid) when ?IS_QUORUM(QPid) ->
- Node.
+-spec list(rabbit_types:vhost()) -> [amqqueue:amqqueue()].
list(VHostPath) ->
list(VHostPath, rabbit_queue).
+list(VHostPath, TableName) ->
+ list_with_possible_retry(fun() -> do_list(VHostPath, TableName) end).
+
%% Not dirty_match_object since that would not be transactional when used in a
%% tx context
-list(VHostPath, TableName) ->
+do_list(VHostPath, TableName) ->
mnesia:async_dirty(
fun () ->
mnesia:match_object(
TableName,
- #amqqueue{name = rabbit_misc:r(VHostPath, queue), _ = '_'},
+ amqqueue:pattern_match_on_name(rabbit_misc:r(VHostPath, queue)),
read)
end).
+list_with_possible_retry(Fun) ->
+ %% amqqueue migration:
+ %% The `rabbit_queue` or `rabbit_durable_queue` tables
+ %% might be migrated between the time we query the pattern
+ %% (with the `amqqueue` module) and the time we call
+ %% `mnesia:dirty_match_object()`. This would lead to an empty list
+ %% (no object matching the now incorrect pattern), not a Mnesia
+ %% error.
+ %%
+ %% So if the result is an empty list and the version of the
+ %% `amqqueue` record changed in between, we retry the operation.
+ %%
+ %% However, we don't do this if inside a Mnesia transaction: we
+ %% could end up with a live lock between this started transaction
+ %% and the Mnesia table migration which is blocked (but the
+ %% rabbit_feature_flags lock is held).
+ AmqqueueRecordVersion = amqqueue:record_version_to_use(),
+ case Fun() of
+ [] ->
+ case mnesia:is_transaction() of
+ true ->
+ [];
+ false ->
+ case amqqueue:record_version_to_use() of
+ AmqqueueRecordVersion -> [];
+ _ -> Fun()
+ end
+ end;
+ Ret ->
+ Ret
+ end.
+
+-spec list_down(rabbit_types:vhost()) -> [amqqueue:amqqueue()].
+
list_down(VHostPath) ->
case rabbit_vhost:exists(VHostPath) of
false -> [];
true ->
Present = list(VHostPath),
Durable = list(VHostPath, rabbit_durable_queue),
- PresentS = sets:from_list([N || #amqqueue{name = N} <- Present]),
- sets:to_list(sets:filter(fun (#amqqueue{name = N}) ->
+ PresentS = sets:from_list([amqqueue:get_name(Q) || Q <- Present]),
+ sets:to_list(sets:filter(fun (Q) ->
+ N = amqqueue:get_name(Q),
not sets:is_element(N, PresentS)
end, sets:from_list(Durable)))
end.
@@ -818,20 +884,31 @@ count(VHost) ->
%% won't work here because with master migration of mirrored queues
%% the "ownership" of queues by nodes becomes a non-trivial problem
%% that requires a proper consensus algorithm.
- length(mnesia:dirty_index_read(rabbit_queue, VHost, #amqqueue.vhost))
+ length(list_for_count(VHost))
catch _:Err ->
rabbit_log:error("Failed to fetch number of queues in vhost ~p:~n~p~n",
[VHost, Err]),
0
end.
+list_for_count(VHost) ->
+ list_with_possible_retry(
+ fun() ->
+ mnesia:dirty_index_read(rabbit_queue,
+ VHost,
+ amqqueue:field_vhost())
+ end).
+
+-spec info_keys() -> rabbit_types:info_keys().
+
info_keys() -> rabbit_amqqueue_process:info_keys().
map(Qs, F) -> rabbit_misc:filter_exit_map(F, Qs).
-is_unresponsive(#amqqueue{ state = crashed }, _Timeout) ->
+is_unresponsive(Q, _Timeout) when ?amqqueue_state_is(Q, crashed) ->
false;
-is_unresponsive(#amqqueue{ pid = QPid }, Timeout) ->
+is_unresponsive(Q, Timeout) ->
+ QPid = amqqueue:get_pid(Q),
try
delegate:invoke(QPid, {gen_server2, call, [{info, [name]}, Timeout]}),
false
@@ -841,21 +918,29 @@ is_unresponsive(#amqqueue{ pid = QPid }, Timeout) ->
true
end.
-format(Q = #amqqueue{ type = quorum }) -> rabbit_quorum_queue:format(Q);
+format(Q) when ?amqqueue_is_quorum(Q) -> rabbit_quorum_queue:format(Q);
format(_) -> [].
-info(Q = #amqqueue{ type = quorum }) -> rabbit_quorum_queue:info(Q);
-info(Q = #amqqueue{ state = crashed }) -> info_down(Q, crashed);
-info(Q = #amqqueue{ state = stopped }) -> info_down(Q, stopped);
-info(#amqqueue{ pid = QPid }) -> delegate:invoke(QPid, {gen_server2, call, [info, infinity]}).
+-spec info(amqqueue:amqqueue()) -> rabbit_types:infos().
+
+info(Q) when ?amqqueue_is_quorum(Q) -> rabbit_quorum_queue:info(Q);
+info(Q) when ?amqqueue_state_is(Q, crashed) -> info_down(Q, crashed);
+info(Q) when ?amqqueue_state_is(Q, stopped) -> info_down(Q, stopped);
+info(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ delegate:invoke(QPid, {gen_server2, call, [info, infinity]}).
-info(Q = #amqqueue{ type = quorum }, Items) ->
+-spec info(amqqueue:amqqueue(), rabbit_types:info_keys()) ->
+ rabbit_types:infos().
+
+info(Q, Items) when ?amqqueue_is_quorum(Q) ->
rabbit_quorum_queue:info(Q, Items);
-info(Q = #amqqueue{ state = crashed }, Items) ->
+info(Q, Items) when ?amqqueue_state_is(Q, crashed) ->
info_down(Q, Items, crashed);
-info(Q = #amqqueue{ state = stopped }, Items) ->
+info(Q, Items) when ?amqqueue_state_is(Q, stopped) ->
info_down(Q, Items, stopped);
-info(#amqqueue{ pid = QPid }, Items) ->
+info(Q, Items) ->
+ QPid = amqqueue:get_pid(Q),
case delegate:invoke(QPid, {gen_server2, call, [{info, Items}, infinity]}) of
{ok, Res} -> Res;
{error, Error} -> throw(Error)
@@ -867,23 +952,28 @@ info_down(Q, DownReason) ->
info_down(Q, Items, DownReason) ->
[{Item, i_down(Item, Q, DownReason)} || Item <- Items].
-i_down(name, #amqqueue{name = Name}, _) -> Name;
-i_down(durable, #amqqueue{durable = Dur}, _) -> Dur;
-i_down(auto_delete, #amqqueue{auto_delete = AD}, _) -> AD;
-i_down(arguments, #amqqueue{arguments = Args}, _) -> Args;
-i_down(pid, #amqqueue{pid = QPid}, _) -> QPid;
-i_down(recoverable_slaves, #amqqueue{recoverable_slaves = RS}, _) -> RS;
-i_down(state, _Q, DownReason) -> DownReason;
+i_down(name, Q, _) -> amqqueue:get_name(Q);
+i_down(durable, Q, _) -> amqqueue:is_durable(Q);
+i_down(auto_delete, Q, _) -> amqqueue:is_auto_delete(Q);
+i_down(arguments, Q, _) -> amqqueue:get_arguments(Q);
+i_down(pid, Q, _) -> amqqueue:get_pid(Q);
+i_down(recoverable_slaves, Q, _) -> amqqueue:get_recoverable_slaves(Q);
+i_down(state, _Q, DownReason) -> DownReason;
i_down(K, _Q, _DownReason) ->
case lists:member(K, rabbit_amqqueue_process:info_keys()) of
true -> '';
false -> throw({bad_argument, K})
end.
+-spec info_all(rabbit_types:vhost()) -> [rabbit_types:infos()].
+
info_all(VHostPath) ->
map(list(VHostPath), fun (Q) -> info(Q) end) ++
map(list_down(VHostPath), fun (Q) -> info_down(Q, down) end).
+-spec info_all(rabbit_types:vhost(), rabbit_types:info_keys()) ->
+ [rabbit_types:infos()].
+
info_all(VHostPath, Items) ->
map(list(VHostPath), fun (Q) -> info(Q, Items) end) ++
map(list_down(VHostPath), fun (Q) -> info_down(Q, Items, down) end).
@@ -919,24 +1009,47 @@ info_local(VHostPath) ->
map(list_local(VHostPath), fun (Q) -> info(Q, [name]) end).
list_local(VHostPath) ->
- [ Q || #amqqueue{state = State, pid = QPid} = Q <- list(VHostPath),
- State =/= crashed, is_local_to_node(QPid, node()) ].
+ [Q || Q <- list(VHostPath),
+ amqqueue:get_state(Q) =/= crashed, is_local_to_node(amqqueue:get_pid(Q), node())].
+
+-spec force_event_refresh(reference()) -> 'ok'.
+
+force_event_refresh(Ref) ->
+ [gen_server2:cast(amqqueue:get_pid(Q),
+ {force_event_refresh, Ref}) || Q <- list()],
+ ok.
+
+-spec notify_policy_changed(amqqueue:amqqueue()) -> 'ok'.
-notify_policy_changed(#amqqueue{pid = QPid}) when ?IS_CLASSIC(QPid) ->
+notify_policy_changed(Q) when ?amqqueue_is_classic(Q) ->
+ QPid = amqqueue:get_pid(Q),
gen_server2:cast(QPid, policy_changed);
-notify_policy_changed(#amqqueue{pid = QPid,
- name = QName}) when ?IS_QUORUM(QPid) ->
+notify_policy_changed(Q) when ?amqqueue_is_quorum(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ QName = amqqueue:get_name(Q),
rabbit_quorum_queue:policy_changed(QName, QPid).
-consumers(#amqqueue{pid = QPid}) when ?IS_CLASSIC(QPid) ->
+-spec consumers(amqqueue:amqqueue()) ->
+ [{pid(), rabbit_types:ctag(), boolean(), non_neg_integer(),
+ rabbit_framing:amqp_table(), rabbit_types:username()}].
+
+consumers(Q) when ?amqqueue_is_classic(Q) ->
+ QPid = amqqueue:get_pid(Q),
delegate:invoke(QPid, {gen_server2, call, [consumers, infinity]});
-consumers(#amqqueue{pid = QPid}) when ?IS_QUORUM(QPid) ->
+consumers(Q) when ?amqqueue_is_quorum(Q) ->
+ QPid = amqqueue:get_pid(Q),
{ok, {_, Result}, _} = ra:local_query(QPid,
fun rabbit_fifo:query_consumers/1),
maps:values(Result).
+-spec consumer_info_keys() -> rabbit_types:info_keys().
+
consumer_info_keys() -> ?CONSUMER_INFO_KEYS.
+-spec consumers_all(rabbit_types:vhost()) ->
+ [{name(), pid(), rabbit_types:ctag(), boolean(),
+ non_neg_integer(), rabbit_framing:amqp_table()}].
+
consumers_all(VHostPath) ->
ConsumerInfoKeys = consumer_info_keys(),
lists:append(
@@ -957,24 +1070,38 @@ emit_consumers_local(VHostPath, Ref, AggregatorPid) ->
get_queue_consumer_info(Q, ConsumerInfoKeys) ->
[lists:zip(ConsumerInfoKeys,
- [Q#amqqueue.name, ChPid, CTag,
+ [amqqueue:get_name(Q), ChPid, CTag,
AckRequired, Prefetch, Active, ActivityStatus, Args]) ||
{ChPid, CTag, AckRequired, Prefetch, Active, ActivityStatus, Args, _} <- consumers(Q)].
-stat(#amqqueue{type = quorum} = Q) -> rabbit_quorum_queue:stat(Q);
-stat(#amqqueue{pid = QPid}) -> delegate:invoke(QPid, {gen_server2, call, [stat, infinity]}).
+-spec stat(amqqueue:amqqueue()) ->
+ {'ok', non_neg_integer(), non_neg_integer()}.
+
+stat(Q) when ?amqqueue_is_quorum(Q) -> rabbit_quorum_queue:stat(Q);
+stat(Q) -> delegate:invoke(amqqueue:get_pid(Q), {gen_server2, call, [stat, infinity]}).
+
+-spec pid_of(amqqueue:amqqueue()) ->
+ {'ok', pid()} | rabbit_types:error('not_found').
+
+pid_of(Q) -> amqqueue:get_pid(Q).
+
+-spec pid_of(rabbit_types:vhost(), rabbit_misc:resource_name()) ->
+ {'ok', pid()} | rabbit_types:error('not_found').
-pid_of(#amqqueue{pid = Pid}) -> Pid.
pid_of(VHost, QueueName) ->
case lookup(rabbit_misc:r(VHost, queue, QueueName)) of
{ok, Q} -> pid_of(Q);
{error, not_found} = E -> E
end.
+-spec delete_exclusive(qpids(), pid()) -> 'ok'.
+
delete_exclusive(QPids, ConnId) ->
[gen_server2:cast(QPid, {delete_exclusive, ConnId}) || QPid <- QPids],
ok.
+-spec delete_immediately(qpids()) -> 'ok'.
+
delete_immediately(QPids) ->
{Classic, Quorum} = filter_pid_per_type(QPids),
[gen_server2:cast(QPid, delete_immediately) || QPid <- Classic],
@@ -990,15 +1117,28 @@ delete_immediately_by_resource(Resources) ->
|| {Resource, QPid} <- Quorum],
ok.
-delete(#amqqueue{ type = quorum} = Q,
- IfUnused, IfEmpty, ActingUser) ->
+-spec delete
+ (amqqueue:amqqueue(), 'false', 'false', rabbit_types:username()) ->
+ qlen();
+ (amqqueue:amqqueue(), 'true' , 'false', rabbit_types:username()) ->
+ qlen() | rabbit_types:error('in_use');
+ (amqqueue:amqqueue(), 'false', 'true', rabbit_types:username()) ->
+ qlen() | rabbit_types:error('not_empty');
+ (amqqueue:amqqueue(), 'true' , 'true', rabbit_types:username()) ->
+ qlen() |
+ rabbit_types:error('in_use') |
+ rabbit_types:error('not_empty').
+
+delete(Q,
+ IfUnused, IfEmpty, ActingUser) when ?amqqueue_is_quorum(Q) ->
rabbit_quorum_queue:delete(Q, IfUnused, IfEmpty, ActingUser);
delete(Q, IfUnused, IfEmpty, ActingUser) ->
case wait_for_promoted_or_stopped(Q) of
- {promoted, #amqqueue{pid = QPid}} ->
+ {promoted, Q1} ->
+ QPid = amqqueue:get_pid(Q1),
delegate:invoke(QPid, {gen_server2, call, [{delete, IfUnused, IfEmpty, ActingUser}, infinity]});
{stopped, Q1} ->
- #resource{name = Name, virtual_host = Vhost} = Q1#amqqueue.name,
+ #resource{name = Name, virtual_host = Vhost} = amqqueue:get_name(Q1),
case IfEmpty of
true ->
rabbit_log:error("Queue ~s in vhost ~s has its master node down and "
@@ -1020,10 +1160,16 @@ delete(Q, IfUnused, IfEmpty, ActingUser) ->
{ok, 0}
end.
--spec wait_for_promoted_or_stopped(#amqqueue{}) -> {promoted, #amqqueue{}} | {stopped, #amqqueue{}} | {error, not_found}.
-wait_for_promoted_or_stopped(#amqqueue{name = QName}) ->
+-spec wait_for_promoted_or_stopped(amqqueue:amqqueue()) ->
+ {promoted, amqqueue:amqqueue()} |
+ {stopped, amqqueue:amqqueue()} |
+ {error, not_found}.
+wait_for_promoted_or_stopped(Q0) ->
+ QName = amqqueue:get_name(Q0),
case lookup(QName) of
- {ok, Q = #amqqueue{pid = QPid, slave_pids = SPids}} ->
+ {ok, Q} ->
+ QPid = amqqueue:get_pid(Q),
+ SPids = amqqueue:get_slave_pids(Q),
case rabbit_mnesia:is_process_alive(QPid) of
true -> {promoted, Q};
false ->
@@ -1043,22 +1189,36 @@ wait_for_promoted_or_stopped(#amqqueue{name = QName}) ->
{error, not_found}
end.
+-spec delete_crashed(amqqueue:amqqueue()) -> 'ok'.
+
delete_crashed(Q) ->
delete_crashed(Q, ?INTERNAL_USER).
-delete_crashed(#amqqueue{ pid = QPid } = Q, ActingUser) ->
- ok = rpc:call(qnode(QPid), ?MODULE, delete_crashed_internal, [Q, ActingUser]).
+delete_crashed(Q, ActingUser) ->
+ ok = rpc:call(amqqueue:qnode(Q), ?MODULE, delete_crashed_internal, [Q, ActingUser]).
-delete_crashed_internal(Q = #amqqueue{ name = QName }, ActingUser) ->
+-spec delete_crashed_internal(amqqueue:amqqueue(), rabbit_types:username()) -> 'ok'.
+
+delete_crashed_internal(Q, ActingUser) ->
+ QName = amqqueue:get_name(Q),
{ok, BQ} = application:get_env(rabbit, backing_queue_module),
BQ:delete_crashed(Q),
ok = internal_delete(QName, ActingUser).
-purge(#amqqueue{ pid = QPid, type = classic}) ->
+-spec purge(amqqueue:amqqueue()) -> {ok, qlen()}.
+
+purge(Q) when ?amqqueue_is_classic(Q) ->
+ QPid = amqqueue:get_pid(Q),
delegate:invoke(QPid, {gen_server2, call, [purge, infinity]});
-purge(#amqqueue{ pid = NodeId, type = quorum}) ->
+purge(Q) when ?amqqueue_is_quorum(Q) ->
+ NodeId = amqqueue:get_pid(Q),
rabbit_quorum_queue:purge(NodeId).
+-spec requeue(pid(),
+ {rabbit_fifo:consumer_tag(), [msg_id()]},
+ pid(),
+ quorum_states()) ->
+ 'ok'.
requeue(QPid, {_, MsgIds}, ChPid, QuorumStates) when ?IS_CLASSIC(QPid) ->
ok = delegate:invoke(QPid, {gen_server2, call, [{requeue, MsgIds, ChPid}, infinity]}),
@@ -1074,6 +1234,12 @@ requeue({Name, _} = QPid, {CTag, MsgIds}, _ChPid, QuorumStates)
QuorumStates
end.
+-spec ack(pid(),
+ {rabbit_fifo:consumer_tag(), [msg_id()]},
+ pid(),
+ quorum_states()) ->
+ quorum_states().
+
ack(QPid, {_, MsgIds}, ChPid, QueueStates) when ?IS_CLASSIC(QPid) ->
delegate:invoke_no_result(QPid, {gen_server2, cast, [{ack, MsgIds, ChPid}]}),
QueueStates;
@@ -1088,6 +1254,13 @@ ack({Name, _} = QPid, {CTag, MsgIds}, _ChPid, QuorumStates)
QuorumStates
end.
+-spec reject(pid() | amqqueue:ra_server_id(),
+ boolean(),
+ {rabbit_fifo:consumer_tag(), [msg_id()]},
+ pid(),
+ quorum_states()) ->
+ quorum_states().
+
reject(QPid, Requeue, {_, MsgIds}, ChPid, QStates) when ?IS_CLASSIC(QPid) ->
ok = delegate:invoke_no_result(QPid, {gen_server2, cast,
[{reject, Requeue, MsgIds, ChPid}]}),
@@ -1104,9 +1277,14 @@ reject({Name, _} = QPid, Requeue, {CTag, MsgIds}, _ChPid, QuorumStates)
QuorumStates
end.
+-spec notify_down_all(qpids(), pid()) -> ok_or_errors().
+
notify_down_all(QPids, ChPid) ->
notify_down_all(QPids, ChPid, ?CHANNEL_OPERATION_TIMEOUT).
+-spec notify_down_all(qpids(), pid(), non_neg_integer()) ->
+ ok_or_errors().
+
notify_down_all(QPids, ChPid, Timeout) ->
case rpc:call(node(), delegate, invoke,
[QPids, {gen_server2, call, [{notify_down, ChPid}, infinity]}], Timeout) of
@@ -1124,30 +1302,51 @@ notify_down_all(QPids, ChPid, Timeout) ->
Error -> {error, Error}
end.
+-spec activate_limit_all(qpids(), pid()) -> ok.
+
activate_limit_all(QRefs, ChPid) ->
QPids = [P || P <- QRefs, ?IS_CLASSIC(P)],
delegate:invoke_no_result(QPids, {gen_server2, cast,
[{activate_limit, ChPid}]}).
-credit(#amqqueue{pid = QPid, type = classic}, ChPid, CTag, Credit,
- Drain, QStates) ->
+-spec credit(amqqueue:amqqueue(),
+ pid(),
+ rabbit_types:ctag(),
+ non_neg_integer(),
+ boolean(),
+ quorum_states()) ->
+ {'ok', quorum_states()}.
+
+credit(Q, ChPid, CTag, Credit,
+ Drain, QStates) when ?amqqueue_is_classic(Q) ->
+ QPid = amqqueue:get_pid(Q),
delegate:invoke_no_result(QPid, {gen_server2, cast,
[{credit, ChPid, CTag, Credit, Drain}]}),
{ok, QStates};
-credit(#amqqueue{pid = {Name, _} = Id, name = QName, type = quorum},
+credit(Q,
_ChPid, CTag, Credit,
- Drain, QStates) ->
+ Drain, QStates) when ?amqqueue_is_quorum(Q) ->
+ {Name, _} = Id = amqqueue:get_pid(Q),
+ QName = amqqueue:get_name(Q),
QState0 = get_quorum_state(Id, QName, QStates),
{ok, QState} = rabbit_quorum_queue:credit(CTag, Credit, Drain, QState0),
{ok, maps:put(Name, QState, QStates)}.
+-spec basic_get(amqqueue:amqqueue(), pid(), boolean(), pid(), rabbit_types:ctag(),
+ #{Name :: atom() => rabbit_fifo_client:state()}) ->
+ {'ok', non_neg_integer(), qmsg(), quorum_states()} |
+ {'empty', quorum_states()} |
+ rabbit_types:channel_exit().
-basic_get(#amqqueue{pid = QPid, type = classic}, ChPid, NoAck, LimiterPid,
- _CTag, _) ->
+basic_get(Q, ChPid, NoAck, LimiterPid, _CTag, _)
+ when ?amqqueue_is_classic(Q) ->
+ QPid = amqqueue:get_pid(Q),
delegate:invoke(QPid, {gen_server2, call,
[{basic_get, ChPid, NoAck, LimiterPid}, infinity]});
-basic_get(#amqqueue{pid = {Name, _} = Id, type = quorum, name = QName} = Q, _ChPid, NoAck,
- _LimiterPid, CTag, QStates) ->
+basic_get(Q, _ChPid, NoAck, _LimiterPid, CTag, QStates)
+ when ?amqqueue_is_quorum(Q) ->
+ {Name, _} = Id = amqqueue:get_pid(Q),
+ QName = amqqueue:get_name(Q),
QState0 = get_quorum_state(Id, QName, QStates),
case rabbit_quorum_queue:basic_get(Q, NoAck, CTag, QState0) of
{ok, empty, QState} ->
@@ -1160,9 +1359,19 @@ basic_get(#amqqueue{pid = {Name, _} = Id, type = quorum, name = QName} = Q, _ChP
[rabbit_misc:rs(QName), Reason])
end.
-basic_consume(#amqqueue{pid = QPid, name = QName, type = classic}, NoAck, ChPid,
- LimiterPid, LimiterActive, ConsumerPrefetchCount, ConsumerTag,
- ExclusiveConsume, Args, OkMsg, ActingUser, QState) ->
+-spec basic_consume
+ (amqqueue:amqqueue(), boolean(), pid(), pid(), boolean(),
+ non_neg_integer(), rabbit_types:ctag(), boolean(),
+ rabbit_framing:amqp_table(), any(), rabbit_types:username(),
+ #{Name :: atom() => rabbit_fifo_client:state()}) ->
+ rabbit_types:ok_or_error('exclusive_consume_unavailable').
+
+basic_consume(Q, NoAck, ChPid, LimiterPid,
+ LimiterActive, ConsumerPrefetchCount, ConsumerTag,
+ ExclusiveConsume, Args, OkMsg, ActingUser, QState)
+ when ?amqqueue_is_classic(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ QName = amqqueue:get_name(Q),
ok = check_consume_arguments(QName, Args),
case delegate:invoke(QPid,
{gen_server2, call,
@@ -1174,14 +1383,18 @@ basic_consume(#amqqueue{pid = QPid, name = QName, type = classic}, NoAck, ChPid,
Err ->
Err
end;
-basic_consume(#amqqueue{type = quorum}, _NoAck, _ChPid,
+basic_consume(Q, _NoAck, _ChPid,
_LimiterPid, true, _ConsumerPrefetchCount, _ConsumerTag,
- _ExclusiveConsume, _Args, _OkMsg, _ActingUser, _QStates) ->
+ _ExclusiveConsume, _Args, _OkMsg, _ActingUser, _QStates)
+ when ?amqqueue_is_quorum(Q) ->
{error, global_qos_not_supported_for_queue_type};
-basic_consume(#amqqueue{pid = {Name, _} = Id, name = QName, type = quorum} = Q,
+basic_consume(Q,
NoAck, ChPid, _LimiterPid, _LimiterActive, ConsumerPrefetchCount,
ConsumerTag, ExclusiveConsume, Args, OkMsg,
- ActingUser, QStates) ->
+ ActingUser, QStates)
+ when ?amqqueue_is_quorum(Q) ->
+ {Name, _} = Id = amqqueue:get_pid(Q),
+ QName = amqqueue:get_name(Q),
ok = check_consume_arguments(QName, Args),
QState0 = get_quorum_state(Id, QName, QStates),
{ok, QState} = rabbit_quorum_queue:basic_consume(Q, NoAck, ChPid,
@@ -1192,8 +1405,15 @@ basic_consume(#amqqueue{pid = {Name, _} = Id, name = QName, type = quorum} = Q,
OkMsg, QState0),
{ok, maps:put(Name, QState, QStates)}.
-basic_cancel(#amqqueue{pid = QPid, type = classic}, ChPid, ConsumerTag, OkMsg, ActingUser,
- QState) ->
+-spec basic_cancel
+ (amqqueue:amqqueue(), pid(), rabbit_types:ctag(), any(),
+ rabbit_types:username(), #{Name :: atom() => rabbit_fifo_client:state()}) ->
+ 'ok' | {'ok', #{Name :: atom() => rabbit_fifo_client:state()}}.
+
+basic_cancel(Q, ChPid, ConsumerTag, OkMsg, ActingUser,
+ QState)
+ when ?amqqueue_is_classic(Q) ->
+ QPid = amqqueue:get_pid(Q),
case delegate:invoke(QPid, {gen_server2, call,
[{basic_cancel, ChPid, ConsumerTag, OkMsg, ActingUser},
infinity]}) of
@@ -1201,13 +1421,18 @@ basic_cancel(#amqqueue{pid = QPid, type = classic}, ChPid, ConsumerTag, OkMsg, A
{ok, QState};
Err -> Err
end;
-basic_cancel(#amqqueue{pid = {Name, _} = Id, type = quorum}, ChPid,
- ConsumerTag, OkMsg, _ActingUser, QStates) ->
+basic_cancel(Q, ChPid,
+ ConsumerTag, OkMsg, _ActingUser, QStates)
+ when ?amqqueue_is_quorum(Q) ->
+ {Name, _} = Id = amqqueue:get_pid(Q),
QState0 = get_quorum_state(Id, QStates),
{ok, QState} = rabbit_quorum_queue:basic_cancel(ConsumerTag, ChPid, OkMsg, QState0),
{ok, maps:put(Name, QState, QStates)}.
-notify_decorators(#amqqueue{pid = QPid}) ->
+-spec notify_decorators(amqqueue:amqqueue()) -> 'ok'.
+
+notify_decorators(Q) ->
+ QPid = amqqueue:get_pid(Q),
delegate:invoke_no_result(QPid, {gen_server2, cast, [notify_decorators]}).
notify_sent(QPid, ChPid) ->
@@ -1216,6 +1441,8 @@ notify_sent(QPid, ChPid) ->
notify_sent_queue_down(QPid) ->
rabbit_amqqueue_common:notify_sent_queue_down(QPid).
+-spec resume(pid(), pid()) -> 'ok'.
+
resume(QPid, ChPid) -> delegate:invoke_no_result(QPid, {gen_server2, cast, [{resume, ChPid}]}).
internal_delete1(QueueName, OnlyDurable) ->
@@ -1236,6 +1463,8 @@ internal_delete1(QueueName, OnlyDurable, Reason) ->
%% after the transaction.
rabbit_binding:remove_for_destination(QueueName, OnlyDurable).
+-spec internal_delete(name(), rabbit_types:username()) -> 'ok'.
+
internal_delete(QueueName, ActingUser) ->
internal_delete(QueueName, ActingUser, normal).
@@ -1260,6 +1489,8 @@ internal_delete(QueueName, ActingUser, Reason) ->
end
end).
+-spec forget_all_durable(node()) -> 'ok'.
+
forget_all_durable(Node) ->
%% Note rabbit is not running so we avoid e.g. the worker pool. Also why
%% we don't invoke the return from rabbit_binding:process_deletions/1.
@@ -1267,10 +1498,10 @@ forget_all_durable(Node) ->
mnesia:sync_transaction(
fun () ->
Qs = mnesia:match_object(rabbit_durable_queue,
- #amqqueue{_ = '_'}, write),
+ amqqueue:pattern_match_all(), write),
[forget_node_for_queue(Node, Q) ||
- #amqqueue{pid = Pid} = Q <- Qs,
- is_local_to_node(Pid, Node)],
+ Q <- Qs,
+ is_local_to_node(amqqueue:get_pid(Q), Node)],
ok
end),
ok.
@@ -1278,26 +1509,30 @@ forget_all_durable(Node) ->
%% Try to promote a slave while down - it should recover as a
%% master. We try to take the oldest slave here for best chance of
%% recovery.
-forget_node_for_queue(DeadNode, Q = #amqqueue{type = quorum,
- quorum_nodes = QN}) ->
+forget_node_for_queue(DeadNode, Q)
+ when ?amqqueue_is_quorum(Q) ->
+ QN = amqqueue:get_quorum_nodes(Q),
forget_node_for_queue(DeadNode, QN, Q);
-forget_node_for_queue(DeadNode, Q = #amqqueue{recoverable_slaves = RS}) ->
+forget_node_for_queue(DeadNode, Q) ->
+ RS = amqqueue:get_recoverable_slaves(Q),
forget_node_for_queue(DeadNode, RS, Q).
-forget_node_for_queue(_DeadNode, [], #amqqueue{name = Name}) ->
+forget_node_for_queue(_DeadNode, [], Q) ->
%% No slaves to recover from, queue is gone.
%% Don't process_deletions since that just calls callbacks and we
%% are not really up.
+ Name = amqqueue:get_name(Q),
internal_delete1(Name, true);
%% Should not happen, but let's be conservative.
forget_node_for_queue(DeadNode, [DeadNode | T], Q) ->
forget_node_for_queue(DeadNode, T, Q);
-forget_node_for_queue(DeadNode, [H|T], #amqqueue{type = Type} = Q) ->
+forget_node_for_queue(DeadNode, [H|T], Q) when ?is_amqqueue(Q) ->
+ Type = amqqueue:get_type(Q),
case {node_permits_offline_promotion(H), Type} of
{false, _} -> forget_node_for_queue(DeadNode, T, Q);
- {true, classic} -> Q1 = Q#amqqueue{pid = rabbit_misc:node_to_fake_pid(H)},
+ {true, classic} -> Q1 = amqqueue:set_pid(Q, rabbit_misc:node_to_fake_pid(H)),
ok = mnesia:write(rabbit_durable_queue, Q1, write);
{true, quorum} -> ok
end.
@@ -1317,49 +1552,73 @@ node_permits_offline_promotion(Node) ->
%%
%% [2] This is simpler; as long as it's down that's OK
+-spec run_backing_queue
+ (pid(), atom(), (fun ((atom(), A) -> {[rabbit_types:msg_id()], A}))) ->
+ 'ok'.
+
run_backing_queue(QPid, Mod, Fun) ->
gen_server2:cast(QPid, {run_backing_queue, Mod, Fun}).
+-spec set_ram_duration_target(pid(), number() | 'infinity') -> 'ok'.
+
set_ram_duration_target(QPid, Duration) ->
gen_server2:cast(QPid, {set_ram_duration_target, Duration}).
+-spec set_maximum_since_use(pid(), non_neg_integer()) -> 'ok'.
+
set_maximum_since_use(QPid, Age) ->
gen_server2:cast(QPid, {set_maximum_since_use, Age}).
+-spec update_mirroring(pid()) -> 'ok'.
+
update_mirroring(QPid) ->
ok = delegate:invoke_no_result(QPid, {gen_server2, cast, [update_mirroring]}).
-sync_mirrors(#amqqueue{pid = QPid}) ->
+-spec sync_mirrors(amqqueue:amqqueue() | pid()) ->
+ 'ok' | rabbit_types:error('not_mirrored').
+
+sync_mirrors(Q) when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
delegate:invoke(QPid, {gen_server2, call, [sync_mirrors, infinity]});
sync_mirrors(QPid) ->
delegate:invoke(QPid, {gen_server2, call, [sync_mirrors, infinity]}).
-cancel_sync_mirrors(#amqqueue{pid = QPid}) ->
+
+-spec cancel_sync_mirrors(amqqueue:amqqueue() | pid()) ->
+ 'ok' | {'ok', 'not_syncing'}.
+
+cancel_sync_mirrors(Q) when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
delegate:invoke(QPid, {gen_server2, call, [cancel_sync_mirrors, infinity]});
cancel_sync_mirrors(QPid) ->
delegate:invoke(QPid, {gen_server2, call, [cancel_sync_mirrors, infinity]}).
-is_replicated(#amqqueue{type = quorum}) ->
+-spec is_replicated(amqqueue:amqqueue()) -> boolean().
+
+is_replicated(Q) when ?amqqueue_is_quorum(Q) ->
true;
is_replicated(Q) ->
rabbit_mirror_queue_misc:is_mirrored(Q).
-is_dead_exclusive(#amqqueue{exclusive_owner = none}) ->
+is_dead_exclusive(Q) when ?amqqueue_exclusive_owner_is(Q, none) ->
false;
-is_dead_exclusive(#amqqueue{exclusive_owner = Pid}) when is_pid(Pid) ->
+is_dead_exclusive(Q) when ?amqqueue_exclusive_owner_is_pid(Q) ->
+ Pid = amqqueue:get_pid(Q),
not rabbit_mnesia:is_process_alive(Pid).
+-spec on_node_up(node()) -> 'ok'.
+
on_node_up(Node) ->
ok = rabbit_misc:execute_mnesia_transaction(
fun () ->
Qs = mnesia:match_object(rabbit_queue,
- #amqqueue{_ = '_'}, write),
+ amqqueue:pattern_match_all(), write),
[maybe_clear_recoverable_node(Node, Q) || Q <- Qs],
ok
end).
-maybe_clear_recoverable_node(Node,
- #amqqueue{sync_slave_pids = SPids,
- recoverable_slaves = RSs} = Q) ->
+maybe_clear_recoverable_node(Node, Q) ->
+ SPids = amqqueue:get_sync_slave_pids(Q),
+ RSs = amqqueue:get_recoverable_slaves(Q),
case lists:member(Node, RSs) of
true ->
%% There is a race with
@@ -1381,13 +1640,15 @@ maybe_clear_recoverable_node(Node,
if
DoClearNode -> RSs1 = RSs -- [Node],
store_queue(
- Q#amqqueue{recoverable_slaves = RSs1});
+ amqqueue:set_recoverable_slaves(Q, RSs1));
true -> ok
end;
false ->
ok
end.
+-spec on_node_down(node()) -> 'ok'.
+
on_node_down(Node) ->
{QueueNames, QueueDeletions} = delete_queues_on_node_down(Node),
notify_queue_binding_deletions(QueueDeletions),
@@ -1421,10 +1682,10 @@ partition_queues(T) ->
queues_to_delete_when_node_down(NodeDown) ->
rabbit_misc:execute_mnesia_transaction(fun () ->
- qlc:e(qlc:q([QName ||
- #amqqueue{name = QName, pid = Pid} = Q <- mnesia:table(rabbit_queue),
- qnode(Pid) == NodeDown andalso
- not rabbit_mnesia:is_process_alive(Pid) andalso
+ qlc:e(qlc:q([amqqueue:get_name(Q) ||
+ Q <- mnesia:table(rabbit_queue),
+ amqqueue:qnode(Q) == NodeDown andalso
+ not rabbit_mnesia:is_process_alive(amqqueue:get_pid(Q)) andalso
(not rabbit_amqqueue:is_replicated(Q) orelse
rabbit_amqqueue:is_dead_exclusive(Q))]
))
@@ -1453,27 +1714,44 @@ notify_queues_deleted(QueueDeletions) ->
end,
QueueDeletions).
+-spec pseudo_queue(name(), pid()) -> amqqueue:amqqueue().
+
pseudo_queue(QueueName, Pid) ->
- #amqqueue{name = QueueName,
- durable = false,
- auto_delete = false,
- arguments = [],
- pid = Pid,
- slave_pids = []}.
-
-immutable(Q) -> Q#amqqueue{pid = none,
- slave_pids = none,
- sync_slave_pids = none,
- recoverable_slaves = none,
- gm_pids = none,
- policy = none,
- decorators = none,
- state = none}.
+ pseudo_queue(QueueName, Pid, false).
+
+-spec pseudo_queue(name(), pid(), boolean()) -> amqqueue:amqqueue().
+
+pseudo_queue(#resource{kind = queue} = QueueName, Pid, Durable)
+ when is_pid(Pid) andalso
+ is_boolean(Durable) ->
+ amqqueue:new(QueueName,
+ Pid,
+ Durable,
+ false,
+ none, % Owner,
+ [],
+ undefined, % VHost,
+ #{user => undefined}, % ActingUser
+ classic % Type
+ ).
+
+-spec immutable(amqqueue:amqqueue()) -> amqqueue:amqqueue().
+
+immutable(Q) -> amqqueue:set_immutable(Q).
+
+-spec deliver([amqqueue:amqqueue()], rabbit_types:delivery()) -> 'ok'.
deliver(Qs, Delivery) ->
deliver(Qs, Delivery, untracked),
ok.
+-spec deliver([amqqueue:amqqueue()],
+ rabbit_types:delivery(),
+ quorum_states() | 'untracked') ->
+ {qpids(),
+ [{amqqueue:ra_server_id(), name()}],
+ quorum_states()}.
+
deliver([], _Delivery, QueueState) ->
%% /dev/null optimisation
{[], [], QueueState};
@@ -1530,17 +1808,26 @@ deliver(Qs, Delivery = #delivery{flow = Flow,
{QPids, QuorumPids, QueueState}.
qpids([]) -> {[], [], []}; %% optimisation
-qpids([#amqqueue{pid = {LocalName, LeaderNode}, type = quorum, name = QName}]) ->
+qpids([Q]) when ?amqqueue_is_quorum(Q) ->
+ QName = amqqueue:get_name(Q),
+ {LocalName, LeaderNode} = amqqueue:get_pid(Q),
{[{{LocalName, LeaderNode}, QName}], [], []}; %% opt
-qpids([#amqqueue{pid = QPid, slave_pids = SPids}]) ->
+qpids([Q]) ->
+ QPid = amqqueue:get_pid(Q),
+ SPids = amqqueue:get_slave_pids(Q),
{[], [QPid], SPids}; %% opt
qpids(Qs) ->
{QuoPids, MPids, SPids} =
- lists:foldl(fun (#amqqueue{pid = QPid, type = quorum, name = QName},
- {QuoPidAcc, MPidAcc, SPidAcc}) ->
+ lists:foldl(fun (Q,
+ {QuoPidAcc, MPidAcc, SPidAcc})
+ when ?amqqueue_is_quorum(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ QName = amqqueue:get_name(Q),
{[{QPid, QName} | QuoPidAcc], MPidAcc, SPidAcc};
- (#amqqueue{pid = QPid, slave_pids = SPids},
+ (Q,
{QuoPidAcc, MPidAcc, SPidAcc}) ->
+ QPid = amqqueue:get_pid(Q),
+ SPids = amqqueue:get_slave_pids(Q),
{QuoPidAcc, [QPid | MPidAcc], [SPids | SPidAcc]}
end, {[], [], []}, Qs),
{QuoPids, MPids, lists:append(SPids)}.
diff --git a/src/rabbit_amqqueue_process.erl b/src/rabbit_amqqueue_process.erl
index 5782bc6e23..c3ba4a5c59 100644
--- a/src/rabbit_amqqueue_process.erl
+++ b/src/rabbit_amqqueue_process.erl
@@ -17,6 +17,7 @@
-module(rabbit_amqqueue_process).
-include_lib("rabbit_common/include/rabbit.hrl").
-include_lib("rabbit_common/include/rabbit_framing.hrl").
+-include("amqqueue.hrl").
-behaviour(gen_server2).
@@ -35,7 +36,7 @@
%% Queue's state
-record(q, {
%% an #amqqueue record
- q,
+ q :: amqqueue:amqqueue(),
%% none | {exclusive consumer channel PID, consumer tag} | {single active consumer channel PID, consumer}
active_consumer,
%% Set to true if a queue has ever had a consumer.
@@ -101,14 +102,6 @@
%%----------------------------------------------------------------------------
--spec info_keys() -> rabbit_types:info_keys().
--spec init_with_backing_queue_state
- (rabbit_types:amqqueue(), atom(), tuple(), any(),
- [rabbit_types:delivery()], pmon:pmon(), gb_trees:tree()) ->
- #q{}.
-
-%%----------------------------------------------------------------------------
-
-define(STATISTICS_KEYS,
[messages_ready,
messages_unacknowledged,
@@ -146,6 +139,8 @@
%%----------------------------------------------------------------------------
+-spec info_keys() -> rabbit_types:info_keys().
+
info_keys() -> ?INFO_KEYS ++ rabbit_backing_queue:info_keys().
statistics_keys() -> ?STATISTICS_KEYS ++ rabbit_backing_queue:info_keys().
@@ -153,13 +148,13 @@ statistics_keys() -> ?STATISTICS_KEYS ++ rabbit_backing_queue:info_keys().
init(Q) ->
process_flag(trap_exit, true),
- ?store_proc_name(Q#amqqueue.name),
- {ok, init_state(Q#amqqueue{pid = self()}), hibernate,
+ ?store_proc_name(amqqueue:get_name(Q)),
+ {ok, init_state(amqqueue:set_pid(Q, self())), hibernate,
{backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN, ?DESIRED_HIBERNATE},
?MODULE}.
init_state(Q) ->
- SingleActiveConsumerOn = case rabbit_misc:table_lookup(Q#amqqueue.arguments, <<"x-single-active-consumer">>) of
+ SingleActiveConsumerOn = case rabbit_misc:table_lookup(amqqueue:get_arguments(Q), <<"x-single-active-consumer">>) of
{bool, true} -> true;
_ -> false
end,
@@ -175,14 +170,16 @@ init_state(Q) ->
single_active_consumer_on = SingleActiveConsumerOn},
rabbit_event:init_stats_timer(State, #q.stats_timer).
-init_it(Recover, From, State = #q{q = #amqqueue{exclusive_owner = none}}) ->
+init_it(Recover, From, State = #q{q = Q})
+ when ?amqqueue_exclusive_owner_is(Q, none) ->
init_it2(Recover, From, State);
%% You used to be able to declare an exclusive durable queue. Sadly we
%% need to still tidy up after that case, there could be the remnants
%% of one left over from an upgrade. So that's why we don't enforce
%% Recover = new here.
-init_it(Recover, From, State = #q{q = #amqqueue{exclusive_owner = Owner}}) ->
+init_it(Recover, From, State = #q{q = Q0}) ->
+ Owner = amqqueue:get_exclusive_owner(Q0),
case rabbit_misc:is_process_alive(Owner) of
true -> erlang:monitor(process, Owner),
init_it2(Recover, From, State);
@@ -204,7 +201,9 @@ init_it2(Recover, From, State = #q{q = Q,
backing_queue_state = undefined}) ->
{Barrier, TermsOrNew} = recovery_status(Recover),
case rabbit_amqqueue:internal_declare(Q, Recover /= new) of
- {Res, #amqqueue{} = Q1} when Res == created orelse Res == existing ->
+ {Res, Q1}
+ when ?is_amqqueue(Q1) andalso
+ (Res == created orelse Res == existing) ->
case matches(Recover, Q, Q1) of
true ->
ok = file_handle_cache:register_callback(
@@ -240,13 +239,14 @@ send_reply(From, Q) -> gen_server2:reply(From, Q).
matches(new, Q1, Q2) ->
%% i.e. not policy
- Q1#amqqueue.name =:= Q2#amqqueue.name andalso
- Q1#amqqueue.durable =:= Q2#amqqueue.durable andalso
- Q1#amqqueue.auto_delete =:= Q2#amqqueue.auto_delete andalso
- Q1#amqqueue.exclusive_owner =:= Q2#amqqueue.exclusive_owner andalso
- Q1#amqqueue.arguments =:= Q2#amqqueue.arguments andalso
- Q1#amqqueue.pid =:= Q2#amqqueue.pid andalso
- Q1#amqqueue.slave_pids =:= Q2#amqqueue.slave_pids;
+ amqqueue:get_name(Q1) =:= amqqueue:get_name(Q2) andalso
+ amqqueue:is_durable(Q1) =:= amqqueue:is_durable(Q2) andalso
+ amqqueue:is_auto_delete(Q1) =:= amqqueue:is_auto_delete(Q2) andalso
+ amqqueue:get_exclusive_owner(Q1) =:= amqqueue:get_exclusive_owner(Q2) andalso
+ amqqueue:get_arguments(Q1) =:= amqqueue:get_arguments(Q2) andalso
+ amqqueue:get_pid(Q1) =:= amqqueue:get_pid(Q2) andalso
+ amqqueue:get_slave_pids(Q1) =:= amqqueue:get_slave_pids(Q2);
+%% FIXME: Should v1 vs. v2 of the same record match?
matches(_, Q, Q) -> true;
matches(_, _Q, _Q1) -> false.
@@ -259,8 +259,14 @@ recovery_barrier(BarrierPid) ->
{'DOWN', MRef, process, _, _} -> ok
end.
-init_with_backing_queue_state(Q = #amqqueue{exclusive_owner = Owner}, BQ, BQS,
+-spec init_with_backing_queue_state
+ (amqqueue:amqqueue(), atom(), tuple(), any(),
+ [rabbit_types:delivery()], pmon:pmon(), gb_trees:tree()) ->
+ #q{}.
+
+init_with_backing_queue_state(Q, BQ, BQS,
RateTRef, Deliveries, Senders, MTC) ->
+ Owner = amqqueue:get_exclusive_owner(Q),
case Owner of
none -> ok;
_ -> erlang:monitor(process, Owner)
@@ -278,14 +284,18 @@ init_with_backing_queue_state(Q = #amqqueue{exclusive_owner = Owner}, BQ, BQS,
notify_decorators(startup, State3),
State3.
-terminate(shutdown = R, State = #q{backing_queue = BQ, q = #amqqueue{ name = QName }}) ->
+terminate(shutdown = R, State = #q{backing_queue = BQ, q = Q0}) ->
+ QName = amqqueue:get_name(Q0),
rabbit_core_metrics:queue_deleted(qname(State)),
terminate_shutdown(
fun (BQS) ->
rabbit_misc:execute_mnesia_transaction(
fun() ->
[Q] = mnesia:read({rabbit_queue, QName}),
- Q2 = Q#amqqueue{state = stopped},
+ Q2 = amqqueue:set_state(Q, stopped),
+ %% amqqueue migration:
+ %% The amqqueue was read from this transaction, no need
+ %% to handle migration.
rabbit_amqqueue:store_queue(Q2)
end),
BQ:terminate(R, BQS)
@@ -309,18 +319,24 @@ terminate(normal, State) -> %% delete case
%% If we crashed don't try to clean up the BQS, probably best to leave it.
terminate(_Reason, State = #q{q = Q}) ->
terminate_shutdown(fun (BQS) ->
- Q2 = Q#amqqueue{state = crashed},
+ Q2 = amqqueue:set_state(Q, crashed),
rabbit_misc:execute_mnesia_transaction(
fun() ->
- rabbit_amqqueue:store_queue(Q2)
+ ?try_mnesia_tx_or_upgrade_amqqueue_and_retry(
+ rabbit_amqqueue:store_queue(Q2),
+ begin
+ Q3 = amqqueue:upgrade(Q2),
+ rabbit_amqqueue:store_queue(Q3)
+ end)
end),
BQS
end, State).
terminate_delete(EmitStats, Reason0,
- State = #q{q = #amqqueue{name = QName},
+ State = #q{q = Q,
backing_queue = BQ,
status = Status}) ->
+ QName = amqqueue:get_name(Q),
ActingUser = terminated_by(Status),
fun (BQS) ->
Reason = case Reason0 of
@@ -389,7 +405,8 @@ notify_decorators(State = #q{consumers = Consumers,
decorator_callback(QName, F, A) ->
%% Look up again in case policy and hence decorators have changed
case rabbit_amqqueue:lookup(QName) of
- {ok, Q = #amqqueue{decorators = Ds}} ->
+ {ok, Q} ->
+ Ds = amqqueue:get_decorators(Q),
[ok = apply(M, F, [Q|A]) || M <- rabbit_queue_decorator:select(Ds)];
{error, not_found} ->
ok
@@ -418,7 +435,8 @@ process_args_policy(State = #q{q = Q,
Fun(args_policy_lookup(Name, Resolve, Q), StateN)
end, State#q{args_policy_version = N + 1}, ArgsTable)).
-args_policy_lookup(Name, Resolve, Q = #amqqueue{arguments = Args}) ->
+args_policy_lookup(Name, Resolve, Q) ->
+ Args = amqqueue:get_arguments(Q),
AName = <<"x-", Name/binary>>,
case {rabbit_policy:get(Name, Q), rabbit_misc:table_lookup(Args, AName)} of
{undefined, undefined} -> undefined;
@@ -442,7 +460,8 @@ init_ttl(TTL, State) -> (init_ttl(undefined, State))#q{ttl = TTL}.
init_dlx(undefined, State) ->
State#q{dlx = undefined};
-init_dlx(DLX, State = #q{q = #amqqueue{name = QName}}) ->
+init_dlx(DLX, State = #q{q = Q}) ->
+ QName = amqqueue:get_name(Q),
State#q{dlx = rabbit_misc:r(QName, exchange, DLX)}.
init_dlx_rkey(RoutingKey, State) -> State#q{dlx_routing_key = RoutingKey}.
@@ -596,8 +615,9 @@ send_or_record_confirm(#delivery{confirm = true,
message = #basic_message {
is_persistent = true,
id = MsgId}},
- State = #q{q = #amqqueue{durable = true},
- msg_id_to_channel = MTC}) ->
+ State = #q{q = Q,
+ msg_id_to_channel = MTC})
+ when ?amqqueue_is_durable(Q) ->
MTC1 = gb_trees:insert(MsgId, {SenderPid, MsgSeqNo}, MTC),
{eventually, State#q{msg_id_to_channel = MTC1}};
send_or_record_confirm(#delivery{confirm = true,
@@ -606,6 +626,18 @@ send_or_record_confirm(#delivery{confirm = true,
rabbit_misc:confirm_to_sender(SenderPid, [MsgSeqNo]),
{immediately, State}.
+%% This feature was used by `rabbit_amqqueue_process` and
+%% `rabbit_mirror_queue_slave` up-to and including RabbitMQ 3.7.x. It is
+%% unused in 3.8.x and thus deprecated. We keep it to support in-place
+%% upgrades to 3.8.x (i.e. mixed-version clusters), but it is a no-op
+%% starting with that version.
+send_mandatory(#delivery{mandatory = false}) ->
+ ok;
+send_mandatory(#delivery{mandatory = true,
+ sender = SenderPid,
+ msg_seq_no = MsgSeqNo}) ->
+ gen_server2:cast(SenderPid, {mandatory_received, MsgSeqNo}).
+
discard(#delivery{confirm = Confirm,
sender = SenderPid,
flow = Flow,
@@ -669,6 +701,7 @@ maybe_deliver_or_enqueue(Delivery = #delivery{message = Message},
State = #q{overflow = Overflow,
backing_queue = BQ,
backing_queue_state = BQS}) ->
+ send_mandatory(Delivery), %% must do this before confirms
case {will_overflow(Delivery, State), Overflow} of
{true, 'reject-publish'} ->
%% Drop publish and nack to publisher
@@ -815,7 +848,8 @@ possibly_unblock(Update, ChPid, State = #q{consumers = Consumers}) ->
run_message_queue(true, State1)
end.
-should_auto_delete(#q{q = #amqqueue{auto_delete = false}}) -> false;
+should_auto_delete(#q{q = Q})
+ when not ?amqqueue_is_auto_delete(Q) -> false;
should_auto_delete(#q{has_had_consumers = false}) -> false;
should_auto_delete(State) -> is_unused(State).
@@ -896,7 +930,7 @@ is_unused(_State) -> rabbit_queue_consumers:count() == 0.
maybe_send_reply(_ChPid, undefined) -> ok;
maybe_send_reply(ChPid, Msg) -> ok = rabbit_channel:send_command(ChPid, Msg).
-qname(#q{q = #amqqueue{name = QName}}) -> QName.
+qname(#q{q = Q}) -> amqqueue:get_name(Q).
backing_queue_timeout(State = #q{backing_queue = BQ,
backing_queue_state = BQS}) ->
@@ -1001,17 +1035,18 @@ stop(Reply, State) -> {stop, normal, Reply, State}.
infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items].
-i(name, #q{q = #amqqueue{name = Name}}) -> Name;
-i(durable, #q{q = #amqqueue{durable = Durable}}) -> Durable;
-i(auto_delete, #q{q = #amqqueue{auto_delete = AutoDelete}}) -> AutoDelete;
-i(arguments, #q{q = #amqqueue{arguments = Arguments}}) -> Arguments;
+i(name, #q{q = Q}) -> amqqueue:get_name(Q);
+i(durable, #q{q = Q}) -> amqqueue:is_durable(Q);
+i(auto_delete, #q{q = Q}) -> amqqueue:is_auto_delete(Q);
+i(arguments, #q{q = Q}) -> amqqueue:get_arguments(Q);
i(pid, _) ->
self();
-i(owner_pid, #q{q = #amqqueue{exclusive_owner = none}}) ->
+i(owner_pid, #q{q = Q}) when ?amqqueue_exclusive_owner_is(Q, none) ->
'';
-i(owner_pid, #q{q = #amqqueue{exclusive_owner = ExclusiveOwner}}) ->
- ExclusiveOwner;
-i(exclusive, #q{q = #amqqueue{exclusive_owner = ExclusiveOwner}}) ->
+i(owner_pid, #q{q = Q}) ->
+ amqqueue:get_exclusive_owner(Q);
+i(exclusive, #q{q = Q}) ->
+ ExclusiveOwner = amqqueue:get_exclusive_owner(Q),
is_pid(ExclusiveOwner);
i(policy, #q{q = Q}) ->
case rabbit_policy:name(Q) of
@@ -1061,27 +1096,27 @@ i(consumer_utilisation, #q{consumers = Consumers}) ->
i(memory, _) ->
{memory, M} = process_info(self(), memory),
M;
-i(slave_pids, #q{q = #amqqueue{name = Name}}) ->
- {ok, Q = #amqqueue{slave_pids = SPids}} =
- rabbit_amqqueue:lookup(Name),
+i(slave_pids, #q{q = Q0}) ->
+ Name = amqqueue:get_name(Q0),
+ {ok, Q} = rabbit_amqqueue:lookup(Name),
case rabbit_mirror_queue_misc:is_mirrored(Q) of
false -> '';
- true -> SPids
+ true -> amqqueue:get_slave_pids(Q)
end;
-i(synchronised_slave_pids, #q{q = #amqqueue{name = Name}}) ->
- {ok, Q = #amqqueue{sync_slave_pids = SSPids}} =
- rabbit_amqqueue:lookup(Name),
+i(synchronised_slave_pids, #q{q = Q0}) ->
+ Name = amqqueue:get_name(Q0),
+ {ok, Q} = rabbit_amqqueue:lookup(Name),
case rabbit_mirror_queue_misc:is_mirrored(Q) of
false -> '';
- true -> SSPids
+ true -> amqqueue:get_sync_slave_pids(Q)
end;
-i(recoverable_slaves, #q{q = #amqqueue{name = Name,
- durable = Durable}}) ->
- {ok, Q = #amqqueue{recoverable_slaves = Nodes}} =
- rabbit_amqqueue:lookup(Name),
+i(recoverable_slaves, #q{q = Q0}) ->
+ Name = amqqueue:get_name(Q0),
+ Durable = amqqueue:is_durable(Q0),
+ {ok, Q} = rabbit_amqqueue:lookup(Name),
case Durable andalso rabbit_mirror_queue_misc:is_mirrored(Q) of
false -> '';
- true -> Nodes
+ true -> amqqueue:get_recoverable_slaves(Q)
end;
i(state, #q{status = running}) -> credit_flow:state();
i(state, #q{status = State}) -> State;
@@ -1090,7 +1125,8 @@ i(garbage_collection, _State) ->
i(reductions, _State) ->
{reductions, Reductions} = erlang:process_info(self(), reductions),
Reductions;
-i(user_who_performed_action, #q{q = #amqqueue{options = Opts}}) ->
+i(user_who_performed_action, #q{q = Q}) ->
+ Opts = amqqueue:get_options(Q),
maps:get(user, Opts, ?UNKNOWN_USER);
i(Item, #q{backing_queue_state = BQS, backing_queue = BQ}) ->
BQ:info(Item, BQS).
@@ -1174,7 +1210,8 @@ consumer_bias(#q{backing_queue = BQ, backing_queue_state = BQS}, Low, High) ->
{_, _} -> Low
end.
-prioritise_info(Msg, _Len, #q{q = #amqqueue{exclusive_owner = DownPid}}) ->
+prioritise_info(Msg, _Len, #q{q = Q}) ->
+ DownPid = amqqueue:get_exclusive_owner(Q),
case Msg of
{'DOWN', _, process, DownPid, _} -> 8;
update_ram_duration -> 8;
@@ -1225,7 +1262,8 @@ handle_call({notify_down, ChPid}, _From, State) ->
end;
handle_call({basic_get, ChPid, NoAck, LimiterPid}, _From,
- State = #q{q = #amqqueue{name = QName}}) ->
+ State = #q{q = Q}) ->
+ QName = amqqueue:get_name(Q),
AckRequired = not NoAck,
State1 = ensure_expiry_timer(State),
case fetch(AckRequired, State1) of
@@ -1542,11 +1580,33 @@ handle_cast({credit, ChPid, CTag, Credit, Drain},
{unblocked, Consumers1} -> State1 = State#q{consumers = Consumers1},
run_message_queue(true, State1)
end);
+
+handle_cast({force_event_refresh, Ref},
+ State = #q{consumers = Consumers,
+ active_consumer = Holder}) ->
+ rabbit_event:notify(queue_created, infos(?CREATION_EVENT_KEYS, State), Ref),
+ QName = qname(State),
+ AllConsumers = rabbit_queue_consumers:all(Consumers),
+ case Holder of
+ none ->
+ [emit_consumer_created(
+ Ch, CTag, false, AckRequired, QName, Prefetch,
+ Args, Ref, ActingUser) ||
+ {Ch, CTag, AckRequired, Prefetch, Args, ActingUser}
+ <- AllConsumers];
+ {Ch, CTag} ->
+ [{Ch, CTag, AckRequired, Prefetch, Args, ActingUser}] = AllConsumers,
+ emit_consumer_created(
+ Ch, CTag, true, AckRequired, QName, Prefetch, Args, Ref, ActingUser)
+ end,
+ noreply(rabbit_event:init_stats_timer(State, #q.stats_timer));
+
handle_cast(notify_decorators, State) ->
notify_decorators(State),
noreply(State);
-handle_cast(policy_changed, State = #q{q = #amqqueue{name = Name}}) ->
+handle_cast(policy_changed, State = #q{q = Q0}) ->
+ Name = amqqueue:get_name(Q0),
%% We depend on the #q.q field being up to date at least WRT
%% policy (but not slave pids) in various places, so when it
%% changes we go and read it from Mnesia again.
@@ -1556,7 +1616,8 @@ handle_cast(policy_changed, State = #q{q = #amqqueue{name = Name}}) ->
{ok, Q} = rabbit_amqqueue:lookup(Name),
noreply(process_args_policy(State#q{q = Q}));
-handle_cast({sync_start, _, _}, State = #q{q = #amqqueue{name = Name}}) ->
+handle_cast({sync_start, _, _}, State = #q{q = Q}) ->
+ Name = amqqueue:get_name(Q),
%% Only a slave should receive this, it means we are a duplicated master
rabbit_mirror_queue_misc:log_warning(
Name, "Stopping after receiving sync_start from another master", []),
@@ -1587,7 +1648,7 @@ handle_info(emit_stats, State) ->
{noreply, State1, Timeout};
handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason},
- State = #q{q = #amqqueue{exclusive_owner = DownPid}}) ->
+ State = #q{q = Q}) when ?amqqueue_exclusive_owner_is(Q, DownPid) ->
%% Exclusively owned queues must disappear with their owner. In
%% the case of clean shutdown we delete the queue synchronously in
%% the reader - although not required by the spec this seems to
@@ -1665,21 +1726,23 @@ format_message_queue(Opt, MQ) -> rabbit_misc:format_message_queue(Opt, MQ).
log_delete_exclusive({ConPid, _ConRef}, State) ->
log_delete_exclusive(ConPid, State);
-log_delete_exclusive(ConPid, #q{ q = #amqqueue{ name = Resource } }) ->
+log_delete_exclusive(ConPid, #q{ q = Q }) ->
+ Resource = amqqueue:get_name(Q),
#resource{ name = QName, virtual_host = VHost } = Resource,
rabbit_log_queue:debug("Deleting exclusive queue '~s' in vhost '~s' " ++
"because its declaring connection ~p was closed",
[QName, VHost, ConPid]).
-log_auto_delete(Reason, #q{ q = #amqqueue{ name = Resource } }) ->
+log_auto_delete(Reason, #q{ q = Q }) ->
+ Resource = amqqueue:get_name(Q),
#resource{ name = QName, virtual_host = VHost } = Resource,
rabbit_log_queue:debug("Deleting auto-delete queue '~s' in vhost '~s' " ++
Reason,
[QName, VHost]).
needs_update_mirroring(Q, Version) ->
- {ok, UpQ} = rabbit_amqqueue:lookup(Q#amqqueue.name),
- DBVersion = UpQ#amqqueue.policy_version,
+ {ok, UpQ} = rabbit_amqqueue:lookup(amqqueue:get_name(Q)),
+ DBVersion = amqqueue:get_policy_version(UpQ),
case DBVersion > Version of
true -> {rabbit_policy:get(<<"ha-mode">>, UpQ), DBVersion};
false -> false
diff --git a/src/rabbit_amqqueue_sup.erl b/src/rabbit_amqqueue_sup.erl
index 54a97b717e..64efc01786 100644
--- a/src/rabbit_amqqueue_sup.erl
+++ b/src/rabbit_amqqueue_sup.erl
@@ -26,11 +26,9 @@
%%----------------------------------------------------------------------------
--spec start_link(rabbit_types:amqqueue(), rabbit_prequeue:start_mode()) ->
+-spec start_link(amqqueue:amqqueue(), rabbit_prequeue:start_mode()) ->
{'ok', pid(), pid()}.
-%%----------------------------------------------------------------------------
-
start_link(Q, StartMode) ->
Marker = spawn_link(fun() -> receive stop -> ok end end),
ChildSpec = {rabbit_amqqueue,
diff --git a/src/rabbit_amqqueue_sup_sup.erl b/src/rabbit_amqqueue_sup_sup.erl
index d8045276c6..9e30a7f0ae 100644
--- a/src/rabbit_amqqueue_sup_sup.erl
+++ b/src/rabbit_amqqueue_sup_sup.erl
@@ -31,17 +31,16 @@
%%----------------------------------------------------------------------------
-spec start_link() -> rabbit_types:ok_pid_or_error().
--spec start_queue_process
- (node(), rabbit_types:amqqueue(), 'declare' | 'recovery' | 'slave') ->
- pid().
-
-%%----------------------------------------------------------------------------
start_link() ->
supervisor2:start_link(?MODULE, []).
+-spec start_queue_process
+ (node(), amqqueue:amqqueue(), 'declare' | 'recovery' | 'slave') ->
+ pid().
+
start_queue_process(Node, Q, StartMode) ->
- #amqqueue{name = #resource{virtual_host = VHost}} = Q,
+ #resource{virtual_host = VHost} = amqqueue:get_name(Q),
{ok, Sup} = find_for_vhost(VHost, Node),
{ok, _SupPid, QPid} = supervisor2:start_child(Sup, [Q, StartMode]),
QPid.
diff --git a/src/rabbit_auth_backend_internal.erl b/src/rabbit_auth_backend_internal.erl
index 83580e8b9c..5732ac4b30 100644
--- a/src/rabbit_auth_backend_internal.erl
+++ b/src/rabbit_auth_backend_internal.erl
@@ -47,46 +47,6 @@
-type regexp() :: binary().
--spec add_user(rabbit_types:username(), rabbit_types:password(),
- rabbit_types:username()) -> 'ok' | {'error', string()}.
--spec delete_user(rabbit_types:username(), rabbit_types:username()) -> 'ok'.
--spec lookup_user
- (rabbit_types:username()) ->
- rabbit_types:ok(rabbit_types:internal_user()) |
- rabbit_types:error('not_found').
--spec change_password
- (rabbit_types:username(), rabbit_types:password(), rabbit_types:username()) -> 'ok'.
--spec clear_password(rabbit_types:username(), rabbit_types:username()) -> 'ok'.
--spec hash_password
- (module(), rabbit_types:password()) -> rabbit_types:password_hash().
--spec change_password_hash
- (rabbit_types:username(), rabbit_types:password_hash()) -> 'ok'.
--spec set_tags(rabbit_types:username(), [atom()], rabbit_types:username()) -> 'ok'.
--spec set_permissions
- (rabbit_types:username(), rabbit_types:vhost(), regexp(), regexp(),
- regexp(), rabbit_types:username()) ->
- 'ok'.
--spec clear_permissions
- (rabbit_types:username(), rabbit_types:vhost(), rabbit_types:username()) -> 'ok'.
--spec user_info_keys() -> rabbit_types:info_keys().
--spec perms_info_keys() -> rabbit_types:info_keys().
--spec user_perms_info_keys() -> rabbit_types:info_keys().
--spec vhost_perms_info_keys() -> rabbit_types:info_keys().
--spec user_vhost_perms_info_keys() -> rabbit_types:info_keys().
--spec list_users() -> [rabbit_types:infos()].
--spec list_users(reference(), pid()) -> 'ok'.
--spec list_permissions() -> [rabbit_types:infos()].
--spec list_user_permissions
- (rabbit_types:username()) -> [rabbit_types:infos()].
--spec list_user_permissions
- (rabbit_types:username(), reference(), pid()) -> 'ok'.
--spec list_vhost_permissions
- (rabbit_types:vhost()) -> [rabbit_types:infos()].
--spec list_vhost_permissions
- (rabbit_types:vhost(), reference(), pid()) -> 'ok'.
--spec list_user_vhost_permissions
- (rabbit_types:username(), rabbit_types:vhost()) -> [rabbit_types:infos()].
-
%%----------------------------------------------------------------------------
%% Implementation of rabbit_auth_backend
@@ -238,6 +198,9 @@ validate_and_alternate_credentials(Username, Password, ActingUser, Fun) ->
{error, Err}
end.
+-spec add_user(rabbit_types:username(), rabbit_types:password(),
+ rabbit_types:username()) -> 'ok' | {'error', string()}.
+
add_user(Username, Password, ActingUser) ->
validate_and_alternate_credentials(Username, Password, ActingUser,
fun add_user_sans_validation/3).
@@ -265,6 +228,8 @@ add_user_sans_validation(Username, Password, ActingUser) ->
{user_who_performed_action, ActingUser}]),
R.
+-spec delete_user(rabbit_types:username(), rabbit_types:username()) -> 'ok'.
+
delete_user(Username, ActingUser) ->
rabbit_log:info("Deleting user '~s'~n", [Username]),
R = rabbit_misc:execute_mnesia_transaction(
@@ -291,9 +256,17 @@ delete_user(Username, ActingUser) ->
{user_who_performed_action, ActingUser}]),
R.
+-spec lookup_user
+ (rabbit_types:username()) ->
+ rabbit_types:ok(rabbit_types:internal_user()) |
+ rabbit_types:error('not_found').
+
lookup_user(Username) ->
rabbit_misc:dirty_read({rabbit_user, Username}).
+-spec change_password
+ (rabbit_types:username(), rabbit_types:password(), rabbit_types:username()) -> 'ok'.
+
change_password(Username, Password, ActingUser) ->
validate_and_alternate_credentials(Username, Password, ActingUser,
fun change_password_sans_validation/3).
@@ -310,6 +283,8 @@ change_password_sans_validation(Username, Password, ActingUser) ->
{user_who_performed_action, ActingUser}]),
R.
+-spec clear_password(rabbit_types:username(), rabbit_types:username()) -> 'ok'.
+
clear_password(Username, ActingUser) ->
rabbit_log:info("Clearing password for '~s'~n", [Username]),
R = change_password_hash(Username, <<"">>),
@@ -318,9 +293,15 @@ clear_password(Username, ActingUser) ->
{user_who_performed_action, ActingUser}]),
R.
+-spec hash_password
+ (module(), rabbit_types:password()) -> rabbit_types:password_hash().
+
hash_password(HashingMod, Cleartext) ->
rabbit_password:hash(HashingMod, Cleartext).
+-spec change_password_hash
+ (rabbit_types:username(), rabbit_types:password_hash()) -> 'ok'.
+
change_password_hash(Username, PasswordHash) ->
change_password_hash(Username, PasswordHash, rabbit_password:hashing_mod()).
@@ -332,6 +313,8 @@ change_password_hash(Username, PasswordHash, HashingAlgorithm) ->
hashing_algorithm = HashingAlgorithm }
end).
+-spec set_tags(rabbit_types:username(), [atom()], rabbit_types:username()) -> 'ok'.
+
set_tags(Username, Tags, ActingUser) ->
ConvertedTags = [rabbit_data_coercion:to_atom(I) || I <- Tags],
rabbit_log:info("Setting user tags for user '~s' to ~p~n",
@@ -343,6 +326,11 @@ set_tags(Username, Tags, ActingUser) ->
{user_who_performed_action, ActingUser}]),
R.
+-spec set_permissions
+ (rabbit_types:username(), rabbit_types:vhost(), regexp(), regexp(),
+ regexp(), rabbit_types:username()) ->
+ 'ok'.
+
set_permissions(Username, VHostPath, ConfigurePerm, WritePerm, ReadPerm, ActingUser) ->
rabbit_log:info("Setting permissions for "
"'~s' in '~s' to '~s', '~s', '~s'~n",
@@ -378,6 +366,9 @@ set_permissions(Username, VHostPath, ConfigurePerm, WritePerm, ReadPerm, ActingU
{user_who_performed_action, ActingUser}]),
R.
+-spec clear_permissions
+ (rabbit_types:username(), rabbit_types:vhost(), rabbit_types:username()) -> 'ok'.
+
clear_permissions(Username, VHostPath, ActingUser) ->
R = rabbit_misc:execute_mnesia_transaction(
rabbit_vhost:with_user_and_vhost(
@@ -481,11 +472,24 @@ clear_topic_permissions(Username, VHostPath, Exchange, ActingUser) ->
-define(PERMS_INFO_KEYS, [configure, write, read]).
-define(USER_INFO_KEYS, [user, tags]).
+-spec user_info_keys() -> rabbit_types:info_keys().
+
user_info_keys() -> ?USER_INFO_KEYS.
+-spec perms_info_keys() -> rabbit_types:info_keys().
+
perms_info_keys() -> [user, vhost | ?PERMS_INFO_KEYS].
+
+-spec vhost_perms_info_keys() -> rabbit_types:info_keys().
+
vhost_perms_info_keys() -> [user | ?PERMS_INFO_KEYS].
+
+-spec user_perms_info_keys() -> rabbit_types:info_keys().
+
user_perms_info_keys() -> [vhost | ?PERMS_INFO_KEYS].
+
+-spec user_vhost_perms_info_keys() -> rabbit_types:info_keys().
+
user_vhost_perms_info_keys() -> ?PERMS_INFO_KEYS.
topic_perms_info_keys() -> [user, vhost, exchange, write, read].
@@ -493,16 +497,22 @@ user_topic_perms_info_keys() -> [vhost, exchange, write, read].
vhost_topic_perms_info_keys() -> [user, exchange, write, read].
user_vhost_topic_perms_info_keys() -> [exchange, write, read].
+-spec list_users() -> [rabbit_types:infos()].
+
list_users() ->
[extract_internal_user_params(U) ||
U <- mnesia:dirty_match_object(rabbit_user, #internal_user{_ = '_'})].
+-spec list_users(reference(), pid()) -> 'ok'.
+
list_users(Ref, AggregatorPid) ->
rabbit_control_misc:emitting_map(
AggregatorPid, Ref,
fun(U) -> extract_internal_user_params(U) end,
mnesia:dirty_match_object(rabbit_user, #internal_user{_ = '_'})).
+-spec list_permissions() -> [rabbit_types:infos()].
+
list_permissions() ->
list_permissions(perms_info_keys(), match_user_vhost('_', '_')).
@@ -517,28 +527,43 @@ list_permissions(Keys, QueryThunk, Ref, AggregatorPid) ->
filter_props(Keys, Props) -> [T || T = {K, _} <- Props, lists:member(K, Keys)].
+-spec list_user_permissions
+ (rabbit_types:username()) -> [rabbit_types:infos()].
+
list_user_permissions(Username) ->
list_permissions(
user_perms_info_keys(),
rabbit_misc:with_user(Username, match_user_vhost(Username, '_'))).
+-spec list_user_permissions
+ (rabbit_types:username(), reference(), pid()) -> 'ok'.
+
list_user_permissions(Username, Ref, AggregatorPid) ->
list_permissions(
user_perms_info_keys(),
rabbit_misc:with_user(Username, match_user_vhost(Username, '_')),
Ref, AggregatorPid).
+-spec list_vhost_permissions
+ (rabbit_types:vhost()) -> [rabbit_types:infos()].
+
list_vhost_permissions(VHostPath) ->
list_permissions(
vhost_perms_info_keys(),
rabbit_vhost:with(VHostPath, match_user_vhost('_', VHostPath))).
+-spec list_vhost_permissions
+ (rabbit_types:vhost(), reference(), pid()) -> 'ok'.
+
list_vhost_permissions(VHostPath, Ref, AggregatorPid) ->
list_permissions(
vhost_perms_info_keys(),
rabbit_vhost:with(VHostPath, match_user_vhost('_', VHostPath)),
Ref, AggregatorPid).
+-spec list_user_vhost_permissions
+ (rabbit_types:username(), rabbit_types:vhost()) -> [rabbit_types:infos()].
+
list_user_vhost_permissions(Username, VHostPath) ->
list_permissions(
user_vhost_perms_info_keys(),
diff --git a/src/rabbit_backing_queue.erl b/src/rabbit_backing_queue.erl
new file mode 100644
index 0000000000..c3e570b26f
--- /dev/null
+++ b/src/rabbit_backing_queue.erl
@@ -0,0 +1,273 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2017 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_backing_queue).
+
+-export([info_keys/0]).
+
+-define(INFO_KEYS, [messages_ram, messages_ready_ram,
+ messages_unacknowledged_ram, messages_persistent,
+ message_bytes, message_bytes_ready,
+ message_bytes_unacknowledged, message_bytes_ram,
+ message_bytes_persistent, head_message_timestamp,
+ disk_reads, disk_writes, backing_queue_status,
+ messages_paged_out, message_bytes_paged_out]).
+
+%% We can't specify a per-queue ack/state with callback signatures
+-type ack() :: any().
+-type state() :: any().
+
+-type flow() :: 'flow' | 'noflow'.
+-type msg_ids() :: [rabbit_types:msg_id()].
+-type publish() :: {rabbit_types:basic_message(),
+ rabbit_types:message_properties(), boolean()}.
+-type delivered_publish() :: {rabbit_types:basic_message(),
+ rabbit_types:message_properties()}.
+-type fetch_result(Ack) ::
+ ('empty' | {rabbit_types:basic_message(), boolean(), Ack}).
+-type drop_result(Ack) ::
+ ('empty' | {rabbit_types:msg_id(), Ack}).
+-type recovery_terms() :: [term()] | 'non_clean_shutdown'.
+-type recovery_info() :: 'new' | recovery_terms().
+-type purged_msg_count() :: non_neg_integer().
+-type async_callback() ::
+ fun ((atom(), fun ((atom(), state()) -> state())) -> 'ok').
+-type duration() :: ('undefined' | 'infinity' | number()).
+
+-type msg_fun(A) :: fun ((rabbit_types:basic_message(), ack(), A) -> A).
+-type msg_pred() :: fun ((rabbit_types:message_properties()) -> boolean()).
+
+-type queue_mode() :: atom().
+
+%% Called on startup with a vhost and a list of durable queue names on this vhost.
+%% The queues aren't being started at this point, but this call allows the
+%% backing queue to perform any checking necessary for the consistency
+%% of those queues, or initialise any other shared resources.
+%%
+%% The list of queue recovery terms returned as {ok, Terms} must be given
+%% in the same order as the list of queue names supplied.
+-callback start(rabbit_types:vhost(), [rabbit_amqqueue:name()]) -> rabbit_types:ok(recovery_terms()).
+
+%% Called to tear down any state/resources for vhost. NB: Implementations should
+%% not depend on this function being called on shutdown and instead
+%% should hook into the rabbit supervision hierarchy.
+-callback stop(rabbit_types:vhost()) -> 'ok'.
+
+%% Initialise the backing queue and its state.
+%%
+%% Takes
+%% 1. the amqqueue record
+%% 2. a term indicating whether the queue is an existing queue that
+%% should be recovered or not. When 'new' is given, no recovery is
+%% taking place, otherwise a list of recovery terms is given, or
+%% the atom 'non_clean_shutdown' if no recovery terms are available.
+%% 3. an asynchronous callback which accepts a function of type
+%% backing-queue-state to backing-queue-state. This callback
+%% function can be safely invoked from any process, which makes it
+%% useful for passing messages back into the backing queue,
+%% especially as the backing queue does not have control of its own
+%% mailbox.
+-callback init(amqqueue:amqqueue(), recovery_info(),
+ async_callback()) -> state().
+
+%% Called on queue shutdown when queue isn't being deleted.
+-callback terminate(any(), state()) -> state().
+
+%% Called when the queue is terminating and needs to delete all its
+%% content.
+-callback delete_and_terminate(any(), state()) -> state().
+
+%% Called to clean up after a crashed queue. In this case we don't
+%% have a process and thus a state(), we are just removing on-disk data.
+-callback delete_crashed(amqqueue:amqqueue()) -> 'ok'.
+
+%% Remove all 'fetchable' messages from the queue, i.e. all messages
+%% except those that have been fetched already and are pending acks.
+-callback purge(state()) -> {purged_msg_count(), state()}.
+
+%% Remove all messages in the queue which have been fetched and are
+%% pending acks.
+-callback purge_acks(state()) -> state().
+
+%% Publish a message.
+-callback publish(rabbit_types:basic_message(),
+ rabbit_types:message_properties(), boolean(), pid(), flow(),
+ state()) -> state().
+
+%% Like publish/6 but for batches of publishes.
+-callback batch_publish([publish()], pid(), flow(), state()) -> state().
+
+%% Called for messages which have already been passed straight
+%% out to a client. The queue will be empty for these calls
+%% (i.e. saves the round trip through the backing queue).
+-callback publish_delivered(rabbit_types:basic_message(),
+ rabbit_types:message_properties(), pid(), flow(),
+ state())
+ -> {ack(), state()}.
+
+%% Like publish_delivered/5 but for batches of publishes.
+-callback batch_publish_delivered([delivered_publish()], pid(), flow(),
+ state())
+ -> {[ack()], state()}.
+
+%% Called to inform the BQ about messages which have reached the
+%% queue, but are not going to be further passed to BQ.
+-callback discard(rabbit_types:msg_id(), pid(), flow(), state()) -> state().
+
+%% Return ids of messages which have been confirmed since the last
+%% invocation of this function (or initialisation).
+%%
+%% Message ids should only appear in the result of drain_confirmed
+%% under the following circumstances:
+%%
+%% 1. The message appears in a call to publish_delivered/4 and the
+%% first argument (ack_required) is false; or
+%% 2. The message is fetched from the queue with fetch/2 and the first
+%% argument (ack_required) is false; or
+%% 3. The message is acked (ack/2 is called for the message); or
+%% 4. The message is fully fsync'd to disk in such a way that the
+%% recovery of the message is guaranteed in the event of a crash of
+%% this rabbit node (excluding hardware failure).
+%%
+%% In addition to the above conditions, a message id may only appear
+%% in the result of drain_confirmed if
+%% #message_properties.needs_confirming = true when the msg was
+%% published (through whichever means) to the backing queue.
+%%
+%% It is legal for the same message id to appear in the results of
+%% multiple calls to drain_confirmed, which means that the backing
+%% queue is not required to keep track of which messages it has
+%% already confirmed. The confirm will be issued to the publisher the
+%% first time the message id appears in the result of
+%% drain_confirmed. All subsequent appearances of that message id will
+%% be ignored.
+-callback drain_confirmed(state()) -> {msg_ids(), state()}.
+
+%% Drop messages from the head of the queue while the supplied
+%% predicate on message properties returns true. Returns the first
+%% message properties for which the predictate returned false, or
+%% 'undefined' if the whole backing queue was traversed w/o the
+%% predicate ever returning false.
+-callback dropwhile(msg_pred(), state())
+ -> {rabbit_types:message_properties() | undefined, state()}.
+
+%% Like dropwhile, except messages are fetched in "require
+%% acknowledgement" mode and are passed, together with their ack tag,
+%% to the supplied function. The function is also fed an
+%% accumulator. The result of fetchwhile is as for dropwhile plus the
+%% accumulator.
+-callback fetchwhile(msg_pred(), msg_fun(A), A, state())
+ -> {rabbit_types:message_properties() | undefined,
+ A, state()}.
+
+%% Produce the next message.
+-callback fetch(true, state()) -> {fetch_result(ack()), state()};
+ (false, state()) -> {fetch_result(undefined), state()}.
+
+%% Remove the next message.
+-callback drop(true, state()) -> {drop_result(ack()), state()};
+ (false, state()) -> {drop_result(undefined), state()}.
+
+%% Acktags supplied are for messages which can now be forgotten
+%% about. Must return 1 msg_id per Ack, in the same order as Acks.
+-callback ack([ack()], state()) -> {msg_ids(), state()}.
+
+%% Reinsert messages into the queue which have already been delivered
+%% and were pending acknowledgement.
+-callback requeue([ack()], state()) -> {msg_ids(), state()}.
+
+%% Fold over messages by ack tag. The supplied function is called with
+%% each message, its ack tag, and an accumulator.
+-callback ackfold(msg_fun(A), A, state(), [ack()]) -> {A, state()}.
+
+%% Fold over all the messages in a queue and return the accumulated
+%% results, leaving the queue undisturbed.
+-callback fold(fun((rabbit_types:basic_message(),
+ rabbit_types:message_properties(),
+ boolean(), A) -> {('stop' | 'cont'), A}),
+ A, state()) -> {A, state()}.
+
+%% How long is my queue?
+-callback len(state()) -> non_neg_integer().
+
+%% Is my queue empty?
+-callback is_empty(state()) -> boolean().
+
+%% What's the queue depth, where depth = length + number of pending acks
+-callback depth(state()) -> non_neg_integer().
+
+%% For the next three functions, the assumption is that you're
+%% monitoring something like the ingress and egress rates of the
+%% queue. The RAM duration is thus the length of time represented by
+%% the messages held in RAM given the current rates. If you want to
+%% ignore all of this stuff, then do so, and return 0 in
+%% ram_duration/1.
+
+%% The target is to have no more messages in RAM than indicated by the
+%% duration and the current queue rates.
+-callback set_ram_duration_target(duration(), state()) -> state().
+
+%% Optionally recalculate the duration internally (likely to be just
+%% update your internal rates), and report how many seconds the
+%% messages in RAM represent given the current rates of the queue.
+-callback ram_duration(state()) -> {duration(), state()}.
+
+%% Should 'timeout' be called as soon as the queue process can manage
+%% (either on an empty mailbox, or when a timer fires)?
+-callback needs_timeout(state()) -> 'false' | 'timed' | 'idle'.
+
+%% Called (eventually) after needs_timeout returns 'idle' or 'timed'.
+%% Note this may be called more than once for each 'idle' or 'timed'
+%% returned from needs_timeout
+-callback timeout(state()) -> state().
+
+%% Called immediately before the queue hibernates.
+-callback handle_pre_hibernate(state()) -> state().
+
+%% Called when more credit has become available for credit_flow.
+-callback resume(state()) -> state().
+
+%% Used to help prioritisation in rabbit_amqqueue_process. The rate of
+%% inbound messages and outbound messages at the moment.
+-callback msg_rates(state()) -> {float(), float()}.
+
+-callback info(atom(), state()) -> any().
+
+%% Passed a function to be invoked with the relevant backing queue's
+%% state. Useful for when the backing queue or other components need
+%% to pass functions into the backing queue.
+-callback invoke(atom(), fun ((atom(), A) -> A), state()) -> state().
+
+%% Called prior to a publish or publish_delivered call. Allows the BQ
+%% to signal that it's already seen this message, (e.g. it was published
+%% or discarded previously) specifying whether to drop the message or reject it.
+-callback is_duplicate(rabbit_types:basic_message(), state())
+ -> {{true, drop} | {true, reject} | boolean(), state()}.
+
+-callback set_queue_mode(queue_mode(), state()) -> state().
+
+-callback zip_msgs_and_acks(delivered_publish(),
+ [ack()], Acc, state())
+ -> Acc.
+
+%% Called when rabbit_amqqueue_process receives a message via
+%% handle_info and it should be processed by the backing
+%% queue
+-callback handle_info(term(), state()) -> state().
+
+-spec info_keys() -> rabbit_types:info_keys().
+
+info_keys() -> ?INFO_KEYS.
diff --git a/src/rabbit_basic.erl b/src/rabbit_basic.erl
index 6cb91a9243..40c60ece45 100644
--- a/src/rabbit_basic.erl
+++ b/src/rabbit_basic.erl
@@ -37,61 +37,27 @@
-type exchange_input() :: rabbit_types:exchange() | rabbit_exchange:name().
-type body_input() :: binary() | [binary()].
+%%----------------------------------------------------------------------------
+
+%% Convenience function, for avoiding round-trips in calls across the
+%% erlang distributed network.
+
-spec publish
(exchange_input(), rabbit_router:routing_key(), properties_input(),
body_input()) ->
publish_result().
--spec publish
- (exchange_input(), rabbit_router:routing_key(), boolean(),
- properties_input(), body_input()) ->
- publish_result().
--spec publish(rabbit_types:delivery()) -> publish_result().
--spec delivery
- (boolean(), boolean(), rabbit_types:message(), undefined | integer()) ->
- rabbit_types:delivery().
--spec message
- (rabbit_exchange:name(), rabbit_router:routing_key(), properties_input(),
- binary()) ->
- rabbit_types:message().
--spec message
- (rabbit_exchange:name(), rabbit_router:routing_key(),
- rabbit_types:decoded_content()) ->
- rabbit_types:ok_or_error2(rabbit_types:message(), any()).
--spec properties
- (properties_input()) -> rabbit_framing:amqp_property_record().
-
--spec prepend_table_header
- (binary(), rabbit_framing:amqp_table(), headers()) -> headers().
--spec header(header(), headers()) -> 'undefined' | any().
--spec header(header(), headers(), any()) -> 'undefined' | any().
-
--spec extract_headers(rabbit_types:content()) -> headers().
-
--spec map_headers
- (fun((headers()) -> headers()), rabbit_types:content()) ->
- rabbit_types:content().
-
--spec header_routes(undefined | rabbit_framing:amqp_table()) -> [string()].
--spec build_content
- (rabbit_framing:amqp_property_record(), binary() | [binary()]) ->
- rabbit_types:content().
--spec from_content
- (rabbit_types:content()) ->
- {rabbit_framing:amqp_property_record(), binary()}.
--spec parse_expiration
- (rabbit_framing:amqp_property_record()) ->
- rabbit_types:ok_or_error2('undefined' | non_neg_integer(), any()).
-
-%%----------------------------------------------------------------------------
-
-%% Convenience function, for avoiding round-trips in calls across the
-%% erlang distributed network.
publish(Exchange, RoutingKeyBin, Properties, Body) ->
publish(Exchange, RoutingKeyBin, false, Properties, Body).
%% Convenience function, for avoiding round-trips in calls across the
%% erlang distributed network.
+
+-spec publish
+ (exchange_input(), rabbit_router:routing_key(), boolean(),
+ properties_input(), body_input()) ->
+ publish_result().
+
publish(X = #exchange{name = XName}, RKey, Mandatory, Props, Body) ->
Message = message(XName, RKey, properties(Props), Body),
publish(X, delivery(Mandatory, false, Message, undefined));
@@ -99,6 +65,8 @@ publish(XName, RKey, Mandatory, Props, Body) ->
Message = message(XName, RKey, properties(Props), Body),
publish(delivery(Mandatory, false, Message, undefined)).
+-spec publish(rabbit_types:delivery()) -> publish_result().
+
publish(Delivery = #delivery{
message = #basic_message{exchange_name = XName}}) ->
case rabbit_exchange:lookup(XName) of
@@ -111,10 +79,18 @@ publish(X, Delivery) ->
DeliveredQPids = rabbit_amqqueue:deliver(Qs, Delivery),
{ok, DeliveredQPids}.
+-spec delivery
+ (boolean(), boolean(), rabbit_types:message(), undefined | integer()) ->
+ rabbit_types:delivery().
+
delivery(Mandatory, Confirm, Message, MsgSeqNo) ->
#delivery{mandatory = Mandatory, confirm = Confirm, sender = self(),
message = Message, msg_seq_no = MsgSeqNo, flow = noflow}.
+-spec build_content
+ (rabbit_framing:amqp_property_record(), binary() | [binary()]) ->
+ rabbit_types:content().
+
build_content(Properties, BodyBin) when is_binary(BodyBin) ->
build_content(Properties, [BodyBin]);
@@ -128,6 +104,10 @@ build_content(Properties, PFR) ->
protocol = none,
payload_fragments_rev = PFR}.
+-spec from_content
+ (rabbit_types:content()) ->
+ {rabbit_framing:amqp_property_record(), binary()}.
+
from_content(Content) ->
#content{class_id = ClassId,
properties = Props,
@@ -153,6 +133,11 @@ strip_header(#content{properties = Props = #'P_basic'{headers = Headers}}
headers = Headers0}})
end.
+-spec message
+ (rabbit_exchange:name(), rabbit_router:routing_key(),
+ rabbit_types:decoded_content()) ->
+ rabbit_types:ok_or_error2(rabbit_types:message(), any()).
+
message(XName, RoutingKey, #content{properties = Props} = DecodedContent) ->
try
{ok, #basic_message{
@@ -166,12 +151,20 @@ message(XName, RoutingKey, #content{properties = Props} = DecodedContent) ->
{error, _Reason} = Error -> Error
end.
+-spec message
+ (rabbit_exchange:name(), rabbit_router:routing_key(), properties_input(),
+ binary()) ->
+ rabbit_types:message().
+
message(XName, RoutingKey, RawProperties, Body) ->
Properties = properties(RawProperties),
Content = build_content(Properties, Body),
{ok, Msg} = message(XName, RoutingKey, Content),
Msg.
+-spec properties
+ (properties_input()) -> rabbit_framing:amqp_property_record().
+
properties(P = #'P_basic'{}) ->
P;
properties(P) when is_list(P) ->
@@ -185,6 +178,9 @@ properties(P) when is_list(P) ->
end
end, #'P_basic'{}, P).
+-spec prepend_table_header
+ (binary(), rabbit_framing:amqp_table(), headers()) -> headers().
+
prepend_table_header(Name, Info, undefined) ->
prepend_table_header(Name, Info, []);
prepend_table_header(Name, Info, Headers) ->
@@ -224,6 +220,8 @@ update_invalid(Name, Value, ExistingHdr, Header) ->
NewHdr = rabbit_misc:set_table_value(ExistingHdr, Name, array, Values),
set_invalid(NewHdr, Header).
+-spec header(header(), headers()) -> 'undefined' | any().
+
header(_Header, undefined) ->
undefined;
header(_Header, []) ->
@@ -231,12 +229,16 @@ header(_Header, []) ->
header(Header, Headers) ->
header(Header, Headers, undefined).
+-spec header(header(), headers(), any()) -> 'undefined' | any().
+
header(Header, Headers, Default) ->
case lists:keysearch(Header, 1, Headers) of
false -> Default;
{value, Val} -> Val
end.
+-spec extract_headers(rabbit_types:content()) -> headers().
+
extract_headers(Content) ->
#content{properties = #'P_basic'{headers = Headers}} =
rabbit_binary_parser:ensure_content_decoded(Content),
@@ -247,6 +249,10 @@ extract_timestamp(Content) ->
rabbit_binary_parser:ensure_content_decoded(Content),
Timestamp.
+-spec map_headers
+ (fun((headers()) -> headers()), rabbit_types:content()) ->
+ rabbit_types:content().
+
map_headers(F, Content) ->
Content1 = rabbit_binary_parser:ensure_content_decoded(Content),
#content{properties = #'P_basic'{headers = Headers} = Props} = Content1,
@@ -270,6 +276,9 @@ is_message_persistent(#content{properties = #'P_basic'{
end.
%% Extract CC routes from headers
+
+-spec header_routes(undefined | rabbit_framing:amqp_table()) -> [string()].
+
header_routes(undefined) ->
[];
header_routes(HeadersTable) ->
@@ -281,6 +290,10 @@ header_routes(HeadersTable) ->
binary_to_list(HeaderKey), Type}})
end || HeaderKey <- ?ROUTING_HEADERS]).
+-spec parse_expiration
+ (rabbit_framing:amqp_property_record()) ->
+ rabbit_types:ok_or_error2('undefined' | non_neg_integer(), any()).
+
parse_expiration(#'P_basic'{expiration = undefined}) ->
{ok, undefined};
parse_expiration(#'P_basic'{expiration = Expiration}) ->
diff --git a/src/rabbit_binding.erl b/src/rabbit_binding.erl
index bb1c754b5c..ab3bc6c819 100644
--- a/src/rabbit_binding.erl
+++ b/src/rabbit_binding.erl
@@ -15,7 +15,8 @@
%%
-module(rabbit_binding).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-export([recover/0, recover/2, exists/1, add/2, add/3, remove/1, remove/3, list/1]).
-export([list_for_source/1, list_for_destination/1,
@@ -44,7 +45,7 @@
{'resources_missing',
[{'not_found', (rabbit_types:binding_source() |
rabbit_types:binding_destination())} |
- {'absent', rabbit_types:amqqueue()}]}).
+ {'absent', amqqueue:amqqueue()}]}).
-type bind_ok_or_error() :: 'ok' | bind_errors() |
rabbit_types:error(
@@ -53,7 +54,7 @@
-type bind_res() :: bind_ok_or_error() | rabbit_misc:thunk(bind_ok_or_error()).
-type inner_fun() ::
fun((rabbit_types:exchange(),
- rabbit_types:exchange() | rabbit_types:amqqueue()) ->
+ rabbit_types:exchange() | amqqueue:amqqueue()) ->
rabbit_types:ok_or_error(rabbit_types:amqp_error())).
-type bindings() :: [rabbit_types:binding()].
@@ -61,47 +62,6 @@
%% dialyzer into objecting to everything that uses it.
-type deletions() :: dict:dict().
--spec recover([rabbit_exchange:name()], [rabbit_amqqueue:name()]) ->
- 'ok'.
--spec exists(rabbit_types:binding()) -> boolean() | bind_errors().
--spec add(rabbit_types:binding(), rabbit_types:username()) -> bind_res().
--spec add(rabbit_types:binding(), inner_fun(), rabbit_types:username()) -> bind_res().
--spec remove(rabbit_types:binding()) -> bind_res().
--spec remove(rabbit_types:binding(), inner_fun(), rabbit_types:username()) -> bind_res().
--spec list(rabbit_types:vhost()) -> bindings().
--spec list_for_source
- (rabbit_types:binding_source()) -> bindings().
--spec list_for_destination
- (rabbit_types:binding_destination()) -> bindings().
--spec list_for_source_and_destination
- (rabbit_types:binding_source(), rabbit_types:binding_destination()) ->
- bindings().
--spec info_keys() -> rabbit_types:info_keys().
--spec info(rabbit_types:binding()) -> rabbit_types:infos().
--spec info(rabbit_types:binding(), rabbit_types:info_keys()) ->
- rabbit_types:infos().
--spec info_all(rabbit_types:vhost()) -> [rabbit_types:infos()].
--spec info_all(rabbit_types:vhost(), rabbit_types:info_keys()) ->
- [rabbit_types:infos()].
--spec info_all(rabbit_types:vhost(), rabbit_types:info_keys(),
- reference(), pid()) -> 'ok'.
--spec has_for_source(rabbit_types:binding_source()) -> boolean().
--spec remove_for_source(rabbit_types:binding_source()) -> bindings().
--spec remove_for_destination
- (rabbit_types:binding_destination(), boolean()) -> deletions().
--spec remove_transient_for_destination
- (rabbit_types:binding_destination()) -> deletions().
--spec process_deletions(deletions(), rabbit_types:username()) -> rabbit_misc:thunk('ok').
--spec combine_deletions(deletions(), deletions()) -> deletions().
--spec add_deletion
- (rabbit_exchange:name(),
- {'undefined' | rabbit_types:exchange(),
- 'deleted' | 'not_deleted',
- bindings()},
- deletions()) ->
- deletions().
--spec new_deletions() -> deletions().
-
%%----------------------------------------------------------------------------
-define(INFO_KEYS, [source_name, source_kind,
@@ -110,6 +70,10 @@
vhost]).
%% Global table recovery
+
+-spec recover([rabbit_exchange:name()], [rabbit_amqqueue:name()]) ->
+ 'ok'.
+
recover() ->
rabbit_misc:table_filter(
fun (Route) ->
@@ -163,6 +127,8 @@ recover_semi_durable_route_txn(R = #route{binding = B}, X) ->
(Serial, false) -> x_callback(Serial, X, add_binding, B)
end).
+-spec exists(rabbit_types:binding()) -> boolean() | bind_errors().
+
exists(#binding{source = ?DEFAULT_EXCHANGE(_),
destination = #resource{kind = queue, name = QName} = Queue,
key = QName,
@@ -177,8 +143,12 @@ exists(Binding) ->
rabbit_misc:const(mnesia:read({rabbit_route, B}) /= [])
end, fun not_found_or_absent_errs/1).
+-spec add(rabbit_types:binding(), rabbit_types:username()) -> bind_res().
+
add(Binding, ActingUser) -> add(Binding, fun (_Src, _Dst) -> ok end, ActingUser).
+-spec add(rabbit_types:binding(), inner_fun(), rabbit_types:username()) -> bind_res().
+
add(Binding, InnerFun, ActingUser) ->
binding_action(
Binding,
@@ -223,8 +193,12 @@ add(Src, Dst, B, ActingUser) ->
true -> rabbit_misc:const({error, binding_not_found})
end.
+-spec remove(rabbit_types:binding()) -> bind_res().
+
remove(Binding) -> remove(Binding, fun (_Src, _Dst) -> ok end, ?INTERNAL_USER).
+-spec remove(rabbit_types:binding(), inner_fun(), rabbit_types:username()) -> bind_res().
+
remove(Binding, InnerFun, ActingUser) ->
binding_action(
Binding,
@@ -254,9 +228,13 @@ remove(Src, Dst, B, ActingUser) ->
%% Implicit bindings are implicit as of rabbitmq/rabbitmq-server#1721.
remove_default_exchange_binding_rows_of(Dst = #resource{}) ->
- case rabbit_binding:implicit_for_destination(Dst) of
+ case implicit_for_destination(Dst) of
[Binding] ->
- mnesia:dirty_delete(rabbit_durable_route, Binding);
+ mnesia:dirty_delete(rabbit_durable_route, Binding),
+ mnesia:dirty_delete(rabbit_semi_durable_route, Binding),
+ mnesia:dirty_delete(rabbit_reverse_route,
+ reverse_binding(Binding)),
+ mnesia:dirty_delete(rabbit_route, Binding);
_ ->
%% no binding to remove or
%% a competing tx has beaten us to it?
@@ -264,6 +242,8 @@ remove_default_exchange_binding_rows_of(Dst = #resource{}) ->
end,
ok.
+-spec list(rabbit_types:vhost()) -> bindings().
+
list(VHostPath) ->
VHostResource = rabbit_misc:r(VHostPath, '_'),
Route = #route{binding = #binding{source = VHostResource,
@@ -279,6 +259,9 @@ list(VHostPath) ->
end, AllBindings),
implicit_bindings(VHostPath) ++ Filtered.
+-spec list_for_source
+ (rabbit_types:binding_source()) -> bindings().
+
list_for_source(?DEFAULT_EXCHANGE(VHostPath)) ->
implicit_bindings(VHostPath);
list_for_source(SrcName) ->
@@ -289,6 +272,9 @@ list_for_source(SrcName) ->
<- mnesia:match_object(rabbit_route, Route, read)]
end).
+-spec list_for_destination
+ (rabbit_types:binding_destination()) -> bindings().
+
list_for_destination(DstName) ->
implicit_for_destination(DstName) ++
mnesia:async_dirty(
@@ -310,8 +296,8 @@ implicit_bindings(VHostPath) ->
|| DstQueue = #resource{name = QName} <- DstQueues ].
implicit_for_destination(DstQueue = #resource{kind = queue,
- virtual_host = VHostPath,
- name = QName}) ->
+ virtual_host = VHostPath,
+ name = QName}) ->
[#binding{source = ?DEFAULT_EXCHANGE(VHostPath),
destination = DstQueue,
key = QName,
@@ -319,6 +305,10 @@ implicit_for_destination(DstQueue = #resource{kind = queue,
implicit_for_destination(_) ->
[].
+-spec list_for_source_and_destination
+ (rabbit_types:binding_source(), rabbit_types:binding_destination()) ->
+ bindings().
+
list_for_source_and_destination(?DEFAULT_EXCHANGE(VHostPath),
#resource{kind = queue,
virtual_host = VHostPath,
@@ -337,6 +327,8 @@ list_for_source_and_destination(SrcName, DstName) ->
Route, read)]
end).
+-spec info_keys() -> rabbit_types:info_keys().
+
info_keys() -> ?INFO_KEYS.
map(VHostPath, F) ->
@@ -355,18 +347,33 @@ i(routing_key, #binding{key = RoutingKey}) -> RoutingKey;
i(arguments, #binding{args = Arguments}) -> Arguments;
i(Item, _) -> throw({bad_argument, Item}).
+-spec info(rabbit_types:binding()) -> rabbit_types:infos().
+
info(B = #binding{}) -> infos(?INFO_KEYS, B).
+-spec info(rabbit_types:binding(), rabbit_types:info_keys()) ->
+ rabbit_types:infos().
+
info(B = #binding{}, Items) -> infos(Items, B).
+-spec info_all(rabbit_types:vhost()) -> [rabbit_types:infos()].
+
info_all(VHostPath) -> map(VHostPath, fun (B) -> info(B) end).
+-spec info_all(rabbit_types:vhost(), rabbit_types:info_keys()) ->
+ [rabbit_types:infos()].
+
info_all(VHostPath, Items) -> map(VHostPath, fun (B) -> info(B, Items) end).
+-spec info_all(rabbit_types:vhost(), rabbit_types:info_keys(),
+ reference(), pid()) -> 'ok'.
+
info_all(VHostPath, Items, Ref, AggregatorPid) ->
rabbit_control_misc:emitting_map(
AggregatorPid, Ref, fun(B) -> info(B, Items) end, list(VHostPath)).
+-spec has_for_source(rabbit_types:binding_source()) -> boolean().
+
has_for_source(SrcName) ->
Match = #route{binding = #binding{source = SrcName, _ = '_'}},
%% we need to check for semi-durable routes (which subsumes
@@ -376,6 +383,8 @@ has_for_source(SrcName) ->
contains(rabbit_route, Match) orelse
contains(rabbit_semi_durable_route, Match).
+-spec remove_for_source(rabbit_types:binding_source()) -> bindings().
+
remove_for_source(SrcName) ->
lock_resource(SrcName),
Match = #route{binding = #binding{source = SrcName, _ = '_'}},
@@ -384,16 +393,23 @@ remove_for_source(SrcName) ->
mnesia:dirty_match_object(rabbit_route, Match) ++
mnesia:dirty_match_object(rabbit_semi_durable_route, Match))).
+-spec remove_for_destination
+ (rabbit_types:binding_destination(), boolean()) -> deletions().
+
remove_for_destination(DstName, OnlyDurable) ->
remove_for_destination(DstName, OnlyDurable, fun remove_routes/1).
+-spec remove_transient_for_destination
+ (rabbit_types:binding_destination()) -> deletions().
+
remove_transient_for_destination(DstName) ->
remove_for_destination(DstName, false, fun remove_transient_routes/1).
%%----------------------------------------------------------------------------
durable(#exchange{durable = D}) -> D;
-durable(#amqqueue{durable = D}) -> D.
+durable(Q) when ?is_amqqueue(Q) ->
+ amqqueue:is_durable(Q).
binding_action(Binding = #binding{source = SrcName,
destination = DstName,
@@ -591,12 +607,24 @@ anything_but( NotThis, NotThis, This) -> This;
anything_but( NotThis, This, NotThis) -> This;
anything_but(_NotThis, This, This) -> This.
+-spec new_deletions() -> deletions().
+
new_deletions() -> dict:new().
+-spec add_deletion
+ (rabbit_exchange:name(),
+ {'undefined' | rabbit_types:exchange(),
+ 'deleted' | 'not_deleted',
+ bindings()},
+ deletions()) ->
+ deletions().
+
add_deletion(XName, Entry, Deletions) ->
dict:update(XName, fun (Entry1) -> merge_entry(Entry1, Entry) end,
Entry, Deletions).
+-spec combine_deletions(deletions(), deletions()) -> deletions().
+
combine_deletions(Deletions1, Deletions2) ->
dict:merge(fun (_XName, Entry1, Entry2) -> merge_entry(Entry1, Entry2) end,
Deletions1, Deletions2).
@@ -606,6 +634,8 @@ merge_entry({X1, Deleted1, Bindings1}, {X2, Deleted2, Bindings2}) ->
anything_but(not_deleted, Deleted1, Deleted2),
[Bindings1 | Bindings2]}.
+-spec process_deletions(deletions(), rabbit_types:username()) -> rabbit_misc:thunk('ok').
+
process_deletions(Deletions, ActingUser) ->
AugmentedDeletions =
dict:map(fun (_XName, {X, deleted, Bindings}) ->
diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl
index 634789adab..ffd7c8fabc 100644
--- a/src/rabbit_channel.erl
+++ b/src/rabbit_channel.erl
@@ -51,6 +51,7 @@
-include_lib("rabbit_common/include/rabbit_framing.hrl").
-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-behaviour(gen_server2).
@@ -61,10 +62,14 @@
emit_info_all/4, info_local/1]).
-export([refresh_config_local/0, ready_for_close/1]).
-export([refresh_interceptors/0]).
+-export([force_event_refresh/1]).
-export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2,
handle_info/2, handle_pre_hibernate/1, prioritise_call/4,
prioritise_cast/3, prioritise_info/3, format_message_queue/2]).
+
+-deprecated([{force_event_refresh, 1, eventually}]).
+
%% Internal
-export([list_local/0, emit_info_local/3, deliver_reply_local/3]).
-export([get_vhost/1, get_user/1]).
@@ -220,40 +225,13 @@
-type channel() :: #ch{}.
+%%----------------------------------------------------------------------------
+
-spec start_link
(channel_number(), pid(), pid(), pid(), string(), rabbit_types:protocol(),
rabbit_types:user(), rabbit_types:vhost(), rabbit_framing:amqp_table(),
pid(), pid()) ->
rabbit_types:ok_pid_or_error().
--spec do(pid(), rabbit_framing:amqp_method_record()) -> 'ok'.
--spec do
- (pid(), rabbit_framing:amqp_method_record(),
- rabbit_types:maybe(rabbit_types:content())) ->
- 'ok'.
--spec do_flow
- (pid(), rabbit_framing:amqp_method_record(),
- rabbit_types:maybe(rabbit_types:content())) ->
- 'ok'.
--spec flush(pid()) -> 'ok'.
--spec shutdown(pid()) -> 'ok'.
--spec send_command(pid(), rabbit_framing:amqp_method_record()) -> 'ok'.
--spec deliver
- (pid(), rabbit_types:ctag(), boolean(), rabbit_amqqueue:qmsg()) -> 'ok'.
--spec deliver_reply(binary(), rabbit_types:delivery()) -> 'ok'.
--spec deliver_reply_local(pid(), binary(), rabbit_types:delivery()) -> 'ok'.
--spec send_credit_reply(pid(), non_neg_integer()) -> 'ok'.
--spec send_drained(pid(), [{rabbit_types:ctag(), non_neg_integer()}]) -> 'ok'.
--spec list() -> [pid()].
--spec list_local() -> [pid()].
--spec info_keys() -> rabbit_types:info_keys().
--spec info(pid()) -> rabbit_types:infos().
--spec info(pid(), rabbit_types:info_keys()) -> rabbit_types:infos().
--spec info_all() -> [rabbit_types:infos()].
--spec info_all(rabbit_types:info_keys()) -> [rabbit_types:infos()].
--spec refresh_config_local() -> 'ok'.
--spec ready_for_close(pid()) -> 'ok'.
-
-%%----------------------------------------------------------------------------
start_link(Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User,
VHost, Capabilities, CollectorPid, Limiter) ->
@@ -261,27 +239,50 @@ start_link(Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol, User,
?MODULE, [Channel, ReaderPid, WriterPid, ConnPid, ConnName, Protocol,
User, VHost, Capabilities, CollectorPid, Limiter], []).
+-spec do(pid(), rabbit_framing:amqp_method_record()) -> 'ok'.
+
do(Pid, Method) ->
rabbit_channel_common:do(Pid, Method).
+-spec do
+ (pid(), rabbit_framing:amqp_method_record(),
+ rabbit_types:maybe(rabbit_types:content())) ->
+ 'ok'.
+
do(Pid, Method, Content) ->
rabbit_channel_common:do(Pid, Method, Content).
+-spec do_flow
+ (pid(), rabbit_framing:amqp_method_record(),
+ rabbit_types:maybe(rabbit_types:content())) ->
+ 'ok'.
+
do_flow(Pid, Method, Content) ->
rabbit_channel_common:do_flow(Pid, Method, Content).
+-spec flush(pid()) -> 'ok'.
+
flush(Pid) ->
gen_server2:call(Pid, flush, infinity).
+-spec shutdown(pid()) -> 'ok'.
+
shutdown(Pid) ->
gen_server2:cast(Pid, terminate).
+-spec send_command(pid(), rabbit_framing:amqp_method_record()) -> 'ok'.
+
send_command(Pid, Msg) ->
gen_server2:cast(Pid, {command, Msg}).
+-spec deliver
+ (pid(), rabbit_types:ctag(), boolean(), rabbit_amqqueue:qmsg()) -> 'ok'.
+
deliver(Pid, ConsumerTag, AckRequired, Msg) ->
gen_server2:cast(Pid, {deliver, ConsumerTag, AckRequired, Msg}).
+-spec deliver_reply(binary(), rabbit_types:delivery()) -> 'ok'.
+
deliver_reply(<<"amq.rabbitmq.reply-to.", Rest/binary>>, Delivery) ->
case decode_fast_reply_to(Rest) of
{ok, Pid, Key} ->
@@ -293,6 +294,9 @@ deliver_reply(<<"amq.rabbitmq.reply-to.", Rest/binary>>, Delivery) ->
%% We want to ensure people can't use this mechanism to send a message
%% to an arbitrary process and kill it!
+
+-spec deliver_reply_local(pid(), binary(), rabbit_types:delivery()) -> 'ok'.
+
deliver_reply_local(Pid, Key, Delivery) ->
case pg_local:in_group(rabbit_channels, Pid) of
true -> gen_server2:cast(Pid, {deliver_reply, Key, Delivery});
@@ -321,21 +325,33 @@ decode_fast_reply_to(Rest) ->
_ -> error
end.
+-spec send_credit_reply(pid(), non_neg_integer()) -> 'ok'.
+
send_credit_reply(Pid, Len) ->
gen_server2:cast(Pid, {send_credit_reply, Len}).
+-spec send_drained(pid(), [{rabbit_types:ctag(), non_neg_integer()}]) -> 'ok'.
+
send_drained(Pid, CTagCredit) ->
gen_server2:cast(Pid, {send_drained, CTagCredit}).
+-spec list() -> [pid()].
+
list() ->
rabbit_misc:append_rpc_all_nodes(rabbit_mnesia:cluster_nodes(running),
rabbit_channel, list_local, []).
+-spec list_local() -> [pid()].
+
list_local() ->
pg_local:get_members(rabbit_channels).
+-spec info_keys() -> rabbit_types:info_keys().
+
info_keys() -> ?INFO_KEYS.
+-spec info(pid()) -> rabbit_types:infos().
+
info(Pid) ->
{Timeout, Deadline} = get_operation_timeout_and_deadline(),
try
@@ -349,6 +365,8 @@ info(Pid) ->
throw(timeout)
end.
+-spec info(pid(), rabbit_types:info_keys()) -> rabbit_types:infos().
+
info(Pid, Items) ->
{Timeout, Deadline} = get_operation_timeout_and_deadline(),
try
@@ -362,9 +380,13 @@ info(Pid, Items) ->
throw(timeout)
end.
+-spec info_all() -> [rabbit_types:infos()].
+
info_all() ->
rabbit_misc:filter_exit_map(fun (C) -> info(C) end, list()).
+-spec info_all(rabbit_types:info_keys()) -> [rabbit_types:infos()].
+
info_all(Items) ->
rabbit_misc:filter_exit_map(fun (C) -> info(C, Items) end, list()).
@@ -382,6 +404,8 @@ emit_info(PidList, InfoItems, Ref, AggregatorPid) ->
rabbit_control_misc:emitting_map_with_exit_handler(
AggregatorPid, Ref, fun(C) -> info(C, InfoItems) end, PidList).
+-spec refresh_config_local() -> 'ok'.
+
refresh_config_local() ->
rabbit_misc:upmap(
fun (C) ->
@@ -410,9 +434,17 @@ refresh_interceptors() ->
list_local()),
ok.
+-spec ready_for_close(pid()) -> 'ok'.
+
ready_for_close(Pid) ->
rabbit_channel_common:ready_for_close(Pid).
+-spec force_event_refresh(reference()) -> 'ok'.
+
+force_event_refresh(Ref) ->
+ [gen_server2:cast(C, {force_event_refresh, Ref}) || C <- list()],
+ ok.
+
list_queue_states(Pid) ->
gen_server2:call(Pid, list_queue_states).
@@ -630,6 +662,21 @@ handle_cast({send_drained, CTagCredit}, State = #ch{writer_pid = WriterPid}) ->
|| {ConsumerTag, CreditDrained} <- CTagCredit],
noreply(State);
+handle_cast({force_event_refresh, Ref}, State) ->
+ rabbit_event:notify(channel_created, infos(?CREATION_EVENT_KEYS, State),
+ Ref),
+ noreply(rabbit_event:init_stats_timer(State, #ch.stats_timer));
+
+handle_cast({mandatory_received, _MsgSeqNo}, State) ->
+ %% This feature was used by `rabbit_amqqueue_process` and
+ %% `rabbit_mirror_queue_slave` up-to and including RabbitMQ 3.7.x.
+ %% It is unused in 3.8.x and thus deprecated. We keep it to support
+ %% in-place upgrades to 3.8.x (i.e. mixed-version clusters), but it
+ %% is a no-op starting with that version.
+ %%
+ %% NB: don't call noreply/1 since we don't want to send confirms.
+ noreply_coalesce(State);
+
handle_cast({reject_publish, MsgSeqNo, _QPid}, State = #ch{unconfirmed = UC}) ->
%% It does not matter which queue rejected the message,
%% if any queue rejected it - it should not be confirmed.
@@ -1366,7 +1413,8 @@ handle_method(#'basic.cancel'{consumer_tag = ConsumerTag, nowait = NoWait},
error ->
%% Spec requires we ignore this situation.
return_ok(State, NoWait, OkMsg);
- {ok, {Q = #amqqueue{pid = QPid}, _CParams}} ->
+ {ok, {Q, _CParams}} when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
ConsumerMapping1 = maps:remove(ConsumerTag, ConsumerMapping),
QRef = qpid_to_ref(QPid),
QCons1 =
@@ -1636,7 +1684,9 @@ basic_consume(QueueName, NoAck, ConsumerPrefetch, ActualConsumerTag,
Username, QueueStates0),
Q}
end) of
- {{ok, QueueStates}, Q = #amqqueue{pid = QPid, name = QName}} ->
+ {{ok, QueueStates}, Q} when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ QName = amqqueue:get_name(Q),
CM1 = maps:put(
ActualConsumerTag,
{Q, {NoAck, ConsumerPrefetch, ExclusiveConsume, Args}},
@@ -1649,7 +1699,9 @@ basic_consume(QueueName, NoAck, ConsumerPrefetch, ActualConsumerTag,
true -> consumer_monitor(ActualConsumerTag, State1);
false -> State1
end};
- {ok, Q = #amqqueue{pid = QPid, name = QName}} ->
+ {ok, Q} when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ QName = amqqueue:get_name(Q),
CM1 = maps:put(
ActualConsumerTag,
{Q, {NoAck, ConsumerPrefetch, ExclusiveConsume, Args}},
@@ -1674,7 +1726,8 @@ consumer_monitor(ConsumerTag,
State = #ch{consumer_mapping = ConsumerMapping,
queue_monitors = QMons,
queue_consumers = QCons}) ->
- {#amqqueue{pid = QPid}, _} = maps:get(ConsumerTag, ConsumerMapping),
+ {Q, _} = maps:get(ConsumerTag, ConsumerMapping),
+ QPid = amqqueue:get_pid(Q),
QRef = qpid_to_ref(QPid),
CTags1 = case maps:find(QRef, QCons) of
{ok, CTags} -> gb_sets:insert(ConsumerTag, CTags);
@@ -1782,7 +1835,7 @@ binding_action(Fun, SourceNameBin0, DestinationType, DestinationNameBin0,
destination = DestinationName,
key = RoutingKey,
args = Arguments},
- fun (_X, Q = #amqqueue{}) ->
+ fun (_X, Q) when ?is_amqqueue(Q) ->
try rabbit_amqqueue:check_exclusive_access(Q, ConnPid)
catch exit:Reason -> {error, Reason}
end;
@@ -1791,9 +1844,9 @@ binding_action(Fun, SourceNameBin0, DestinationType, DestinationNameBin0,
end,
Username) of
{error, {resources_missing, [{not_found, Name} | _]}} ->
- rabbit_misc:not_found(Name);
+ rabbit_amqqueue:not_found(Name);
{error, {resources_missing, [{absent, Q, Reason} | _]}} ->
- rabbit_misc:absent(Q, Reason);
+ rabbit_amqqueue:absent(Q, Reason);
{error, binding_not_found} ->
rabbit_misc:protocol_error(
not_found, "no binding ~s between ~s and ~s",
@@ -1956,8 +2009,9 @@ foreach_per_queue(F, UAL, Acc) ->
rabbit_misc:gb_trees_fold(fun (Key, Val, Acc0) -> F(Key, Val, Acc0) end, Acc, T).
consumer_queue_refs(Consumers) ->
- lists:usort([qpid_to_ref(QPid) || {_Key, {#amqqueue{pid = QPid}, _CParams}}
- <- maps:to_list(Consumers)]).
+ lists:usort([qpid_to_ref(amqqueue:get_pid(Q))
+ || {_Key, {Q, _CParams}} <- maps:to_list(Consumers),
+ amqqueue:is_amqqueue(Q)]).
%% tell the limiter about the number of acks that have been received
%% for messages delivered to subscribed consumers, but not acks for
@@ -2011,9 +2065,10 @@ deliver_to_queues({Delivery = #delivery{message = Message = #basic_message{
%% since alternative algorithms to update queue_names less
%% frequently would in fact be more expensive in the common case.
{QNames1, QMons1} =
- lists:foldl(fun (#amqqueue{pid = QPid, name = QName},
- {QNames0, QMons0}) ->
+ lists:foldl(fun (Q, {QNames0, QMons0}) when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
QRef = qpid_to_ref(QPid),
+ QName = amqqueue:get_name(Q),
{case maps:is_key(QRef, QNames0) of
true -> QNames0;
false -> maps:put(QRef, QName, QNames0)
@@ -2287,7 +2342,7 @@ handle_method(#'queue.declare'{queue = <<"amq.rabbitmq.reply-to",
QueueName = rabbit_misc:r(VHost, queue, StrippedQueueNameBin),
case declare_fast_reply_to(StrippedQueueNameBin) of
exists -> {ok, QueueName, 0, 1};
- not_found -> rabbit_misc:not_found(QueueName)
+ not_found -> rabbit_amqqueue:not_found(QueueName)
end;
handle_method(#'queue.declare'{queue = QueueNameBin,
passive = false,
@@ -2338,11 +2393,12 @@ handle_method(#'queue.declare'{queue = QueueNameBin,
end,
case rabbit_amqqueue:declare(QueueName, Durable, AutoDelete,
Args, Owner, Username) of
- {new, #amqqueue{pid = QPid}} ->
+ {new, Q} when ?is_amqqueue(Q) ->
%% We need to notify the reader within the channel
%% process so that we can be sure there are no
%% outstanding exclusive queues being declared as
%% the connection shuts down.
+ QPid = amqqueue:get_pid(Q),
ok = case {Owner, CollectorPid} of
{none, _} -> ok;
{_, none} -> ok; %% Supports call from mgmt API
@@ -2357,7 +2413,7 @@ handle_method(#'queue.declare'{queue = QueueNameBin,
handle_method(Declare, ConnPid, CollectorPid, VHostPath,
User);
{absent, Q, Reason} ->
- rabbit_misc:absent(Q, Reason);
+ rabbit_amqqueue:absent(Q, Reason);
{owner_died, _Q} ->
%% Presumably our own days are numbered since the
%% connection has died. Pretend the queue exists though,
@@ -2365,7 +2421,7 @@ handle_method(#'queue.declare'{queue = QueueNameBin,
{ok, QueueName, 0, 0}
end;
{error, {absent, Q, Reason}} ->
- rabbit_misc:absent(Q, Reason)
+ rabbit_amqqueue:absent(Q, Reason)
end;
handle_method(#'queue.declare'{queue = QueueNameBin,
nowait = NoWait,
@@ -2373,9 +2429,12 @@ handle_method(#'queue.declare'{queue = QueueNameBin,
ConnPid, _CollectorPid, VHostPath, _User) ->
StrippedQueueNameBin = strip_cr_lf(QueueNameBin),
QueueName = rabbit_misc:r(VHostPath, queue, StrippedQueueNameBin),
- {{ok, MessageCount, ConsumerCount}, #amqqueue{} = Q} =
- rabbit_amqqueue:with_or_die(
- QueueName, fun (Q) -> {maybe_stat(NoWait, Q), Q} end),
+ Fun = fun (Q0) ->
+ QStat = maybe_stat(NoWait, Q0),
+ {QStat, Q0}
+ end,
+ %% Note: no need to check if Q is an #amqqueue, with_or_die does it
+ {{ok, MessageCount, ConsumerCount}, Q} = rabbit_amqqueue:with_or_die(QueueName, Fun),
ok = rabbit_amqqueue:check_exclusive_access(Q, ConnPid),
{ok, QueueName, MessageCount, ConsumerCount};
handle_method(#'queue.delete'{queue = QueueNameBin,
@@ -2399,7 +2458,7 @@ handle_method(#'queue.delete'{queue = QueueNameBin,
{ok, 0};
({absent, Q, stopped}) -> rabbit_amqqueue:delete_crashed(Q, Username),
{ok, 0};
- ({absent, Q, Reason}) -> rabbit_misc:absent(Q, Reason)
+ ({absent, Q, Reason}) -> rabbit_amqqueue:absent(Q, Reason)
end) of
{error, in_use} ->
precondition_failed("~s in use", [rabbit_misc:rs(QueueName)]);
diff --git a/src/rabbit_channel_sup.erl b/src/rabbit_channel_sup.erl
index e2e24e2f38..86a0f15650 100644
--- a/src/rabbit_channel_sup.erl
+++ b/src/rabbit_channel_sup.erl
@@ -47,12 +47,12 @@
rabbit_types:protocol(), rabbit_types:user(), rabbit_types:vhost(),
rabbit_framing:amqp_table(), pid()}.
--spec start_link(start_link_args()) -> {'ok', pid(), {pid(), any()}}.
-
-define(FAIR_WAIT, 70000).
%%----------------------------------------------------------------------------
+-spec start_link(start_link_args()) -> {'ok', pid(), {pid(), any()}}.
+
start_link({tcp, Sock, Channel, FrameMax, ReaderPid, ConnName, Protocol, User,
VHost, Capabilities, Collector}) ->
{ok, SupPid} = supervisor2:start_link(
diff --git a/src/rabbit_channel_sup_sup.erl b/src/rabbit_channel_sup_sup.erl
index d39e3f3e9b..b813b42e89 100644
--- a/src/rabbit_channel_sup_sup.erl
+++ b/src/rabbit_channel_sup_sup.erl
@@ -32,14 +32,13 @@
%%----------------------------------------------------------------------------
-spec start_link() -> rabbit_types:ok_pid_or_error().
--spec start_channel(pid(), rabbit_channel_sup:start_link_args()) ->
- {'ok', pid(), {pid(), any()}}.
-
-%%----------------------------------------------------------------------------
start_link() ->
supervisor2:start_link(?MODULE, []).
+-spec start_channel(pid(), rabbit_channel_sup:start_link_args()) ->
+ {'ok', pid(), {pid(), any()}}.
+
start_channel(Pid, Args) ->
supervisor2:start_child(Pid, [Args]).
diff --git a/src/rabbit_client_sup.erl b/src/rabbit_client_sup.erl
index 982c1d1e62..8b49244982 100644
--- a/src/rabbit_client_sup.erl
+++ b/src/rabbit_client_sup.erl
@@ -28,19 +28,19 @@
-spec start_link(rabbit_types:mfargs()) ->
rabbit_types:ok_pid_or_error().
--spec start_link({'local', atom()}, rabbit_types:mfargs()) ->
- rabbit_types:ok_pid_or_error().
--spec start_link_worker({'local', atom()}, rabbit_types:mfargs()) ->
- rabbit_types:ok_pid_or_error().
-
-%%----------------------------------------------------------------------------
start_link(Callback) ->
supervisor2:start_link(?MODULE, Callback).
+-spec start_link({'local', atom()}, rabbit_types:mfargs()) ->
+ rabbit_types:ok_pid_or_error().
+
start_link(SupName, Callback) ->
supervisor2:start_link(SupName, ?MODULE, Callback).
+-spec start_link_worker({'local', atom()}, rabbit_types:mfargs()) ->
+ rabbit_types:ok_pid_or_error().
+
start_link_worker(SupName, Callback) ->
supervisor2:start_link(SupName, ?MODULE, {Callback, worker}).
diff --git a/src/rabbit_connection_helper_sup.erl b/src/rabbit_connection_helper_sup.erl
index 8c4ba88da2..c5a0825d83 100644
--- a/src/rabbit_connection_helper_sup.erl
+++ b/src/rabbit_connection_helper_sup.erl
@@ -38,21 +38,21 @@
%%----------------------------------------------------------------------------
-spec start_link() -> rabbit_types:ok_pid_or_error().
--spec start_channel_sup_sup(pid()) -> rabbit_types:ok_pid_or_error().
--spec start_queue_collector(pid(), rabbit_types:proc_name()) ->
- rabbit_types:ok_pid_or_error().
-
-%%----------------------------------------------------------------------------
start_link() ->
supervisor2:start_link(?MODULE, []).
+-spec start_channel_sup_sup(pid()) -> rabbit_types:ok_pid_or_error().
+
start_channel_sup_sup(SupPid) ->
supervisor2:start_child(
SupPid,
{channel_sup_sup, {rabbit_channel_sup_sup, start_link, []},
intrinsic, infinity, supervisor, [rabbit_channel_sup_sup]}).
+-spec start_queue_collector(pid(), rabbit_types:proc_name()) ->
+ rabbit_types:ok_pid_or_error().
+
start_queue_collector(SupPid, Identity) ->
supervisor2:start_child(
SupPid,
diff --git a/src/rabbit_connection_sup.erl b/src/rabbit_connection_sup.erl
index 6e85514d44..51661dd8b6 100644
--- a/src/rabbit_connection_sup.erl
+++ b/src/rabbit_connection_sup.erl
@@ -38,9 +38,6 @@
-spec start_link(any(), rabbit_net:socket(), module(), any()) ->
{'ok', pid(), pid()}.
--spec reader(pid()) -> pid().
-
-%%--------------------------------------------------------------------------
start_link(Ref, _Sock, _Transport, _Opts) ->
{ok, SupPid} = supervisor2:start_link(?MODULE, []),
@@ -66,6 +63,8 @@ start_link(Ref, _Sock, _Transport, _Opts) ->
intrinsic, ?WORKER_WAIT, worker, [rabbit_reader]}),
{ok, SupPid, ReaderPid}.
+-spec reader(pid()) -> pid().
+
reader(Pid) ->
hd(supervisor2:find_child(Pid, reader)).
diff --git a/src/rabbit_core_ff.erl b/src/rabbit_core_ff.erl
new file mode 100644
index 0000000000..a13099e304
--- /dev/null
+++ b/src/rabbit_core_ff.erl
@@ -0,0 +1,97 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2018 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_core_ff).
+
+-export([quorum_queue_migration/3,
+ implicit_default_bindings_migration/3]).
+
+-rabbit_feature_flag(
+ {quorum_queue,
+ #{desc => "Support queues of type `quorum`",
+ doc_url => "http://www.rabbitmq.com/quorum-queues.html",
+ stability => stable,
+ migration_fun => {?MODULE, quorum_queue_migration}
+ }}).
+
+-rabbit_feature_flag(
+ {implicit_default_bindings,
+ #{desc => "Default bindings are now implicit, instead of "
+ "being stored in the database",
+ stability => stable,
+ migration_fun => {?MODULE, implicit_default_bindings_migration}
+ }}).
+
+%% -------------------------------------------------------------------
+%% Quorum queues.
+%% -------------------------------------------------------------------
+
+-define(quorum_queue_tables, [rabbit_queue,
+ rabbit_durable_queue]).
+
+quorum_queue_migration(FeatureName, _FeatureProps, enable) ->
+ Tables = ?quorum_queue_tables,
+ rabbit_table:wait(Tables),
+ Fields = amqqueue:fields(amqqueue_v2),
+ migrate_to_amqqueue_with_type(FeatureName, Tables, Fields);
+quorum_queue_migration(_FeatureName, _FeatureProps, is_enabled) ->
+ Tables = ?quorum_queue_tables,
+ rabbit_table:wait(Tables),
+ Fields = amqqueue:fields(amqqueue_v2),
+ mnesia:table_info(rabbit_queue, attributes) =:= Fields andalso
+ mnesia:table_info(rabbit_durable_queue, attributes) =:= Fields.
+
+migrate_to_amqqueue_with_type(FeatureName, [Table | Rest], Fields) ->
+ rabbit_log:info("Feature flag `~s`: migrating Mnesia table ~s...",
+ [FeatureName, Table]),
+ Fun = fun(Queue) -> amqqueue:upgrade_to(amqqueue_v2, Queue) end,
+ case mnesia:transform_table(Table, Fun, Fields) of
+ {atomic, ok} -> migrate_to_amqqueue_with_type(FeatureName,
+ Rest,
+ Fields);
+ {aborted, Reason} -> {error, Reason}
+ end;
+migrate_to_amqqueue_with_type(FeatureName, [], _) ->
+ rabbit_log:info("Feature flag `~s`: Mnesia tables migration done",
+ [FeatureName]),
+ ok.
+
+%% -------------------------------------------------------------------
+%% Default bindings.
+%% -------------------------------------------------------------------
+
+implicit_default_bindings_migration(FeatureName, _FeatureProps,
+ enable) ->
+ %% Default exchange bindings are now implicit (not stored in the
+ %% route tables). It should be safe to remove them outside of a
+ %% transaction.
+ rabbit_table:wait([rabbit_queue]),
+ Queues = mnesia:dirty_all_keys(rabbit_queue),
+ remove_explicit_default_bindings(FeatureName, Queues);
+implicit_default_bindings_migration(_Feature_Name, _FeatureProps,
+ is_enabled) ->
+ undefined.
+
+remove_explicit_default_bindings(_FeatureName, []) ->
+ ok;
+remove_explicit_default_bindings(FeatureName, Queues) ->
+ rabbit_log:info("Feature flag `~s`: deleting explicit "
+ "default bindings for ~b queues "
+ "(it may take some time)...",
+ [FeatureName, length(Queues)]),
+ [rabbit_binding:remove_default_exchange_binding_rows_of(Q)
+ || Q <- Queues],
+ ok.
diff --git a/src/rabbit_core_metrics_gc.erl b/src/rabbit_core_metrics_gc.erl
index d4c065e64f..586913ea2c 100644
--- a/src/rabbit_core_metrics_gc.erl
+++ b/src/rabbit_core_metrics_gc.erl
@@ -19,12 +19,12 @@
interval
}).
--spec start_link() -> rabbit_types:ok_pid_or_error().
-
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
+-spec start_link() -> rabbit_types:ok_pid_or_error().
+
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
diff --git a/src/rabbit_credential_validation.erl b/src/rabbit_credential_validation.erl
index 4b24d35f9d..ec0dea7893 100644
--- a/src/rabbit_credential_validation.erl
+++ b/src/rabbit_credential_validation.erl
@@ -27,8 +27,6 @@
-export([validate/2, backend/0]).
--spec validate(rabbit_types:username(), rabbit_types:password()) -> 'ok' | {'error', string()}.
-
%% Validates a username/password pair by delegating to the effective
%% `rabbit_credential_validator`. Used by `rabbit_auth_backend_internal`.
%% Note that some validators may choose to only validate passwords.
@@ -38,6 +36,8 @@
%% * ok: provided credentials passed validation.
%% * {error, Error, Args}: provided password password failed validation.
+-spec validate(rabbit_types:username(), rabbit_types:password()) -> 'ok' | {'error', string()}.
+
validate(Username, Password) ->
Backend = backend(),
Backend:validate(Username, Password).
diff --git a/src/rabbit_dead_letter.erl b/src/rabbit_dead_letter.erl
index 71ad8814b1..e26ea8297b 100644
--- a/src/rabbit_dead_letter.erl
+++ b/src/rabbit_dead_letter.erl
@@ -25,11 +25,11 @@
-type reason() :: 'expired' | 'rejected' | 'maxlen'.
+%%----------------------------------------------------------------------------
+
-spec publish(rabbit_types:message(), reason(), rabbit_types:exchange(),
'undefined' | binary(), rabbit_amqqueue:name()) -> 'ok'.
-%%----------------------------------------------------------------------------
-
publish(Msg, Reason, X, RK, QName) ->
DLMsg = make_msg(Msg, Reason, X#exchange.name, RK, QName),
Delivery = rabbit_basic:delivery(false, false, DLMsg, undefined),
diff --git a/src/rabbit_direct.erl b/src/rabbit_direct.erl
index f45ba3b1ca..50e8f3d2b0 100644
--- a/src/rabbit_direct.erl
+++ b/src/rabbit_direct.erl
@@ -16,8 +16,11 @@
-module(rabbit_direct).
--export([boot/0, list/0, connect/5,
+-export([boot/0, force_event_refresh/1, list/0, connect/5,
start_channel/9, disconnect/2]).
+
+-deprecated([{force_event_refresh, 1, eventually}]).
+
%% Internal
-export([list_local/0]).
@@ -29,34 +32,25 @@
%%----------------------------------------------------------------------------
-spec boot() -> 'ok'.
--spec list() -> [pid()].
--spec list_local() -> [pid()].
--spec connect
- (({'none', 'none'} | {rabbit_types:username(), 'none'} |
- {rabbit_types:username(), rabbit_types:password()}),
- rabbit_types:vhost(), rabbit_types:protocol(), pid(),
- rabbit_event:event_props()) ->
- rabbit_types:ok_or_error2(
- {rabbit_types:user(), rabbit_framing:amqp_table()},
- 'broker_not_found_on_node' |
- {'auth_failure', string()} | 'access_refused').
--spec start_channel
- (rabbit_channel:channel_number(), pid(), pid(), string(),
- rabbit_types:protocol(), rabbit_types:user(), rabbit_types:vhost(),
- rabbit_framing:amqp_table(), pid()) ->
- {'ok', pid()}.
--spec disconnect(pid(), rabbit_event:event_props()) -> 'ok'.
-
-%%----------------------------------------------------------------------------
boot() -> rabbit_sup:start_supervisor_child(
rabbit_direct_client_sup, rabbit_client_sup,
[{local, rabbit_direct_client_sup},
{rabbit_channel_sup, start_link, []}]).
+-spec force_event_refresh(reference()) -> 'ok'.
+
+force_event_refresh(Ref) ->
+ [Pid ! {force_event_refresh, Ref} || Pid <- list()],
+ ok.
+
+-spec list_local() -> [pid()].
+
list_local() ->
pg_local:get_members(rabbit_direct).
+-spec list() -> [pid()].
+
list() ->
rabbit_misc:append_rpc_all_nodes(rabbit_mnesia:cluster_nodes(running),
rabbit_direct, list_local, []).
@@ -76,6 +70,16 @@ auth_fun({Username, Password}, VHost, ExtraAuthProps) ->
[{password, Password}, {vhost, VHost}] ++ ExtraAuthProps)
end.
+-spec connect
+ (({'none', 'none'} | {rabbit_types:username(), 'none'} |
+ {rabbit_types:username(), rabbit_types:password()}),
+ rabbit_types:vhost(), rabbit_types:protocol(), pid(),
+ rabbit_event:event_props()) ->
+ rabbit_types:ok_or_error2(
+ {rabbit_types:user(), rabbit_framing:amqp_table()},
+ 'broker_not_found_on_node' |
+ {'auth_failure', string()} | 'access_refused').
+
connect(Creds, VHost, Protocol, Pid, Infos) ->
ExtraAuthProps = extract_extra_auth_props(Creds, VHost, Pid, Infos),
AuthFun = auth_fun(Creds, VHost, ExtraAuthProps),
@@ -194,6 +198,12 @@ connect1(User, VHost, Protocol, Pid, Infos) ->
{error, Reason}
end.
+-spec start_channel
+ (rabbit_channel:channel_number(), pid(), pid(), string(),
+ rabbit_types:protocol(), rabbit_types:user(), rabbit_types:vhost(),
+ rabbit_framing:amqp_table(), pid()) ->
+ {'ok', pid()}.
+
start_channel(Number, ClientChannelPid, ConnPid, ConnName, Protocol, User,
VHost, Capabilities, Collector) ->
{ok, _, {ChannelPid, _}} =
@@ -203,6 +213,8 @@ start_channel(Number, ClientChannelPid, ConnPid, ConnName, Protocol, User,
User, VHost, Capabilities, Collector}]),
{ok, ChannelPid}.
+-spec disconnect(pid(), rabbit_event:event_props()) -> 'ok'.
+
disconnect(Pid, Infos) ->
pg_local:leave(rabbit_direct, Pid),
rabbit_core_metrics:connection_closed(Pid),
diff --git a/src/rabbit_disk_monitor.erl b/src/rabbit_disk_monitor.erl
index 3ba1f35a6b..f48b0001fb 100644
--- a/src/rabbit_disk_monitor.erl
+++ b/src/rabbit_disk_monitor.erl
@@ -76,37 +76,43 @@
%%----------------------------------------------------------------------------
-type disk_free_limit() :: (integer() | string() | {'mem_relative', float() | integer()}).
--spec start_link(disk_free_limit()) -> rabbit_types:ok_pid_or_error().
--spec get_disk_free_limit() -> integer().
--spec set_disk_free_limit(disk_free_limit()) -> 'ok'.
--spec get_min_check_interval() -> integer().
--spec set_min_check_interval(integer()) -> 'ok'.
--spec get_max_check_interval() -> integer().
--spec set_max_check_interval(integer()) -> 'ok'.
--spec get_disk_free() -> (integer() | 'unknown').
%%----------------------------------------------------------------------------
%% Public API
%%----------------------------------------------------------------------------
+-spec get_disk_free_limit() -> integer().
+
get_disk_free_limit() ->
gen_server:call(?MODULE, get_disk_free_limit, infinity).
+-spec set_disk_free_limit(disk_free_limit()) -> 'ok'.
+
set_disk_free_limit(Limit) ->
gen_server:call(?MODULE, {set_disk_free_limit, Limit}, infinity).
+-spec get_min_check_interval() -> integer().
+
get_min_check_interval() ->
gen_server:call(?MODULE, get_min_check_interval, infinity).
+-spec set_min_check_interval(integer()) -> 'ok'.
+
set_min_check_interval(Interval) ->
gen_server:call(?MODULE, {set_min_check_interval, Interval}, infinity).
+-spec get_max_check_interval() -> integer().
+
get_max_check_interval() ->
gen_server:call(?MODULE, get_max_check_interval, infinity).
+-spec set_max_check_interval(integer()) -> 'ok'.
+
set_max_check_interval(Interval) ->
gen_server:call(?MODULE, {set_max_check_interval, Interval}, infinity).
+-spec get_disk_free() -> (integer() | 'unknown').
+
get_disk_free() ->
gen_server:call(?MODULE, get_disk_free, infinity).
@@ -114,6 +120,8 @@ get_disk_free() ->
%% gen_server callbacks
%%----------------------------------------------------------------------------
+-spec start_link(disk_free_limit()) -> rabbit_types:ok_pid_or_error().
+
start_link(Args) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [Args], []).
diff --git a/src/rabbit_epmd_monitor.erl b/src/rabbit_epmd_monitor.erl
index 15c7489e99..fb13f61ea3 100644
--- a/src/rabbit_epmd_monitor.erl
+++ b/src/rabbit_epmd_monitor.erl
@@ -29,10 +29,6 @@
-define(CHECK_FREQUENCY, 60000).
%%----------------------------------------------------------------------------
-
--spec start_link() -> rabbit_types:ok_pid_or_error().
-
-%%----------------------------------------------------------------------------
%% It's possible for epmd to be killed out from underneath us. If that
%% happens, then obviously clustering and rabbitmqctl stop
%% working. This process checks up on epmd and restarts it /
@@ -48,6 +44,8 @@
%% epmd" as a shutdown or uninstall step.
%% ----------------------------------------------------------------------------
+-spec start_link() -> rabbit_types:ok_pid_or_error().
+
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
diff --git a/src/rabbit_exchange.erl b/src/rabbit_exchange.erl
index 9c879ad041..1b262e9a48 100644
--- a/src/rabbit_exchange.erl
+++ b/src/rabbit_exchange.erl
@@ -36,77 +36,13 @@
-type type() :: atom().
-type fun_name() :: atom().
--spec recover(rabbit_types:vhost()) -> [name()].
--spec callback
- (rabbit_types:exchange(), fun_name(),
- fun((boolean()) -> non_neg_integer()) | atom(), [any()]) -> 'ok'.
--spec policy_changed
- (rabbit_types:exchange(), rabbit_types:exchange()) -> 'ok'.
--spec declare
- (name(), type(), boolean(), boolean(), boolean(),
- rabbit_framing:amqp_table(), rabbit_types:username())
- -> rabbit_types:exchange().
--spec check_type
- (binary()) -> atom() | rabbit_types:connection_exit().
--spec assert_equivalence
- (rabbit_types:exchange(), atom(), boolean(), boolean(), boolean(),
- rabbit_framing:amqp_table())
- -> 'ok' | rabbit_types:connection_exit().
--spec assert_args_equivalence
- (rabbit_types:exchange(), rabbit_framing:amqp_table())
- -> 'ok' | rabbit_types:connection_exit().
--spec lookup
- (name()) -> rabbit_types:ok(rabbit_types:exchange()) |
- rabbit_types:error('not_found').
--spec lookup_or_die
- (name()) -> rabbit_types:exchange() |
- rabbit_types:channel_exit().
--spec list() -> [rabbit_types:exchange()].
--spec list_names() -> [rabbit_exchange:name()].
--spec list(rabbit_types:vhost()) -> [rabbit_types:exchange()].
--spec lookup_scratch(name(), atom()) ->
- rabbit_types:ok(term()) |
- rabbit_types:error('not_found').
--spec update_scratch(name(), atom(), fun((any()) -> any())) -> 'ok'.
--spec update
- (name(),
- fun((rabbit_types:exchange()) -> rabbit_types:exchange()))
- -> not_found | rabbit_types:exchange().
--spec update_decorators(name()) -> 'ok'.
--spec immutable(rabbit_types:exchange()) -> rabbit_types:exchange().
--spec info_keys() -> rabbit_types:info_keys().
--spec info(rabbit_types:exchange()) -> rabbit_types:infos().
--spec info
- (rabbit_types:exchange(), rabbit_types:info_keys())
- -> rabbit_types:infos().
--spec info_all(rabbit_types:vhost()) -> [rabbit_types:infos()].
--spec info_all(rabbit_types:vhost(), rabbit_types:info_keys())
- -> [rabbit_types:infos()].
--spec info_all(rabbit_types:vhost(), rabbit_types:info_keys(),
- reference(), pid())
- -> 'ok'.
--spec route(rabbit_types:exchange(), rabbit_types:delivery())
- -> [rabbit_amqqueue:name()].
--spec delete
- (name(), 'true', rabbit_types:username()) ->
- 'ok'| rabbit_types:error('not_found' | 'in_use');
- (name(), 'false', rabbit_types:username()) ->
- 'ok' | rabbit_types:error('not_found').
--spec validate_binding
- (rabbit_types:exchange(), rabbit_types:binding())
- -> rabbit_types:ok_or_error({'binding_invalid', string(), [any()]}).
--spec maybe_auto_delete
- (rabbit_types:exchange(), boolean())
- -> 'not_deleted' | {'deleted', rabbit_binding:deletions()}.
--spec serial(rabbit_types:exchange()) ->
- fun((boolean()) -> 'none' | pos_integer()).
--spec peek_serial(name()) -> pos_integer() | 'undefined'.
-
%%----------------------------------------------------------------------------
-define(INFO_KEYS, [name, type, durable, auto_delete, internal, arguments,
policy, user_who_performed_action]).
+-spec recover(rabbit_types:vhost()) -> [name()].
+
recover(VHost) ->
Xs = rabbit_misc:table_filter(
fun (#exchange{name = XName}) ->
@@ -123,6 +59,10 @@ recover(VHost) ->
rabbit_durable_exchange),
[XName || #exchange{name = XName} <- Xs].
+-spec callback
+ (rabbit_types:exchange(), fun_name(),
+ fun((boolean()) -> non_neg_integer()) | atom(), [any()]) -> 'ok'.
+
callback(X = #exchange{type = XType,
decorators = Decorators}, Fun, Serial0, Args) ->
Serial = if is_function(Serial0) -> Serial0;
@@ -133,6 +73,9 @@ callback(X = #exchange{type = XType,
Module = type_to_module(XType),
apply(Module, Fun, [Serial(Module:serialise_events()) | Args]).
+-spec policy_changed
+ (rabbit_types:exchange(), rabbit_types:exchange()) -> 'ok'.
+
policy_changed(X = #exchange{type = XType,
decorators = Decorators},
X1 = #exchange{decorators = Decorators1}) ->
@@ -147,6 +90,9 @@ serialise_events(X = #exchange{type = Type, decorators = Decorators}) ->
rabbit_exchange_decorator:select(all, Decorators))
orelse (type_to_module(Type)):serialise_events().
+-spec serial(rabbit_types:exchange()) ->
+ fun((boolean()) -> 'none' | pos_integer()).
+
serial(#exchange{name = XName} = X) ->
Serial = case serialise_events(X) of
true -> next_serial(XName);
@@ -156,6 +102,11 @@ serial(#exchange{name = XName} = X) ->
(false) -> none
end.
+-spec declare
+ (name(), type(), boolean(), boolean(), boolean(),
+ rabbit_framing:amqp_table(), rabbit_types:username())
+ -> rabbit_types:exchange().
+
declare(XName, Type, Durable, AutoDelete, Internal, Args, Username) ->
X = rabbit_exchange_decorator:set(
rabbit_policy:set(#exchange{name = XName,
@@ -218,6 +169,10 @@ store_ram(X) ->
X1.
%% Used with binaries sent over the wire; the type may not exist.
+
+-spec check_type
+ (binary()) -> atom() | rabbit_types:connection_exit().
+
check_type(TypeBin) ->
case rabbit_registry:binary_to_type(TypeBin) of
{error, not_found} ->
@@ -232,6 +187,11 @@ check_type(TypeBin) ->
end
end.
+-spec assert_equivalence
+ (rabbit_types:exchange(), atom(), boolean(), boolean(), boolean(),
+ rabbit_framing:amqp_table())
+ -> 'ok' | rabbit_types:connection_exit().
+
assert_equivalence(X = #exchange{ name = XName,
durable = Durable,
auto_delete = AutoDelete,
@@ -245,6 +205,10 @@ assert_equivalence(X = #exchange{ name = XName,
AFE(Internal, ReqInternal, XName, internal),
(type_to_module(Type)):assert_args_equivalence(X, ReqArgs).
+-spec assert_args_equivalence
+ (rabbit_types:exchange(), rabbit_framing:amqp_table())
+ -> 'ok' | rabbit_types:connection_exit().
+
assert_args_equivalence(#exchange{ name = Name, arguments = Args },
RequiredArgs) ->
%% The spec says "Arguments are compared for semantic
@@ -253,21 +217,36 @@ assert_args_equivalence(#exchange{ name = Name, arguments = Args },
rabbit_misc:assert_args_equivalence(Args, RequiredArgs, Name,
[<<"alternate-exchange">>]).
+-spec lookup
+ (name()) -> rabbit_types:ok(rabbit_types:exchange()) |
+ rabbit_types:error('not_found').
+
lookup(Name) ->
rabbit_misc:dirty_read({rabbit_exchange, Name}).
+-spec lookup_or_die
+ (name()) -> rabbit_types:exchange() |
+ rabbit_types:channel_exit().
+
lookup_or_die(Name) ->
case lookup(Name) of
{ok, X} -> X;
- {error, not_found} -> rabbit_misc:not_found(Name)
+ {error, not_found} -> rabbit_amqqueue:not_found(Name)
end.
+-spec list() -> [rabbit_types:exchange()].
+
list() -> mnesia:dirty_match_object(rabbit_exchange, #exchange{_ = '_'}).
+-spec list_names() -> [rabbit_exchange:name()].
+
list_names() -> mnesia:dirty_all_keys(rabbit_exchange).
%% Not dirty_match_object since that would not be transactional when used in a
%% tx context
+
+-spec list(rabbit_types:vhost()) -> [rabbit_types:exchange()].
+
list(VHostPath) ->
mnesia:async_dirty(
fun () ->
@@ -277,6 +256,10 @@ list(VHostPath) ->
read)
end).
+-spec lookup_scratch(name(), atom()) ->
+ rabbit_types:ok(term()) |
+ rabbit_types:error('not_found').
+
lookup_scratch(Name, App) ->
case lookup(Name) of
{ok, #exchange{scratches = undefined}} ->
@@ -290,6 +273,8 @@ lookup_scratch(Name, App) ->
{error, not_found}
end.
+-spec update_scratch(name(), atom(), fun((any()) -> any())) -> 'ok'.
+
update_scratch(Name, App, Fun) ->
rabbit_misc:execute_mnesia_transaction(
fun() ->
@@ -310,6 +295,8 @@ update_scratch(Name, App, Fun) ->
ok
end).
+-spec update_decorators(name()) -> 'ok'.
+
update_decorators(Name) ->
rabbit_misc:execute_mnesia_transaction(
fun() ->
@@ -320,6 +307,11 @@ update_decorators(Name) ->
end
end).
+-spec update
+ (name(),
+ fun((rabbit_types:exchange()) -> rabbit_types:exchange()))
+ -> not_found | rabbit_types:exchange().
+
update(Name, Fun) ->
case mnesia:wread({rabbit_exchange, Name}) of
[X] -> X1 = Fun(X),
@@ -327,10 +319,14 @@ update(Name, Fun) ->
[] -> not_found
end.
+-spec immutable(rabbit_types:exchange()) -> rabbit_types:exchange().
+
immutable(X) -> X#exchange{scratches = none,
policy = none,
decorators = none}.
+-spec info_keys() -> rabbit_types:info_keys().
+
info_keys() -> ?INFO_KEYS.
map(VHostPath, F) ->
@@ -358,20 +354,38 @@ i(Item, #exchange{type = Type} = X) ->
[] -> throw({bad_argument, Item})
end.
+-spec info(rabbit_types:exchange()) -> rabbit_types:infos().
+
info(X = #exchange{type = Type}) ->
infos(?INFO_KEYS, X) ++ (type_to_module(Type)):info(X).
+-spec info
+ (rabbit_types:exchange(), rabbit_types:info_keys())
+ -> rabbit_types:infos().
+
info(X = #exchange{type = _Type}, Items) ->
infos(Items, X).
+-spec info_all(rabbit_types:vhost()) -> [rabbit_types:infos()].
+
info_all(VHostPath) -> map(VHostPath, fun (X) -> info(X) end).
+-spec info_all(rabbit_types:vhost(), rabbit_types:info_keys())
+ -> [rabbit_types:infos()].
+
info_all(VHostPath, Items) -> map(VHostPath, fun (X) -> info(X, Items) end).
+-spec info_all(rabbit_types:vhost(), rabbit_types:info_keys(),
+ reference(), pid())
+ -> 'ok'.
+
info_all(VHostPath, Items, Ref, AggregatorPid) ->
rabbit_control_misc:emitting_map(
AggregatorPid, Ref, fun(X) -> info(X, Items) end, list(VHostPath)).
+-spec route(rabbit_types:exchange(), rabbit_types:delivery())
+ -> [rabbit_amqqueue:name()].
+
route(#exchange{name = #resource{virtual_host = VHost, name = RName} = XName,
decorators = Decorators} = X,
#delivery{message = #basic_message{routing_keys = RKs}} = Delivery) ->
@@ -447,6 +461,12 @@ call_with_exchange(XName, Fun) ->
end
end).
+-spec delete
+ (name(), 'true', rabbit_types:username()) ->
+ 'ok'| rabbit_types:error('not_found' | 'in_use');
+ (name(), 'false', rabbit_types:username()) ->
+ 'ok' | rabbit_types:error('not_found').
+
delete(XName, IfUnused, Username) ->
Fun = case IfUnused of
true -> fun conditional_delete/2;
@@ -478,10 +498,18 @@ delete(XName, IfUnused, Username) ->
XName#resource.name, Username)
end.
+-spec validate_binding
+ (rabbit_types:exchange(), rabbit_types:binding())
+ -> rabbit_types:ok_or_error({'binding_invalid', string(), [any()]}).
+
validate_binding(X = #exchange{type = XType}, Binding) ->
Module = type_to_module(XType),
Module:validate_binding(X, Binding).
+-spec maybe_auto_delete
+ (rabbit_types:exchange(), boolean())
+ -> 'not_deleted' | {'deleted', rabbit_binding:deletions()}.
+
maybe_auto_delete(#exchange{auto_delete = false}, _OnlyDurable) ->
not_deleted;
maybe_auto_delete(#exchange{auto_delete = true} = X, OnlyDurable) ->
@@ -516,6 +544,8 @@ next_serial(XName) ->
#exchange_serial{name = XName, next = Serial + 1}, write),
Serial.
+-spec peek_serial(name()) -> pos_integer() | 'undefined'.
+
peek_serial(XName) -> peek_serial(XName, read).
peek_serial(XName, LockType) ->
diff --git a/src/rabbit_exchange_type_headers.erl b/src/rabbit_exchange_type_headers.erl
index 4d6d49ef58..8b94bd343b 100644
--- a/src/rabbit_exchange_type_headers.erl
+++ b/src/rabbit_exchange_type_headers.erl
@@ -33,10 +33,6 @@
{requires, rabbit_registry},
{enables, kernel_ready}]}).
--spec headers_match
- (rabbit_framing:amqp_table(), rabbit_framing:amqp_table()) ->
- boolean().
-
info(_X) -> [].
info(_X, _) -> [].
@@ -84,6 +80,11 @@ parse_x_match(_) -> all. %% legacy; we didn't validate
%% In other words: REQUIRES BOTH PATTERN AND DATA TO BE SORTED ASCENDING BY KEY.
%% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
%%
+
+-spec headers_match
+ (rabbit_framing:amqp_table(), rabbit_framing:amqp_table()) ->
+ boolean().
+
headers_match(Args, Data) ->
MK = parse_x_match(rabbit_misc:table_lookup(Args, <<"x-match">>)),
headers_match(Args, Data, true, false, MK).
diff --git a/src/rabbit_feature_flags.erl b/src/rabbit_feature_flags.erl
new file mode 100644
index 0000000000..e5d59b1a47
--- /dev/null
+++ b/src/rabbit_feature_flags.erl
@@ -0,0 +1,1797 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2018-2019 Pivotal Software, Inc. All rights reserved.
+%%
+
+%% @author The RabbitMQ team
+%% @copyright 2018-2019 Pivotal Software, Inc.
+%%
+%% @doc
+%% This module offers a framework to declare capabilities a RabbitMQ node
+%% supports and therefore a way to determine if multiple RabbitMQ nodes in
+%% a cluster are compatible and can work together.
+%%
+%% == What a feature flag is ==
+%%
+%% A <strong>feature flag</strong> is a name and several properties given
+%% to a change in RabbitMQ which impacts its communication with other
+%% RabbitMQ nodes. This kind of change can be:
+%% <ul>
+%% <li>an update to an Erlang record</li>
+%% <li>a modification to a replicated Mnesia table schema</li>
+%% <li>a modification to Erlang messages exchanged between Erlang processes
+%% which might run on remote nodes</li>
+%% </ul>
+%%
+%% A feature flag is qualified by:
+%% <ul>
+%% <li>a <strong>name</strong></li>
+%% <li>a <strong>description</strong> (optional)</li>
+%% <li>a list of other <strong>feature flags this feature flag depends on
+%% </strong> (optional). This can be useful when the change builds up on
+%% top of a previous change. For instance, it expands a record which was
+%% already modified by a previous feature flag.</li>
+%% <li>a <strong>migration function</strong> (optional). If provided, this
+%% function is called when the feature flag is enabled. It is responsible
+%% for doing all the data conversion, if any, and confirming the feature
+%% flag can be enabled.</li>
+%% <li>a level of stability (stable or experimental). For now, this is only
+%% informational. But it might be used for specific purposes in the
+%% future.</li>
+%% </ul>
+%%
+%% == How to declare a feature flag ==
+%%
+%% To define a new feature flag, you need to use the
+%% `rabbitmq_feature_flag()' module attribute:
+%%
+%% ```
+%% -rabitmq_feature_flag(FeatureFlag).
+%% '''
+%%
+%% `FeatureFlag' is a {@type feature_flag_modattr()}.
+%%
+%% == How to enable a feature flag ==
+%%
+%% To enable a supported feature flag, you have the following solutions:
+%%
+%% <ul>
+%% <li>Using this module API:
+%% ```
+%% rabbit_feature_flags:enable(FeatureFlagName).
+%% '''
+%% </li>
+%% <li>Using the `rabbitmqctl' CLI:
+%% ```
+%% rabbitmqctl enable_feature_flag "$feature_flag_name"
+%% '''
+%% </li>
+%% </ul>
+%%
+%% == How to disable a feature flag ==
+%%
+%% Once enabled, there is <strong>currently no way to disable</strong> a
+%% feature flag.
+
+-module(rabbit_feature_flags).
+
+-export([list/0,
+ list/1,
+ list/2,
+ enable/1,
+ enable_all/0,
+ disable/1,
+ disable_all/0,
+ is_supported/1,
+ is_supported/2,
+ is_supported_locally/1,
+ is_supported_remotely/1,
+ is_supported_remotely/2,
+ is_supported_remotely/3,
+ is_enabled/1,
+ is_enabled/2,
+ is_disabled/1,
+ is_disabled/2,
+ info/0,
+ info/1,
+ init/0,
+ get_state/1,
+ get_stability/1,
+ check_node_compatibility/1,
+ check_node_compatibility/2,
+ is_node_compatible/1,
+ is_node_compatible/2,
+ sync_feature_flags_with_cluster/1,
+ sync_feature_flags_with_cluster/2,
+ enabled_feature_flags_list_file/0
+ ]).
+
+%% RabbitMQ internal use only.
+-export([initialize_registry/0,
+ mark_as_enabled_locally/2,
+ remote_nodes/0,
+ running_remote_nodes/0,
+ does_node_support/3]).
+
+-ifdef(TEST).
+-export([mark_as_enabled_remotely/2,
+ mark_as_enabled_remotely/4]).
+-endif.
+
+%% Default timeout for operations on remote nodes.
+-define(TIMEOUT, 60000).
+
+-define(FF_REGISTRY_LOADING_LOCK, {feature_flags_registry_loading, self()}).
+-define(FF_STATE_CHANGE_LOCK, {feature_flags_state_change, self()}).
+
+-type feature_flag_modattr() :: {feature_name(),
+ feature_props()}.
+%% The value of a `-rabbitmq_feature_flag()' module attribute used to
+%% declare a new feature flag.
+
+-type feature_name() :: atom().
+%% The feature flag's name. It is used in many places to identify a
+%% specific feature flag. In particular, this is how an end-user (or
+%% the CLI) can enable a feature flag. This is also the only bit which
+%% is persisted so a node remember which feature flags are enabled.
+
+-type feature_props() :: #{desc => string(),
+ doc_url => string(),
+ stability => stability(),
+ depends_on => [feature_name()],
+ migration_fun => migration_fun_name()}.
+%% The feature flag properties.
+%%
+%% All properties are optional.
+%%
+%% The properties are:
+%% <ul>
+%% <li>`desc': a description of the feature flag</li>
+%% <li>`doc_url': a URL pointing to more documentation about the feature
+%% flag</li>
+%% <li>`stability': the level of stability</li>
+%% <li>`depends_on': a list of feature flags name which must be enabled
+%% before this one</li>
+%% <li>`migration_fun': a migration function specified by its module and
+%% function names</li>
+%% </ul>
+%%
+%% Note that the `migration_fun' is a {@type migration_fun_name()},
+%% not a {@type migration_fun()}. However, the function signature
+%% must conform to the {@type migration_fun()} signature. The reason
+%% is that we must be able to represent it as an Erlang term when
+%% we regenerate the registry module source code (using {@link
+%% erl_syntax:abstract/1}).
+
+-type feature_flags() :: #{feature_name() => feature_props_extended()}.
+%% The feature flags map as returned or accepted by several functions in
+%% this module. In particular, this what the {@link list/0} function
+%% returns.
+
+-type feature_props_extended() :: #{desc => string(),
+ doc_url => string(),
+ stability => stability(),
+ migration_fun => migration_fun_name(),
+ depends_on => [feature_name()],
+ provided_by => atom()}.
+%% The feature flag properties, once expanded by this module when feature
+%% flags are discovered.
+%%
+%% The new properties compared to {@type feature_props()} are:
+%% <ul>
+%% <li>`provided_by': the name of the application providing the feature flag</li>
+%% </ul>
+
+-type stability() :: stable | experimental.
+%% The level of stability of a feature flag. Currently, only informational.
+
+-type migration_fun_name() :: {Module :: atom(), Function :: atom()}.
+%% The name of the module and function to call when changing the state of
+%% the feature flag.
+
+-type migration_fun() :: fun((feature_name(),
+ feature_props_extended(),
+ migration_fun_context())
+ -> ok | {error, any()} | % context = enable
+ boolean() | undefined). % context = is_enabled
+%% The migration function signature.
+%%
+%% It is called with context `enable' when a feature flag is being enabled.
+%% The function is responsible for this feature-flag-specific verification
+%% and data conversion. It returns `ok' if RabbitMQ can mark the feature
+%% flag as enabled an continue with the next one, if any. Otherwise, it
+%% returns `{error, any()}' if there is an error and the feature flag should
+%% remain disabled. The function must be idempotent: if the feature flag is
+%% already enabled on another node and the local node is running this function
+%% again because it is syncing its feature flags state, it should succeed.
+%%
+%% It is called with the context `is_enabled' to check if a feature flag
+%% is actually enabled. It is useful on RabbitMQ startup, just in case
+%% the previous instance failed to write the feature flags list file.
+
+-type migration_fun_context() :: enable | is_enabled.
+
+-export_type([feature_flag_modattr/0,
+ feature_props/0,
+ feature_name/0,
+ feature_flags/0,
+ feature_props_extended/0,
+ stability/0,
+ migration_fun_name/0,
+ migration_fun/0,
+ migration_fun_context/0]).
+
+-spec list() -> feature_flags().
+%% @doc
+%% Lists all supported feature flags.
+%%
+%% @returns A map of all supported feature flags.
+
+list() -> list(all).
+
+-spec list(Which :: all | enabled | disabled) -> feature_flags().
+%% @doc
+%% Lists all, enabled or disabled feature flags, depending on the argument.
+%%
+%% @param Which The group of feature flags to return: `all', `enabled' or
+%% `disabled'.
+%% @returns A map of selected feature flags.
+
+list(all) -> rabbit_ff_registry:list(all);
+list(enabled) -> rabbit_ff_registry:list(enabled);
+list(disabled) -> maps:filter(
+ fun(FeatureName, _) -> is_disabled(FeatureName) end,
+ list(all)).
+
+-spec list(all | enabled | disabled, stability()) -> feature_flags().
+%% @doc
+%% Lists all, enabled or disabled feature flags, depending on the first
+%% argument, only keeping those having the specified stability.
+%%
+%% @param Which The group of feature flags to return: `all', `enabled' or
+%% `disabled'.
+%% @param Stability The level of stability used to filter the map of feature
+%% flags.
+%% @returns A map of selected feature flags.
+
+list(Which, Stability)
+ when Stability =:= stable orelse Stability =:= experimental ->
+ maps:filter(fun(_, FeatureProps) ->
+ Stability =:= get_stability(FeatureProps)
+ end, list(Which)).
+
+-spec enable(feature_name() | [feature_name()]) -> ok |
+ {error, Reason :: any()}.
+%% @doc
+%% Enables the specified feature flag or set of feature flags.
+%%
+%% @param FeatureName The name or the list of names of feature flags to
+%% enable.
+%% @returns `ok' if the feature flags (and all the feature flags they
+%% depend on) were successfully enabled, or `{error, Reason}' if one
+%% feature flag could not be enabled (subsequent feature flags in the
+%% dependency tree are left unchanged).
+
+enable(FeatureName) when is_atom(FeatureName) ->
+ rabbit_log:debug("Feature flag `~s`: REQUEST TO ENABLE",
+ [FeatureName]),
+ case is_enabled(FeatureName) of
+ true ->
+ rabbit_log:debug("Feature flag `~s`: already enabled",
+ [FeatureName]),
+ ok;
+ false ->
+ rabbit_log:debug("Feature flag `~s`: not enabled, "
+ "check if supported by cluster",
+ [FeatureName]),
+ %% The feature flag must be supported locally and remotely
+ %% (i.e. by all members of the cluster).
+ case is_supported(FeatureName) of
+ true ->
+ rabbit_log:info("Feature flag `~s`: supported, "
+ "attempt to enable...",
+ [FeatureName]),
+ do_enable(FeatureName);
+ false ->
+ rabbit_log:error("Feature flag `~s`: not supported",
+ [FeatureName]),
+ {error, unsupported}
+ end
+ end;
+enable(FeatureNames) when is_list(FeatureNames) ->
+ with_feature_flags(FeatureNames, fun enable/1).
+
+-spec enable_all() -> ok | {error, any()}.
+%% @doc
+%% Enables all supported feature flags.
+%%
+%% @returns `ok' if the feature flags were successfully enabled,
+%% or `{error, Reason}' if one feature flag could not be enabled
+%% (subsequent feature flags in the dependency tree are left
+%% unchanged).
+
+enable_all() ->
+ with_feature_flags(maps:keys(list(all)), fun enable/1).
+
+-spec disable(feature_name() | [feature_name()]) -> ok | {error, any()}.
+%% @doc
+%% Disables the specified feature flag or set of feature flags.
+%%
+%% @param FeatureName The name or the list of names of feature flags to
+%% disable.
+%% @returns `ok' if the feature flags (and all the feature flags they
+%% depend on) were successfully disabled, or `{error, Reason}' if one
+%% feature flag could not be disabled (subsequent feature flags in the
+%% dependency tree are left unchanged).
+
+disable(FeatureName) when is_atom(FeatureName) ->
+ {error, unsupported};
+disable(FeatureNames) when is_list(FeatureNames) ->
+ with_feature_flags(FeatureNames, fun disable/1).
+
+-spec disable_all() -> ok | {error, any()}.
+%% @doc
+%% Disables all supported feature flags.
+%%
+%% @returns `ok' if the feature flags were successfully disabled,
+%% or `{error, Reason}' if one feature flag could not be disabled
+%% (subsequent feature flags in the dependency tree are left
+%% unchanged).
+
+disable_all() ->
+ with_feature_flags(maps:keys(list(all)), fun disable/1).
+
+-spec with_feature_flags([feature_name()],
+ fun((feature_name()) -> ok | {error, any()})) ->
+ ok | {error, any()}.
+%% @private
+
+with_feature_flags([FeatureName | Rest], Fun) ->
+ case Fun(FeatureName) of
+ ok -> with_feature_flags(Rest, Fun);
+ Error -> Error
+ end;
+with_feature_flags([], _) ->
+ ok.
+
+-spec is_supported(feature_name() | [feature_name()]) -> boolean().
+%% @doc
+%% Returns if a single feature flag or a set of feature flags is
+%% supported by the entire cluster.
+%%
+%% This is the same as calling both {@link is_supported_locally/1} and
+%% {@link is_supported_remotely/1} with a logical AND.
+%%
+%% @param FeatureNames The name or a list of names of the feature flag(s)
+%% to be checked.
+%% @returns `true' if the set of feature flags is entirely supported, or
+%% `false' if one of them is not or the RPC timed out.
+
+is_supported(FeatureNames) ->
+ is_supported_locally(FeatureNames) andalso
+ is_supported_remotely(FeatureNames).
+
+-spec is_supported(feature_name() | [feature_name()], timeout()) ->
+ boolean().
+%% @doc
+%% Returns if a single feature flag or a set of feature flags is
+%% supported by the entire cluster.
+%%
+%% This is the same as calling both {@link is_supported_locally/1} and
+%% {@link is_supported_remotely/2} with a logical AND.
+%%
+%% @param FeatureNames The name or a list of names of the feature flag(s)
+%% to be checked.
+%% @param Timeout Time in milliseconds after which the RPC gives up.
+%% @returns `true' if the set of feature flags is entirely supported, or
+%% `false' if one of them is not or the RPC timed out.
+
+is_supported(FeatureNames, Timeout) ->
+ is_supported_locally(FeatureNames) andalso
+ is_supported_remotely(FeatureNames, Timeout).
+
+-spec is_supported_locally(feature_name() | [feature_name()]) -> boolean().
+%% @doc
+%% Returns if a single feature flag or a set of feature flags is
+%% supported by the local node.
+%%
+%% @param FeatureNames The name or a list of names of the feature flag(s)
+%% to be checked.
+%% @returns `true' if the set of feature flags is entirely supported, or
+%% `false' if one of them is not.
+
+is_supported_locally(FeatureName) when is_atom(FeatureName) ->
+ rabbit_ff_registry:is_supported(FeatureName);
+is_supported_locally(FeatureNames) when is_list(FeatureNames) ->
+ lists:all(fun(F) -> rabbit_ff_registry:is_supported(F) end, FeatureNames).
+
+-spec is_supported_remotely(feature_name() | [feature_name()]) -> boolean().
+%% @doc
+%% Returns if a single feature flag or a set of feature flags is
+%% supported by all remote nodes.
+%%
+%% @param FeatureNames The name or a list of names of the feature flag(s)
+%% to be checked.
+%% @returns `true' if the set of feature flags is entirely supported, or
+%% `false' if one of them is not or the RPC timed out.
+
+is_supported_remotely(FeatureNames) ->
+ is_supported_remotely(FeatureNames, ?TIMEOUT).
+
+-spec is_supported_remotely(feature_name() | [feature_name()], timeout()) -> boolean().
+%% @doc
+%% Returns if a single feature flag or a set of feature flags is
+%% supported by all remote nodes.
+%%
+%% @param FeatureNames The name or a list of names of the feature flag(s)
+%% to be checked.
+%% @param Timeout Time in milliseconds after which the RPC gives up.
+%% @returns `true' if the set of feature flags is entirely supported, or
+%% `false' if one of them is not or the RPC timed out.
+
+is_supported_remotely(FeatureName, Timeout) when is_atom(FeatureName) ->
+ is_supported_remotely([FeatureName], Timeout);
+is_supported_remotely([], _) ->
+ rabbit_log:debug("Feature flags: skipping query for feature flags "
+ "support as the given list is empty",
+ []),
+ true;
+is_supported_remotely(FeatureNames, Timeout) when is_list(FeatureNames) ->
+ case running_remote_nodes() of
+ [] ->
+ rabbit_log:debug("Feature flags: isolated node; "
+ "skipping remote node query "
+ "=> consider `~p` supported",
+ [FeatureNames]),
+ true;
+ RemoteNodes ->
+ rabbit_log:debug("Feature flags: about to query these remote nodes "
+ "about support for `~p`: ~p",
+ [FeatureNames, RemoteNodes]),
+ is_supported_remotely(RemoteNodes, FeatureNames, Timeout)
+ end.
+
+-spec is_supported_remotely([node()],
+ feature_name() | [feature_name()],
+ timeout()) -> boolean().
+%% @doc
+%% Returns if a single feature flag or a set of feature flags is
+%% supported by specified remote nodes.
+%%
+%% @param RemoteNodes The list of remote nodes to query.
+%% @param FeatureNames The name or a list of names of the feature flag(s)
+%% to be checked.
+%% @param Timeout Time in milliseconds after which the RPC gives up.
+%% @returns `true' if the set of feature flags is entirely supported by
+%% all nodes, or `false' if one of them is not or the RPC timed out.
+
+is_supported_remotely(_, [], _) ->
+ rabbit_log:debug("Feature flags: skipping query for feature flags "
+ "support as the given list is empty",
+ []),
+ true;
+is_supported_remotely([Node | Rest], FeatureNames, Timeout) ->
+ case does_node_support(Node, FeatureNames, Timeout) of
+ true ->
+ is_supported_remotely(Rest, FeatureNames, Timeout);
+ false ->
+ rabbit_log:debug("Feature flags: stopping query "
+ "for support for `~p` here",
+ [FeatureNames]),
+ false
+ end;
+is_supported_remotely([], FeatureNames, _) ->
+ rabbit_log:info("Feature flags: all running remote nodes support `~p`",
+ [FeatureNames]),
+ true.
+
+-spec is_enabled(feature_name() | [feature_name()]) -> boolean().
+%% @doc
+%% Returns if a single feature flag or a set of feature flags is
+%% enabled.
+%%
+%% This is the same as calling {@link is_enabled/2} as a `blocking'
+%% call.
+%%
+%% @param FeatureNames The name or a list of names of the feature flag(s)
+%% to be checked.
+%% @returns `true' if the set of feature flags is enabled, or
+%% `false' if one of them is not.
+
+is_enabled(FeatureNames) ->
+ is_enabled(FeatureNames, blocking).
+
+-spec is_enabled
+(feature_name() | [feature_name()], blocking) ->
+ boolean();
+(feature_name() | [feature_name()], non_blocking) ->
+ boolean() | state_changing.
+%% @doc
+%% Returns if a single feature flag or a set of feature flags is
+%% enabled.
+%%
+%% When `blocking' is passed, the function waits (blocks) for the
+%% state of a feature flag being disabled or enabled stabilizes before
+%% returning its final state.
+%%
+%% When `non_blocking' is passed, the function returns immediately with
+%% the state of the feature flag (`true' if enabled, `false' otherwise)
+%% or `state_changing' is the state is being changed at the time of the
+%% call.
+%%
+%% @param FeatureNames The name or a list of names of the feature flag(s)
+%% to be checked.
+%% @returns `true' if the set of feature flags is enabled,
+%% `false' if one of them is not, or `state_changing' if one of them
+%% is being worked on. Note that `state_changing' has precedence over
+%% `false', so if one is `false' and another one is `state_changing',
+%% `state_changing' is returned.
+
+is_enabled(FeatureNames, non_blocking) ->
+ is_enabled_nb(FeatureNames);
+is_enabled(FeatureNames, blocking) ->
+ case is_enabled_nb(FeatureNames) of
+ state_changing ->
+ global:set_lock(?FF_STATE_CHANGE_LOCK),
+ global:del_lock(?FF_STATE_CHANGE_LOCK),
+ is_enabled(FeatureNames, blocking);
+ IsEnabled ->
+ IsEnabled
+ end.
+
+is_enabled_nb(FeatureName) when is_atom(FeatureName) ->
+ rabbit_ff_registry:is_enabled(FeatureName);
+is_enabled_nb(FeatureNames) when is_list(FeatureNames) ->
+ lists:foldl(
+ fun
+ (_F, state_changing = Acc) ->
+ Acc;
+ (F, false = Acc) ->
+ case rabbit_ff_registry:is_enabled(F) of
+ state_changing -> state_changing;
+ _ -> Acc
+ end;
+ (F, _) ->
+ rabbit_ff_registry:is_enabled(F)
+ end,
+ true, FeatureNames).
+
+-spec is_disabled(feature_name() | [feature_name()]) -> boolean().
+%% @doc
+%% Returns if a single feature flag or one feature flag in a set of
+%% feature flags is disabled.
+%%
+%% This is the same as negating the result of {@link is_enabled/1}.
+%%
+%% @param FeatureNames The name or a list of names of the feature flag(s)
+%% to be checked.
+%% @returns `true' if one of the feature flags is disabled, or
+%% `false' if they are all enabled.
+
+is_disabled(FeatureNames) ->
+ is_disabled(FeatureNames, blocking).
+
+-spec is_disabled
+(feature_name() | [feature_name()], blocking) ->
+ boolean();
+(feature_name() | [feature_name()], non_blocking) ->
+ boolean() | state_changing.
+%% @doc
+%% Returns if a single feature flag or one feature flag in a set of
+%% feature flags is disabled.
+%%
+%% This is the same as negating the result of {@link is_enabled/2},
+%% except that `state_changing' is returned as is.
+%%
+%% See {@link is_enabled/2} for a description of the `blocking' and
+%% `non_blocking' modes.
+%%
+%% @param FeatureNames The name or a list of names of the feature flag(s)
+%% to be checked.
+%% @returns `true' if one feature flag in the set of feature flags is
+%% disabled, `false' if they are all enabled, or `state_changing' if
+%% one of them is being worked on. Note that `state_changing' has
+%% precedence over `true', so if one is `true' (i.e. disabled) and
+%% another one is `state_changing', `state_changing' is returned.
+%%
+%% @see is_enabled/2
+
+is_disabled(FeatureName, Blocking) ->
+ case is_enabled(FeatureName, Blocking) of
+ state_changing -> state_changing;
+ IsEnabled -> not IsEnabled
+ end.
+
+-spec info() -> ok.
+%% @doc
+%% Displays a table on stdout summing up the supported feature flags,
+%% their state and various informations about them.
+
+info() ->
+ info(#{}).
+
+-spec info(#{color => boolean(),
+ lines => boolean(),
+ verbose => non_neg_integer()}) -> ok.
+%% @doc
+%% Displays a table on stdout summing up the supported feature flags,
+%% their state and various informations about them.
+%%
+%% Supported options are:
+%% <ul>
+%% <li>`color': a boolean to indicate if colors should be used to
+%% highlight some elements.</li>
+%% <li>`lines': a boolean to indicate if table borders should be drawn
+%% using ASCII lines instead of regular characters.</li>
+%% <li>`verbose': a non-negative integer to specify the level of
+%% verbosity.</li>
+%% </ul>
+%%
+%% @param Options A map of various options to tune the displayed table.
+
+info(Options) when is_map(Options) ->
+ rabbit_ff_extra:info(Options).
+
+-spec get_state(feature_name()) -> enabled | disabled | unavailable.
+%% @doc
+%% Returns the state of a feature flag.
+%%
+%% The possible states are:
+%% <ul>
+%% <li>`enabled': the feature flag is enabled.</li>
+%% <li>`disabled': the feature flag is supported by all nodes in the
+%% cluster but currently disabled.</li>
+%% <li>`unavailable': the feature flag is unsupported by at least one
+%% node in the cluster and can not be enabled for now.</li>
+%% </ul>
+%%
+%% @param FeatureName The name of the feature flag to check.
+%% @returns `enabled', `disabled' or `unavailable'.
+
+get_state(FeatureName) when is_atom(FeatureName) ->
+ IsEnabled = rabbit_feature_flags:is_enabled(FeatureName),
+ IsSupported = rabbit_feature_flags:is_supported(FeatureName),
+ case IsEnabled of
+ true -> enabled;
+ false -> case IsSupported of
+ true -> disabled;
+ false -> unavailable
+ end
+ end.
+
+-spec get_stability(feature_name() | feature_props_extended()) -> stability().
+%% @doc
+%% Returns the stability of a feature flag.
+%%
+%% The possible stability levels are:
+%% <ul>
+%% <li>`stable': the feature flag is stable and will not change in future
+%% releases: it can be enabled in production.</li>
+%% <li>`experimental': the feature flag is experimental and may change in
+%% the future (without a guaranteed upgrade path): enabling it in
+%% production is not recommended.</li>
+%% <li>`unavailable': the feature flag is unsupported by at least one
+%% node in the cluster and can not be enabled for now.</li>
+%% </ul>
+%%
+%% @param FeatureName The name of the feature flag to check.
+%% @returns `stable' or `experimental'.
+
+get_stability(FeatureName) when is_atom(FeatureName) ->
+ case rabbit_ff_registry:get(FeatureName) of
+ undefined -> undefined;
+ FeatureProps -> get_stability(FeatureProps)
+ end;
+get_stability(FeatureProps) when is_map(FeatureProps) ->
+ maps:get(stability, FeatureProps, stable).
+
+%% -------------------------------------------------------------------
+%% Feature flags registry.
+%% -------------------------------------------------------------------
+
+-spec init() -> ok | no_return().
+%% @private
+
+init() ->
+ %% We want to make sure the `feature_flags` file exists once
+ %% RabbitMQ was started at least once. This is not required by
+ %% this module (it works fine if the file is missing) but it helps
+ %% external tools.
+ _ = ensure_enabled_feature_flags_list_file_exists(),
+
+ %% We also "list" supported feature flags. We are not interested in
+ %% that list, however, it triggers the first initialization of the
+ %% registry.
+ _ = list(all),
+ ok.
+
+-spec initialize_registry() -> ok | {error, any()} | no_return().
+%% @private
+%% @doc
+%% Initializes or reinitializes the registry.
+%%
+%% The registry is an Erlang module recompiled at runtime to hold the
+%% state of all supported feature flags.
+%%
+%% That Erlang module is called {@link rabbit_ff_registry}. The initial
+%% source code of this module simply calls this function so it is
+%% replaced by a proper registry.
+%%
+%% Once replaced, the registry contains the map of all supported feature
+%% flags and their state. This is makes it very efficient to query a
+%% feature flag state or property.
+%%
+%% The registry is local to all RabbitMQ nodes.
+
+initialize_registry() ->
+ %% The first step is to get the list of enabled feature flags: if
+ %% this is the first time we initialize it, we read the list from
+ %% disk (the `feature_flags` file). Otherwise we query the existing
+ %% registry before it is replaced.
+ RegistryInitialized = rabbit_ff_registry:is_registry_initialized(),
+ EnabledFeatureNames = case RegistryInitialized of
+ true ->
+ maps:keys(rabbit_ff_registry:list(enabled));
+ false ->
+ read_enabled_feature_flags_list()
+ end,
+
+ %% We also record if the feature flags state was correctly written
+ %% to disk. Currently we don't use this information, but in the
+ %% future, we might want to retry the write if it failed so far.
+ %%
+ %% TODO: Retry to write the feature flags state if the first try
+ %% failed.
+ WrittenToDisk = case RegistryInitialized of
+ true ->
+ rabbit_ff_registry:is_registry_written_to_disk();
+ false ->
+ true
+ end,
+ initialize_registry(EnabledFeatureNames, [], WrittenToDisk).
+
+-spec initialize_registry([feature_name()], [feature_name()], boolean()) ->
+ ok | {error, any()} | no_return().
+%% @private
+%% @doc
+%% Initializes or reinitializes the registry.
+%%
+%% See {@link initialize_registry/0} for a description of the registry.
+%%
+%% This function takes two list of feature flag names:
+%% <ul>
+%% <li>the complete list of feature flags to mark as enabled</li>
+%% <li>the list of feature flags being enabled or disabled</li>
+%% </ul>
+%%
+%% The latter is used to block callers asking if a feature flag is
+%% enabled or disabled while its state is changing.
+
+initialize_registry(EnabledFeatureNames,
+ ChangingFeatureNames,
+ WrittenToDisk) ->
+ %% Query the list (it's a map to be exact) of supported feature
+ %% flags. That list comes from the `-rabbitmq_feature_flag().`
+ %% module attributes exposed by all currently loaded Erlang modules.
+ rabbit_log:debug("Feature flags: (re)initialize registry", []),
+ AllFeatureFlags = query_supported_feature_flags(),
+
+ %% We log the state of those feature flags.
+ rabbit_log:info("Feature flags: list of feature flags found:", []),
+ lists:foreach(
+ fun(FeatureName) ->
+ rabbit_log:info(
+ "Feature flags: [~s] ~s",
+ [case lists:member(FeatureName, EnabledFeatureNames) of
+ true -> "x";
+ false -> " "
+ end,
+ FeatureName])
+ end, lists:sort(maps:keys(AllFeatureFlags))),
+
+ %% We request the registry to be regenerated and reloaded with the
+ %% new state.
+ regen_registry_mod(AllFeatureFlags,
+ EnabledFeatureNames,
+ ChangingFeatureNames,
+ WrittenToDisk).
+
+-spec query_supported_feature_flags() -> feature_flags().
+%% @private
+
+query_supported_feature_flags() ->
+ rabbit_log:debug(
+ "Feature flags: query feature flags in loaded applications"),
+ AttributesPerApp = rabbit_misc:all_module_attributes(rabbit_feature_flag),
+ query_supported_feature_flags(AttributesPerApp, #{}).
+
+query_supported_feature_flags([{App, _Module, Attributes} | Rest],
+ AllFeatureFlags) ->
+ rabbit_log:debug("Feature flags: application `~s` "
+ "has ~b feature flags",
+ [App, length(Attributes)]),
+ AllFeatureFlags1 = lists:foldl(
+ fun({FeatureName, FeatureProps}, AllFF) ->
+ merge_new_feature_flags(AllFF,
+ App,
+ FeatureName,
+ FeatureProps)
+ end, AllFeatureFlags, Attributes),
+ query_supported_feature_flags(Rest, AllFeatureFlags1);
+query_supported_feature_flags([], AllFeatureFlags) ->
+ AllFeatureFlags.
+
+-spec merge_new_feature_flags(feature_flags(),
+ atom(),
+ feature_name(),
+ feature_props()) -> feature_flags().
+%% @private
+
+merge_new_feature_flags(AllFeatureFlags, App, FeatureName, FeatureProps)
+ when is_atom(FeatureName) andalso is_map(FeatureProps) ->
+ %% We expand the feature flag properties map with:
+ %% - the name of the application providing it: only informational
+ %% for now, but can be handy to understand that a feature flag
+ %% comes from a plugin.
+ FeatureProps1 = maps:put(provided_by, App, FeatureProps),
+ maps:merge(AllFeatureFlags,
+ #{FeatureName => FeatureProps1}).
+
+-spec regen_registry_mod(feature_flags(),
+ [feature_name()],
+ [feature_name()],
+ boolean()) -> ok | {error, any()} | no_return().
+%% @private
+
+regen_registry_mod(AllFeatureFlags,
+ EnabledFeatureNames,
+ ChangingFeatureNames,
+ WrittenToDisk) ->
+ %% Here, we recreate the source code of the `rabbit_ff_registry`
+ %% module from scratch.
+ %%
+ %% IMPORTANT: We want both modules to have the exact same public
+ %% API in order to simplify the life of developers and their tools
+ %% (Dialyzer, completion, and so on).
+
+ %% -module(rabbit_ff_registry).
+ ModuleAttr = erl_syntax:attribute(
+ erl_syntax:atom(module),
+ [erl_syntax:atom(rabbit_ff_registry)]),
+ ModuleForm = erl_syntax:revert(ModuleAttr),
+ %% -export([...]).
+ ExportAttr = erl_syntax:attribute(
+ erl_syntax:atom(export),
+ [erl_syntax:list(
+ [erl_syntax:arity_qualifier(
+ erl_syntax:atom(F),
+ erl_syntax:integer(A))
+ || {F, A} <- [{get, 1},
+ {list, 1},
+ {is_supported, 1},
+ {is_enabled, 1},
+ {is_registry_initialized, 0},
+ {is_registry_written_to_disk, 0}]]
+ )
+ ]
+ ),
+ ExportForm = erl_syntax:revert(ExportAttr),
+ %% get(_) -> ...
+ GetClauses = [erl_syntax:clause(
+ [erl_syntax:atom(FeatureName)],
+ [],
+ [erl_syntax:abstract(maps:get(FeatureName,
+ AllFeatureFlags))])
+ || FeatureName <- maps:keys(AllFeatureFlags)
+ ],
+ GetUnknownClause = erl_syntax:clause(
+ [erl_syntax:variable("_")],
+ [],
+ [erl_syntax:atom(undefined)]),
+ GetFun = erl_syntax:function(
+ erl_syntax:atom(get),
+ GetClauses ++ [GetUnknownClause]),
+ GetFunForm = erl_syntax:revert(GetFun),
+ %% list(_) -> ...
+ ListAllBody = erl_syntax:abstract(AllFeatureFlags),
+ ListAllClause = erl_syntax:clause([erl_syntax:atom(all)],
+ [],
+ [ListAllBody]),
+ EnabledFeatureFlags = maps:filter(
+ fun(FeatureName, _) ->
+ lists:member(FeatureName,
+ EnabledFeatureNames)
+ end, AllFeatureFlags),
+ ListEnabledBody = erl_syntax:abstract(EnabledFeatureFlags),
+ ListEnabledClause = erl_syntax:clause([erl_syntax:atom(enabled)],
+ [],
+ [ListEnabledBody]),
+ ListFun = erl_syntax:function(
+ erl_syntax:atom(list),
+ [ListAllClause, ListEnabledClause]),
+ ListFunForm = erl_syntax:revert(ListFun),
+ %% is_supported(_) -> ...
+ IsSupportedClauses = [erl_syntax:clause(
+ [erl_syntax:atom(FeatureName)],
+ [],
+ [erl_syntax:atom(true)])
+ || FeatureName <- maps:keys(AllFeatureFlags)
+ ],
+ NotSupportedClause = erl_syntax:clause(
+ [erl_syntax:variable("_")],
+ [],
+ [erl_syntax:atom(false)]),
+ IsSupportedFun = erl_syntax:function(
+ erl_syntax:atom(is_supported),
+ IsSupportedClauses ++ [NotSupportedClause]),
+ IsSupportedFunForm = erl_syntax:revert(IsSupportedFun),
+ %% is_enabled(_) -> ...
+ IsEnabledClauses = [erl_syntax:clause(
+ [erl_syntax:atom(FeatureName)],
+ [],
+ [case lists:member(FeatureName,
+ ChangingFeatureNames) of
+ true ->
+ erl_syntax:atom(state_changing);
+ false ->
+ erl_syntax:atom(
+ lists:member(FeatureName,
+ EnabledFeatureNames))
+ end])
+ || FeatureName <- maps:keys(AllFeatureFlags)
+ ],
+ NotEnabledClause = erl_syntax:clause(
+ [erl_syntax:variable("_")],
+ [],
+ [erl_syntax:atom(false)]),
+ IsEnabledFun = erl_syntax:function(
+ erl_syntax:atom(is_enabled),
+ IsEnabledClauses ++ [NotEnabledClause]),
+ IsEnabledFunForm = erl_syntax:revert(IsEnabledFun),
+ %% is_registry_initialized() -> ...
+ IsInitializedClauses = [erl_syntax:clause(
+ [],
+ [],
+ [erl_syntax:atom(true)])
+ ],
+ IsInitializedFun = erl_syntax:function(
+ erl_syntax:atom(is_registry_initialized),
+ IsInitializedClauses),
+ IsInitializedFunForm = erl_syntax:revert(IsInitializedFun),
+ %% is_registry_written_to_disk() -> ...
+ IsWrittenToDiskClauses = [erl_syntax:clause(
+ [],
+ [],
+ [erl_syntax:atom(WrittenToDisk)])
+ ],
+ IsWrittenToDiskFun = erl_syntax:function(
+ erl_syntax:atom(is_registry_written_to_disk),
+ IsWrittenToDiskClauses),
+ IsWrittenToDiskFunForm = erl_syntax:revert(IsWrittenToDiskFun),
+ %% Compilation!
+ Forms = [ModuleForm,
+ ExportForm,
+ GetFunForm,
+ ListFunForm,
+ IsSupportedFunForm,
+ IsEnabledFunForm,
+ IsInitializedFunForm,
+ IsWrittenToDiskFunForm],
+ CompileOpts = [return_errors,
+ return_warnings],
+ case compile:forms(Forms, CompileOpts) of
+ {ok, Mod, Bin, _} ->
+ load_registry_mod(Mod, Bin);
+ {error, Errors, Warnings} ->
+ rabbit_log:error("Feature flags: registry compilation:~n"
+ "Errors: ~p~n"
+ "Warnings: ~p",
+ [Errors, Warnings]),
+ {error, {compilation_failure, Errors, Warnings}}
+ end.
+
+-spec load_registry_mod(atom(), binary()) ->
+ ok | {error, any()} | no_return().
+%% @private
+
+load_registry_mod(Mod, Bin) ->
+ rabbit_log:debug("Feature flags: registry module ready, loading it..."),
+ FakeFilename = "Compiled and loaded by " ++ ?MODULE_STRING,
+ %% Time to load the new registry, replacing the old one. We use a
+ %% lock here to synchronize concurrent reloads.
+ global:set_lock(?FF_REGISTRY_LOADING_LOCK, [node()]),
+ _ = code:soft_purge(Mod),
+ _ = code:delete(Mod),
+ Ret = code:load_binary(Mod, FakeFilename, Bin),
+ global:del_lock(?FF_REGISTRY_LOADING_LOCK, [node()]),
+ case Ret of
+ {module, _} ->
+ rabbit_log:debug("Feature flags: registry module loaded"),
+ ok;
+ {error, Reason} ->
+ rabbit_log:error("Feature flags: failed to load registry "
+ "module: ~p", [Reason]),
+ throw({feature_flag_registry_reload_failure, Reason})
+ end.
+
+%% -------------------------------------------------------------------
+%% Feature flags state storage.
+%% -------------------------------------------------------------------
+
+-spec ensure_enabled_feature_flags_list_file_exists() -> ok | {error, any()}.
+%% @private
+
+ensure_enabled_feature_flags_list_file_exists() ->
+ File = enabled_feature_flags_list_file(),
+ case filelib:is_regular(File) of
+ true -> ok;
+ false -> write_enabled_feature_flags_list([])
+ end.
+
+-spec read_enabled_feature_flags_list() ->
+ [feature_name()] | no_return().
+%% @private
+
+read_enabled_feature_flags_list() ->
+ case try_to_read_enabled_feature_flags_list() of
+ {error, Reason} ->
+ File = enabled_feature_flags_list_file(),
+ throw({feature_flags_file_read_error, File, Reason});
+ Ret ->
+ Ret
+ end.
+
+-spec try_to_read_enabled_feature_flags_list() ->
+ [feature_name()] | {error, any()}.
+%% @private
+
+try_to_read_enabled_feature_flags_list() ->
+ File = enabled_feature_flags_list_file(),
+ case file:consult(File) of
+ {ok, [List]} ->
+ List;
+ {error, enoent} ->
+ %% If the file is missing, we consider the list of enabled
+ %% feature flags to be empty.
+ [];
+ {error, Reason} = Error ->
+ rabbit_log:error(
+ "Feature flags: failed to read the `feature_flags` "
+ "file at `~s`: ~s",
+ [File, file:format_error(Reason)]),
+ Error
+ end.
+
+-spec write_enabled_feature_flags_list([feature_name()]) ->
+ ok | no_return().
+%% @private
+
+write_enabled_feature_flags_list(FeatureNames) ->
+ case try_to_write_enabled_feature_flags_list(FeatureNames) of
+ {error, Reason} ->
+ File = enabled_feature_flags_list_file(),
+ throw({feature_flags_file_write_error, File, Reason});
+ Ret ->
+ Ret
+ end.
+
+-spec try_to_write_enabled_feature_flags_list([feature_name()]) ->
+ ok | {error, any()}.
+%% @private
+
+try_to_write_enabled_feature_flags_list(FeatureNames) ->
+ %% Before writing the new file, we read the existing one. If there
+ %% are unknown feature flags in that file, we want to keep their
+ %% state, even though they are unsupported at this time. It could be
+ %% that a plugin was disabled in the meantime.
+ PreviouslyEnabled = case try_to_read_enabled_feature_flags_list() of
+ {error, _} -> [];
+ List -> List
+ end,
+ FeatureNames1 = lists:foldl(
+ fun(Name, Acc) ->
+ case is_supported_locally(Name) of
+ true -> Acc;
+ false -> [Name | Acc]
+ end
+ end, FeatureNames, PreviouslyEnabled),
+ FeatureNames2 = lists:sort(FeatureNames1),
+
+ File = enabled_feature_flags_list_file(),
+ Content = io_lib:format("~p.~n", [FeatureNames2]),
+ %% TODO: If we fail to write the the file, we should spawn a process
+ %% to retry the operation.
+ case file:write_file(File, Content) of
+ ok ->
+ ok;
+ {error, Reason} = Error ->
+ rabbit_log:error(
+ "Feature flags: failed to write the `feature_flags` "
+ "file at `~s`: ~s",
+ [File, file:format_error(Reason)]),
+ Error
+ end.
+
+-spec enabled_feature_flags_list_file() -> file:filename().
+%% @doc
+%% Returns the path to the file where the state of feature flags is stored.
+%%
+%% @returns the path to the file.
+
+enabled_feature_flags_list_file() ->
+ case application:get_env(rabbit, feature_flags_file) of
+ {ok, Val} -> Val;
+ _ -> filename:join([rabbit_mnesia:dir(), "feature_flags"])
+ end.
+
+%% -------------------------------------------------------------------
+%% Feature flags management: enabling.
+%% -------------------------------------------------------------------
+
+-spec do_enable(feature_name()) -> ok | {error, any()} | no_return().
+%% @private
+
+do_enable(FeatureName) ->
+ %% We mark this feature flag as "state changing" before doing the
+ %% actual state change. We also take a global lock: this permits
+ %% to block callers asking about a feature flag changing state.
+ global:set_lock(?FF_STATE_CHANGE_LOCK),
+ Ret = case mark_as_enabled(FeatureName, state_changing) of
+ ok ->
+ case enable_dependencies(FeatureName, true) of
+ ok ->
+ case run_migration_fun(FeatureName, enable) of
+ ok ->
+ mark_as_enabled(FeatureName, true);
+ {error, no_migration_fun} ->
+ mark_as_enabled(FeatureName, true);
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end,
+ case Ret of
+ ok -> ok;
+ _ -> mark_as_enabled(FeatureName, false)
+ end,
+ global:del_lock(?FF_STATE_CHANGE_LOCK),
+ Ret.
+
+-spec enable_locally(feature_name()) -> ok | {error, any()} | no_return().
+%% @private
+
+enable_locally(FeatureName) when is_atom(FeatureName) ->
+ case is_enabled(FeatureName) of
+ true ->
+ ok;
+ false ->
+ rabbit_log:debug(
+ "Feature flag `~s`: enable locally (i.e. was enabled on the cluster "
+ "when this node was not part of it)",
+ [FeatureName]),
+ do_enable_locally(FeatureName)
+ end.
+
+-spec do_enable_locally(feature_name()) -> ok | {error, any()} | no_return().
+%% @private
+
+do_enable_locally(FeatureName) ->
+ case enable_dependencies(FeatureName, false) of
+ ok ->
+ case run_migration_fun(FeatureName, enable) of
+ ok ->
+ mark_as_enabled_locally(FeatureName, true);
+ {error, no_migration_fun} ->
+ mark_as_enabled_locally(FeatureName, true);
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+-spec enable_dependencies(feature_name(), boolean()) ->
+ ok | {error, any()} | no_return().
+%% @private
+
+enable_dependencies(FeatureName, Everywhere) ->
+ FeatureProps = rabbit_ff_registry:get(FeatureName),
+ DependsOn = maps:get(depends_on, FeatureProps, []),
+ rabbit_log:debug("Feature flag `~s`: enable dependencies: ~p",
+ [FeatureName, DependsOn]),
+ enable_dependencies(FeatureName, DependsOn, Everywhere).
+
+-spec enable_dependencies(feature_name(), [feature_name()], boolean()) ->
+ ok | {error, any()} | no_return().
+%% @private
+
+enable_dependencies(TopLevelFeatureName, [FeatureName | Rest], Everywhere) ->
+ Ret = case Everywhere of
+ true -> enable(FeatureName);
+ false -> enable_locally(FeatureName)
+ end,
+ case Ret of
+ ok -> enable_dependencies(TopLevelFeatureName, Rest, Everywhere);
+ Error -> Error
+ end;
+enable_dependencies(_, [], _) ->
+ ok.
+
+-spec run_migration_fun(feature_name(), any()) ->
+ any() | {error, any()}.
+%% @private
+
+run_migration_fun(FeatureName, Arg) ->
+ FeatureProps = rabbit_ff_registry:get(FeatureName),
+ run_migration_fun(FeatureName, FeatureProps, Arg).
+
+run_migration_fun(FeatureName, FeatureProps, Arg) ->
+ case maps:get(migration_fun, FeatureProps, none) of
+ {MigrationMod, MigrationFun}
+ when is_atom(MigrationMod) andalso is_atom(MigrationFun) ->
+ rabbit_log:debug("Feature flag `~s`: run migration function ~p "
+ "with arg: ~p",
+ [FeatureName, MigrationFun, Arg]),
+ try
+ erlang:apply(MigrationMod,
+ MigrationFun,
+ [FeatureName, FeatureProps, Arg])
+ catch
+ _:Reason:Stacktrace ->
+ rabbit_log:error("Feature flag `~s`: migration function "
+ "crashed: ~p~n~p",
+ [FeatureName, Reason, Stacktrace]),
+ {error, {migration_fun_crash, Reason, Stacktrace}}
+ end;
+ none ->
+ {error, no_migration_fun};
+ Invalid ->
+ rabbit_log:error("Feature flag `~s`: invalid migration "
+ "function: ~p",
+ [FeatureName, Invalid]),
+ {error, {invalid_migration_fun, Invalid}}
+ end.
+
+-spec mark_as_enabled(feature_name(), boolean() | state_changing) ->
+ any() | {error, any()} | no_return().
+%% @private
+
+mark_as_enabled(FeatureName, IsEnabled) ->
+ case mark_as_enabled_locally(FeatureName, IsEnabled) of
+ ok ->
+ mark_as_enabled_remotely(FeatureName, IsEnabled);
+ Error ->
+ Error
+ end.
+
+-spec mark_as_enabled_locally(feature_name(), boolean() | state_changing) ->
+ any() | {error, any()} | no_return().
+%% @private
+
+mark_as_enabled_locally(FeatureName, IsEnabled) ->
+ rabbit_log:info("Feature flag `~s`: mark as enabled=~p",
+ [FeatureName, IsEnabled]),
+ EnabledFeatureNames = maps:keys(list(enabled)),
+ NewEnabledFeatureNames = case IsEnabled of
+ true ->
+ [FeatureName | EnabledFeatureNames];
+ false ->
+ EnabledFeatureNames -- [FeatureName];
+ state_changing ->
+ EnabledFeatureNames
+ end,
+ WrittenToDisk = case NewEnabledFeatureNames of
+ EnabledFeatureNames ->
+ rabbit_ff_registry:is_registry_written_to_disk();
+ _ ->
+ ok =:= try_to_write_enabled_feature_flags_list(
+ NewEnabledFeatureNames)
+ end,
+ case IsEnabled of
+ state_changing ->
+ initialize_registry(EnabledFeatureNames,
+ [FeatureName],
+ WrittenToDisk);
+ _ ->
+ initialize_registry(NewEnabledFeatureNames,
+ [],
+ WrittenToDisk)
+ end.
+
+-spec mark_as_enabled_remotely(feature_name(), boolean() | state_changing) ->
+ any() | {error, any()} | no_return().
+%% @private
+
+mark_as_enabled_remotely(FeatureName, IsEnabled) ->
+ Nodes = running_remote_nodes(),
+ mark_as_enabled_remotely(Nodes, FeatureName, IsEnabled, ?TIMEOUT).
+
+-spec mark_as_enabled_remotely([node()], feature_name(), boolean() | state_changing, timeout()) ->
+ any() | {error, any()} | no_return().
+%% @private
+
+mark_as_enabled_remotely([], _FeatureName, _IsEnabled, _Timeout) ->
+ ok;
+mark_as_enabled_remotely(Nodes, FeatureName, IsEnabled, Timeout) ->
+ T0 = erlang:timestamp(),
+ Rets = [{Node, rpc:call(Node,
+ ?MODULE,
+ mark_as_enabled_locally,
+ [FeatureName, IsEnabled],
+ Timeout)}
+ || Node <- Nodes],
+ FailedNodes = [Node || {Node, Ret} <- Rets, Ret =/= ok],
+ case FailedNodes of
+ [] ->
+ rabbit_log:debug(
+ "Feature flags: `~s` successfully marked as enabled=~p on all "
+ "nodes", [FeatureName, IsEnabled]),
+ ok;
+ _ ->
+ T1 = erlang:timestamp(),
+ rabbit_log:error(
+ "Feature flags: failed to mark feature flag `~s` as enabled=~p "
+ "on the following nodes:", [FeatureName, IsEnabled]),
+ [rabbit_log:error(
+ "Feature flags: - ~s: ~p",
+ [Node, Ret])
+ || {Node, Ret} <- Rets,
+ Ret =/= ok],
+ NewTimeout = Timeout - (timer:now_diff(T1, T0) div 1000),
+ if
+ NewTimeout > 0 ->
+ rabbit_log:debug(
+ "Feature flags: retrying with a timeout of ~b "
+ "milliseconds", [NewTimeout]),
+ mark_as_enabled_remotely(FailedNodes,
+ FeatureName,
+ IsEnabled,
+ NewTimeout);
+ true ->
+ rabbit_log:debug(
+ "Feature flags: not retrying; RPC went over the "
+ "~b milliseconds timeout", [Timeout]),
+ %% FIXME: Is crashing the process the best solution here?
+ throw(
+ {failed_to_mark_feature_flag_as_enabled_on_remote_nodes,
+ FeatureName, IsEnabled, FailedNodes})
+ end
+ end.
+
+%% -------------------------------------------------------------------
+%% Coordination with remote nodes.
+%% -------------------------------------------------------------------
+
+-spec remote_nodes() -> [node()].
+%% @private
+
+remote_nodes() ->
+ mnesia:system_info(db_nodes) -- [node()].
+
+-spec running_remote_nodes() -> [node()].
+%% @private
+
+running_remote_nodes() ->
+ mnesia:system_info(running_db_nodes) -- [node()].
+
+-spec does_node_support(node(), [feature_name()], timeout()) -> boolean().
+%% @private
+
+does_node_support(Node, FeatureNames, Timeout) ->
+ rabbit_log:debug("Feature flags: querying `~p` support on node ~s...",
+ [FeatureNames, Node]),
+ Ret = case node() of
+ Node ->
+ is_supported_locally(FeatureNames);
+ _ ->
+ rpc:call(Node,
+ ?MODULE, is_supported_locally, [FeatureNames],
+ Timeout)
+ end,
+ case Ret of
+ {badrpc, {'EXIT',
+ {undef,
+ [{?MODULE, is_supported_locally, [FeatureNames], []}
+ | _]}}} ->
+ %% If rabbit_feature_flags:is_supported_locally/1 is undefined
+ %% on the remote node, we consider it to be a 3.7.x node.
+ %%
+ %% Theoritically, it could be an older version (3.6.x and
+ %% older). But the RabbitMQ version consistency check
+ %% (rabbit_misc:version_minor_equivalent/2) called from
+ %% rabbit_mnesia:check_rabbit_consistency/2 already blocked
+ %% this situation from happening before we reach this point.
+ rabbit_log:debug(
+ "Feature flags: ?MODULE:is_supported_locally(~p) unavailable "
+ "on node `~s`: assuming it is a RabbitMQ 3.7.x node "
+ "=> consider the feature flags unsupported",
+ [FeatureNames, Node]),
+ false;
+ {badrpc, Reason} ->
+ rabbit_log:error("Feature flags: error while querying `~p` "
+ "support on node ~s: ~p",
+ [FeatureNames, Node, Reason]),
+ false;
+ true ->
+ rabbit_log:debug("Feature flags: node `~s` supports `~p`",
+ [Node, FeatureNames]),
+ true;
+ false ->
+ rabbit_log:debug("Feature flags: node `~s` does not support `~p`; "
+ "stopping query here",
+ [Node, FeatureNames]),
+ false
+ end.
+
+-spec check_node_compatibility(node()) -> ok | {error, any()}.
+%% @doc
+%% Checks if a node is compatible with the local node.
+%%
+%% To be compatible, the following two conditions must be met:
+%% <ol>
+%% <li>feature flags enabled on the local node must be supported by the
+%% remote node</li>
+%% <li>feature flags enabled on the remote node must be supported by the
+%% local node</li>
+%% </ol>
+%%
+%% @param Node the name of the remote node to test.
+%% @returns `ok' if they are compatible, `{error, Reason}' if they are not.
+
+check_node_compatibility(Node) ->
+ check_node_compatibility(Node, ?TIMEOUT).
+
+-spec check_node_compatibility(node(), timeout()) -> ok | {error, any()}.
+%% @doc
+%% Checks if a node is compatible with the local node.
+%%
+%% See {@link check_node_compatibility/1} for the conditions required to
+%% consider two nodes compatible.
+%%
+%% @param Node the name of the remote node to test.
+%% @param Timeout Time in milliseconds after which the RPC gives up.
+%% @returns `ok' if they are compatible, `{error, Reason}' if they are not.
+%%
+%% @see check_node_compatibility/1
+
+check_node_compatibility(Node, Timeout) ->
+ rabbit_log:debug("Feature flags: node `~s` compatibility check, part 1/2",
+ [Node]),
+ Part1 = local_enabled_feature_flags_is_supported_remotely(Node, Timeout),
+ rabbit_log:debug("Feature flags: node `~s` compatibility check, part 2/2",
+ [Node]),
+ Part2 = remote_enabled_feature_flags_is_supported_locally(Node, Timeout),
+ case {Part1, Part2} of
+ {true, true} ->
+ rabbit_log:debug("Feature flags: node `~s` is compatible", [Node]),
+ ok;
+ {false, _} ->
+ rabbit_log:error("Feature flags: node `~s` is INCOMPATIBLE: "
+ "feature flags enabled locally are not "
+ "supported remotely",
+ [Node]),
+ {error, incompatible_feature_flags};
+ {_, false} ->
+ rabbit_log:error("Feature flags: node `~s` is INCOMPATIBLE: "
+ "feature flags enabled remotely are not "
+ "supported locally",
+ [Node]),
+ {error, incompatible_feature_flags}
+ end.
+
+-spec is_node_compatible(node()) -> boolean().
+%% @doc
+%% Returns if a node is compatible with the local node.
+%%
+%% This function calls {@link check_node_compatibility/2} and returns
+%% `true' the latter returns `ok'. Therefore this is the same code,
+%% except that this function returns a boolean, but not the reason of
+%% the incompatibility if any.
+%%
+%% @param Node the name of the remote node to test.
+%% @returns `true' if they are compatible, `false' otherwise.
+
+is_node_compatible(Node) ->
+ is_node_compatible(Node, ?TIMEOUT).
+
+-spec is_node_compatible(node(), timeout()) -> boolean().
+%% @doc
+%% Returns if a node is compatible with the local node.
+%%
+%% This function calls {@link check_node_compatibility/2} and returns
+%% `true' the latter returns `ok'. Therefore this is the same code,
+%% except that this function returns a boolean, but not the reason
+%% of the incompatibility if any. If the RPC times out, nodes are
+%% considered incompatible.
+%%
+%% @param Node the name of the remote node to test.
+%% @param Timeout Time in milliseconds after which the RPC gives up.
+%% @returns `true' if they are compatible, `false' otherwise.
+
+is_node_compatible(Node, Timeout) ->
+ check_node_compatibility(Node, Timeout) =:= ok.
+
+-spec local_enabled_feature_flags_is_supported_remotely(node(),
+ timeout()) ->
+ boolean().
+%% @private
+
+local_enabled_feature_flags_is_supported_remotely(Node, Timeout) ->
+ LocalEnabledFeatureNames = maps:keys(list(enabled)),
+ is_supported_remotely([Node], LocalEnabledFeatureNames, Timeout).
+
+-spec remote_enabled_feature_flags_is_supported_locally(node(),
+ timeout()) ->
+ boolean().
+%% @private
+
+remote_enabled_feature_flags_is_supported_locally(Node, Timeout) ->
+ case query_remote_feature_flags(Node, enabled, Timeout) of
+ {error, _} ->
+ false;
+ RemoteEnabledFeatureFlags when is_map(RemoteEnabledFeatureFlags) ->
+ RemoteEnabledFeatureNames = maps:keys(RemoteEnabledFeatureFlags),
+ is_supported_locally(RemoteEnabledFeatureNames)
+ end.
+
+-spec query_remote_feature_flags(node(),
+ Which :: all | enabled | disabled,
+ timeout()) ->
+ feature_flags() | {error, any()}.
+%% @private
+
+query_remote_feature_flags(Node, Which, Timeout) ->
+ rabbit_log:debug("Feature flags: querying ~s feature flags "
+ "on node `~s`...",
+ [Which, Node]),
+ case rpc:call(Node, ?MODULE, list, [Which], Timeout) of
+ {badrpc, {'EXIT',
+ {undef,
+ [{?MODULE, list, [Which], []}
+ | _]}}} ->
+ %% See does_node_support/3 for an explanation why we
+ %% consider this node a 3.7.x node.
+ rabbit_log:debug(
+ "Feature flags: ?MODULE:list(~s) unavailable on node `~s`: "
+ "assuming it is a RabbitMQ 3.7.x node "
+ "=> consider the list empty",
+ [Which, Node]),
+ #{};
+ {badrpc, Reason} = Error ->
+ rabbit_log:error(
+ "Feature flags: error while querying ~s feature flags "
+ "on node `~s`: ~p",
+ [Which, Node, Reason]),
+ {error, Error};
+ RemoteFeatureFlags when is_map(RemoteFeatureFlags) ->
+ RemoteFeatureNames = maps:keys(RemoteFeatureFlags),
+ rabbit_log:debug("Feature flags: querying ~s feature flags "
+ "on node `~s` done; ~s features: ~p",
+ [Which, Node, Which, RemoteFeatureNames]),
+ RemoteFeatureFlags
+ end.
+
+-spec sync_feature_flags_with_cluster([node()]) ->
+ ok | {error, any()} | no_return().
+%% @private
+
+sync_feature_flags_with_cluster(Nodes) ->
+ sync_feature_flags_with_cluster(Nodes, ?TIMEOUT).
+
+-spec sync_feature_flags_with_cluster([node()], timeout()) ->
+ ok | {error, any()} | no_return().
+%% @private
+
+sync_feature_flags_with_cluster([], _) ->
+ verify_which_feature_flags_are_actually_enabled(),
+ FeatureNames = get_forced_feature_flag_names(),
+ case remote_nodes() of
+ [] when FeatureNames =:= undefined ->
+ rabbit_log:debug(
+ "Feature flags: starting an unclustered node: "
+ "all feature flags will be enabled by default"),
+ enable_all();
+ [] ->
+ case FeatureNames of
+ [] ->
+ rabbit_log:debug(
+ "Feature flags: starting an unclustered node: "
+ "all feature flags are forcibly left disabled "
+ "from the RABBITMQ_FEATURE_FLAGS environment "
+ "variable");
+ _ ->
+ rabbit_log:debug(
+ "Feature flags: starting an unclustered node: "
+ "only the following feature flags specified in "
+ "the RABBITMQ_FEATURE_FLAGS environment variable "
+ "will be enabled: ~p",
+ [FeatureNames])
+ end,
+ enable(FeatureNames);
+ _ ->
+ ok
+ end;
+sync_feature_flags_with_cluster(Nodes, Timeout) ->
+ verify_which_feature_flags_are_actually_enabled(),
+ RemoteNodes = Nodes -- [node()],
+ sync_feature_flags_with_cluster1(RemoteNodes, Timeout).
+
+sync_feature_flags_with_cluster1([], _) ->
+ ok;
+sync_feature_flags_with_cluster1(RemoteNodes, Timeout) ->
+ RandomRemoteNode = pick_one_node(RemoteNodes),
+ rabbit_log:debug("Feature flags: SYNCING FEATURE FLAGS with node `~s`...",
+ [RandomRemoteNode]),
+ case query_remote_feature_flags(RandomRemoteNode, enabled, Timeout) of
+ {error, _} = Error ->
+ Error;
+ RemoteFeatureFlags ->
+ RemoteFeatureNames = maps:keys(RemoteFeatureFlags),
+ do_sync_feature_flags_with_node1(RemoteFeatureNames)
+ end.
+
+pick_one_node(Nodes) ->
+ RandomIndex = rand:uniform(length(Nodes)),
+ lists:nth(RandomIndex, Nodes).
+
+do_sync_feature_flags_with_node1([FeatureFlag | Rest]) ->
+ case enable_locally(FeatureFlag) of
+ ok -> do_sync_feature_flags_with_node1(Rest);
+ Error -> Error
+ end;
+do_sync_feature_flags_with_node1([]) ->
+ ok.
+
+-spec get_forced_feature_flag_names() -> [feature_name()] | undefined.
+%% @private
+%% @doc
+%% Returns the (possibly empty) list of feature flags the user want
+%% to enable out-of-the-box when starting a node for the first time.
+%%
+%% Without this, the default is to enable all the supported feature
+%% flags.
+%%
+%% There are two ways to specify that list:
+%% <ol>
+%% <li>Using the `$RABBITMQ_FEATURE_FLAGS' environment variable; for
+%% instance `RABBITMQ_FEATURE_FLAGS=quorum_queue,mnevis'.</li>
+%% <li>Using the `forced_feature_flags_on_init' configuration parameter;
+%% for instance
+%% `{rabbit, [{forced_feature_flags_on_init, [quorum_queue, mnevis]}]}'.</li>
+%% </ol>
+%%
+%% The environment variable has precedence over the configuration
+%% parameter.
+
+get_forced_feature_flag_names() ->
+ Ret = case get_forced_feature_flag_names_from_env() of
+ undefined -> get_forced_feature_flag_names_from_config();
+ List -> List
+ end,
+ case Ret of
+ undefined -> ok;
+ [] -> rabbit_log:info("Feature flags: automatic enablement "
+ "of feature flags disabled (i.e. none "
+ "will be enabled automatically)", []);
+ _ -> rabbit_log:info("Feature flags: automatic enablement "
+ "of feature flags limited to the "
+ "following list: ~p", [Ret])
+ end,
+ Ret.
+
+-spec get_forced_feature_flag_names_from_env() -> [feature_name()] | undefined.
+%% @private
+
+get_forced_feature_flag_names_from_env() ->
+ case os:getenv("RABBITMQ_FEATURE_FLAGS") of
+ false -> undefined;
+ Value -> [list_to_atom(V) ||V <- string:lexemes(Value, ",")]
+ end.
+
+-spec get_forced_feature_flag_names_from_config() -> [feature_name()] | undefined.
+%% @private
+
+get_forced_feature_flag_names_from_config() ->
+ Value = application:get_env(rabbit,
+ forced_feature_flags_on_init,
+ undefined),
+ case Value of
+ undefined ->
+ Value;
+ _ when is_list(Value) ->
+ case lists:all(fun is_atom/1, Value) of
+ true -> Value;
+ false -> undefined
+ end;
+ _ ->
+ undefined
+ end.
+
+-spec verify_which_feature_flags_are_actually_enabled() ->
+ ok | {error, any()} | no_return().
+%% @private
+
+verify_which_feature_flags_are_actually_enabled() ->
+ AllFeatureFlags = list(all),
+ EnabledFeatureNames = read_enabled_feature_flags_list(),
+ rabbit_log:debug("Feature flags: double-checking feature flag states..."),
+ %% In case the previous instance of the node failed to write the
+ %% feature flags list file, we want to double-check the list of
+ %% enabled feature flags read from disk. For each feature flag,
+ %% we call the migration function to query if the feature flag is
+ %% actually enabled.
+ %%
+ %% If a feature flag doesn't provide a migration function (or if the
+ %% function fails), we keep the current state of the feature flag.
+ List1 = maps:fold(
+ fun(Name, Props, Acc) ->
+ Ret = run_migration_fun(Name, Props, is_enabled),
+ case Ret of
+ true ->
+ [Name | Acc];
+ false ->
+ Acc;
+ _ ->
+ MarkedAsEnabled = is_enabled(Name),
+ case MarkedAsEnabled of
+ true -> [Name | Acc];
+ false -> Acc
+ end
+ end
+ end,
+ [], AllFeatureFlags),
+ RepairedEnabledFeatureNames = lists:sort(List1),
+ %% We log the list of feature flags for which the state changes
+ %% after the check above.
+ WereEnabled = RepairedEnabledFeatureNames -- EnabledFeatureNames,
+ WereDisabled = EnabledFeatureNames -- RepairedEnabledFeatureNames,
+ case {WereEnabled, WereDisabled} of
+ {[], []} -> ok;
+ _ -> rabbit_log:warning(
+ "Feature flags: the previous instance of this node "
+ "must have failed to write the `feature_flags` "
+ "file at `~s`:",
+ [enabled_feature_flags_list_file()])
+ end,
+ case WereEnabled of
+ [] -> ok;
+ _ -> rabbit_log:warning(
+ "Feature flags: - list of previously enabled "
+ "feature flags now marked as such: ~p", [WereEnabled])
+ end,
+ case WereDisabled of
+ [] -> ok;
+ _ -> rabbit_log:warning(
+ "Feature flags: - list of previously disabled "
+ "feature flags now marked as such: ~p", [WereDisabled])
+ end,
+ %% Finally, if the new list of enabled feature flags is different
+ %% than the one on disk, we write the new list and re-initialize the
+ %% registry.
+ case RepairedEnabledFeatureNames of
+ EnabledFeatureNames ->
+ ok;
+ _ ->
+ rabbit_log:debug(
+ "Feature flags: write the repaired list of enabled feature "
+ "flags"),
+ WrittenToDisk = ok =:= try_to_write_enabled_feature_flags_list(
+ RepairedEnabledFeatureNames),
+ initialize_registry(
+ RepairedEnabledFeatureNames, [], WrittenToDisk)
+ end.
diff --git a/src/rabbit_ff_extra.erl b/src/rabbit_ff_extra.erl
new file mode 100644
index 0000000000..35cd98d740
--- /dev/null
+++ b/src/rabbit_ff_extra.erl
@@ -0,0 +1,236 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2018-2019 Pivotal Software, Inc. All rights reserved.
+%%
+
+%% @author The RabbitMQ team
+%% @copyright 2018-2019 Pivotal Software, Inc.
+%%
+%% @doc
+%% This module provides extra functions unused by the feature flags
+%% subsystem core functionality.
+
+-module(rabbit_ff_extra).
+
+-export([cli_info/0,
+ info/1,
+ info/2,
+ format_error/1]).
+
+-type cli_info() :: [cli_info_entry()].
+%% A list of feature flags properties, formatted for the RabbitMQ CLI.
+
+-type cli_info_entry() :: [{name, rabbit_feature_flags:feature_name()} |
+ {state, enabled | disabled | unavailable} |
+ {stability, rabbit_feature_flags:stability()} |
+ {provided_by, atom()} |
+ {desc, string()} |
+ {doc_url, string()}].
+%% A list of properties for a single feature flag, formatted for the
+%% RabbitMQ CLI.
+
+-type info_options() :: #{color => boolean(),
+ lines => boolean(),
+ verbose => non_neg_integer()}.
+%% Options accepted by {@link info/1} and {@link info/2}.
+
+-export_type([info_options/0]).
+
+-spec cli_info() -> cli_info().
+%% @doc
+%% Returns a list of all feature flags properties.
+%%
+%% @returns the list of all feature flags properties.
+
+cli_info() ->
+ cli_info(rabbit_feature_flags:list(all)).
+
+-spec cli_info(rabbit_feature_flags:feature_flags()) -> cli_info().
+%% @doc
+%% Formats a map of feature flags and their properties into a list of
+%% feature flags properties as expected by the RabbitMQ CLI.
+%%
+%% @param FeatureFlags A map of feature flags.
+%% @returns the list of feature flags properties, created from the map
+%% specified in arguments.
+
+cli_info(FeatureFlags) ->
+ lists:foldr(
+ fun(FeatureName, Acc) ->
+ FeatureProps = maps:get(FeatureName, FeatureFlags),
+ State = rabbit_feature_flags:get_state(FeatureName),
+ Stability = rabbit_feature_flags:get_stability(FeatureProps),
+ App = maps:get(provided_by, FeatureProps),
+ Desc = maps:get(desc, FeatureProps, ""),
+ DocUrl = maps:get(doc_url, FeatureProps, ""),
+ FFInfo = [{name, FeatureName},
+ {desc, unicode:characters_to_binary(Desc)},
+ {doc_url, unicode:characters_to_binary(DocUrl)},
+ {state, State},
+ {stability, Stability},
+ {provided_by, App}],
+ [FFInfo | Acc]
+ end, [], lists:sort(maps:keys(FeatureFlags))).
+
+-spec info(info_options()) -> ok.
+%% @doc
+%% Displays an array of all supported feature flags and their properties
+%% on `stdout'.
+%%
+%% @param Options Options to tune what is displayed and how.
+
+info(Options) ->
+ %% Two tables: one for stable feature flags, one for experimental ones.
+ StableFF = rabbit_feature_flags:list(all, stable),
+ case maps:size(StableFF) of
+ 0 ->
+ ok;
+ _ ->
+ io:format("~n~s## Stable feature flags:~s~n",
+ [rabbit_pretty_stdout:ascii_color(bright_white),
+ rabbit_pretty_stdout:ascii_color(default)]),
+ info(StableFF, Options)
+ end,
+ ExpFF = rabbit_feature_flags:list(all, experimental),
+ case maps:size(ExpFF) of
+ 0 ->
+ ok;
+ _ ->
+ io:format("~n~s## Experimental feature flags:~s~n",
+ [rabbit_pretty_stdout:ascii_color(bright_white),
+ rabbit_pretty_stdout:ascii_color(default)]),
+ info(ExpFF, Options)
+ end,
+ case maps:size(StableFF) + maps:size(ExpFF) of
+ 0 -> ok;
+ _ -> state_legend()
+ end.
+
+-spec info(rabbit_feature_flags:feature_flags(), info_options()) -> ok.
+%% @doc
+%% Displays an array of feature flags and their properties on `stdout',
+%% based on the specified feature flags map.
+%%
+%% @param FeatureFlags Map of the feature flags to display.
+%% @param Options Options to tune what is displayed and how.
+
+info(FeatureFlags, Options) ->
+ Verbose = maps:get(verbose, Options, 0),
+ IsaTty= case os:getenv("TERM") of
+ %% We don't have access to isatty(3), so let's
+ %% assume that is $TERM is defined, we can use
+ %% colors and drawing characters.
+ false -> false;
+ _ -> true
+ end,
+ UseColors = maps:get(color, Options, IsaTty),
+ UseLines = maps:get(lines, Options, IsaTty),
+ %% Table columns:
+ %% | Name | State | Provided by | Description
+ %%
+ %% where:
+ %% State = Enabled | Disabled | Unavailable (if a node doesn't
+ %% support it).
+ TableHeader = [
+ [{"Name", bright_white},
+ {"State", bright_white},
+ {"Provided by", bright_white},
+ {"Description", bright_white}]
+ ],
+ Nodes = lists:sort([node() | rabbit_feature_flags:remote_nodes()]),
+ Rows = lists:foldr(
+ fun(FeatureName, Acc) ->
+ FeatureProps = maps:get(FeatureName, FeatureFlags),
+ State0 = rabbit_feature_flags:get_state(FeatureName),
+ {State, StateColor} = case State0 of
+ enabled ->
+ {"Enabled", green};
+ disabled ->
+ {"Disabled", yellow};
+ unavailable ->
+ {"Unavailable", red_bg}
+ end,
+ App = maps:get(provided_by, FeatureProps),
+ Desc = maps:get(desc, FeatureProps, ""),
+ MainLine = [{atom_to_list(FeatureName), bright_white},
+ {State, StateColor},
+ {atom_to_list(App), default},
+ {Desc, default}],
+ VFun = fun(Node) ->
+ Supported =
+ rabbit_feature_flags:does_node_support(
+ Node, [FeatureName], 60000),
+ {Label, LabelColor} =
+ case Supported of
+ true -> {"supported", default};
+ false -> {"unsupported", red_bg}
+ end,
+ Uncolored = rabbit_misc:format(
+ " ~s: ~s", [Node, Label]),
+ Colored = rabbit_misc:format(
+ " ~s: ~s~s~s",
+ [Node,
+ rabbit_pretty_stdout:
+ ascii_color(LabelColor),
+ Label,
+ rabbit_pretty_stdout:
+ ascii_color(default)]),
+ [empty,
+ empty,
+ empty,
+ {Uncolored, Colored}]
+ end,
+ if
+ Verbose > 0 ->
+ [[MainLine,
+ empty,
+ [empty,
+ empty,
+ empty,
+ {"Per-node support level:", default}]
+ | lists:map(VFun, Nodes)] | Acc];
+ true ->
+ [[MainLine] | Acc]
+ end
+ end, [], lists:sort(maps:keys(FeatureFlags))),
+ io:format("~n", []),
+ rabbit_pretty_stdout:display_table([TableHeader | Rows],
+ UseColors,
+ UseLines).
+
+state_legend() ->
+ io:format(
+ "~n"
+ "Possible states:~n"
+ " ~sEnabled~s: The feature flag is enabled on all nodes~n"
+ " ~sDisabled~s: The feature flag is disabled on all nodes~n"
+ " ~sUnavailable~s: The feature flag cannot be enabled because one or "
+ "more nodes do not support it~n"
+ "~n",
+ [rabbit_pretty_stdout:ascii_color(green),
+ rabbit_pretty_stdout:ascii_color(default),
+ rabbit_pretty_stdout:ascii_color(yellow),
+ rabbit_pretty_stdout:ascii_color(default),
+ rabbit_pretty_stdout:ascii_color(red_bg),
+ rabbit_pretty_stdout:ascii_color(default)]).
+
+-spec format_error(any()) -> string().
+%% @doc
+%% Formats the error reason term so it can be presented to human beings.
+%%
+%% @param Reason The term in the `{error, Reason}' tuple.
+%% @returns the formatted error reason.
+
+format_error(Reason) ->
+ rabbit_misc:format("~p", [Reason]).
diff --git a/src/rabbit_ff_registry.erl b/src/rabbit_ff_registry.erl
new file mode 100644
index 0000000000..e18a4b3456
--- /dev/null
+++ b/src/rabbit_ff_registry.erl
@@ -0,0 +1,167 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License
+%% at http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and
+%% limitations under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2018-2019 Pivotal Software, Inc. All rights reserved.
+%%
+
+%% @author The RabbitMQ team
+%% @copyright 2018-2019 Pivotal Software, Inc.
+%%
+%% @doc
+%% This module exposes the API of the {@link rabbit_feature_flags}
+%% registry. The feature flags registry is an Erlang module, compiled at
+%% runtime, storing all the informations about feature flags: which are
+%% supported, which are enabled, etc.
+%%
+%% Because it is compiled at runtime, the initial source code is mostly
+%% an API reference. What the initial module does is merely ask {@link
+%% rabbit_feature_flags} to generate the real registry.
+
+-module(rabbit_ff_registry).
+
+-export([get/1,
+ list/1,
+ is_supported/1,
+ is_enabled/1,
+ is_registry_initialized/0,
+ is_registry_written_to_disk/0]).
+
+-spec get(rabbit_feature_flags:feature_name()) ->
+ rabbit_feature_flags:feature_props() | undefined.
+%% @doc
+%% Returns the properties of a feature flag.
+%%
+%% Only the informations stored in the local registry is used to answer
+%% this call.
+%%
+%% @param FeatureName The name of the feature flag.
+%% @returns the properties of the specified feature flag.
+
+get(FeatureName) ->
+ rabbit_feature_flags:initialize_registry(),
+ %% Initially, is_registry_initialized/0 always returns `false`
+ %% and this ?MODULE:get(FeatureName) is always called. The case
+ %% statement is here to please Dialyzer.
+ case is_registry_initialized() of
+ false -> ?MODULE:get(FeatureName);
+ true -> undefined
+ end.
+
+-spec list(all | enabled | disabled) -> rabbit_feature_flags:feature_flags().
+%% @doc
+%% Lists all, enabled or disabled feature flags, depending on the argument.
+%%
+%% Only the informations stored in the local registry is used to answer
+%% this call.
+%%
+%% @param Which The group of feature flags to return: `all', `enabled' or
+%% `disabled'.
+%% @returns A map of selected feature flags.
+
+list(Which) ->
+ rabbit_feature_flags:initialize_registry(),
+ %% See get/1 for an explanation of the case statement below.
+ case is_registry_initialized() of
+ false -> ?MODULE:list(Which);
+ true -> #{}
+ end.
+
+-spec is_supported(rabbit_feature_flags:feature_name()) -> boolean().
+%% @doc
+%% Returns if a feature flag is supported.
+%%
+%% Only the informations stored in the local registry is used to answer
+%% this call.
+%%
+%% @param FeatureName The name of the feature flag to be checked.
+%% @returns `true' if the feature flag is supported, or `false'
+%% otherwise.
+
+is_supported(FeatureName) ->
+ rabbit_feature_flags:initialize_registry(),
+ %% See get/1 for an explanation of the case statement below.
+ case is_registry_initialized() of
+ false -> ?MODULE:is_supported(FeatureName);
+ true -> false
+ end.
+
+-spec is_enabled(rabbit_feature_flags:feature_name()) -> boolean() | state_changing.
+%% @doc
+%% Returns if a feature flag is supported or if its state is changing.
+%%
+%% Only the informations stored in the local registry is used to answer
+%% this call.
+%%
+%% @param FeatureName The name of the feature flag to be checked.
+%% @returns `true' if the feature flag is supported, `state_changing' if
+%% its state is transient, or `false' otherwise.
+
+is_enabled(FeatureName) ->
+ rabbit_feature_flags:initialize_registry(),
+ %% See get/1 for an explanation of the case statement below.
+ case is_registry_initialized() of
+ false -> ?MODULE:is_enabled(FeatureName);
+ true -> false
+ end.
+
+-spec is_registry_initialized() -> boolean().
+%% @doc
+%% Indicates if the registry is initialized.
+%%
+%% The registry is considered initialized once the initial Erlang module
+%% was replaced by the copy compiled at runtime.
+%%
+%% @returns `true' when the module is the one compiled at runtime,
+%% `false' when the module is the initial one compiled from RabbitMQ
+%% source code.
+
+is_registry_initialized() ->
+ always_return_false().
+
+-spec is_registry_written_to_disk() -> boolean().
+%% @doc
+%% Indicates if the feature flags state was successfully persisted to disk.
+%%
+%% Note that on startup, {@link rabbit_feature_flags} tries to determine
+%% the state of each supported feature flag, regardless of the
+%% information on disk, to ensure maximum consistency. However, this can
+%% be done for feature flags supporting it only.
+%%
+%% @returns `true' if the state was successfully written to disk and
+%% the registry can be initialized from that during the next RabbitMQ
+%% startup, `false' if the write failed and the node might loose feature
+%% flags state on restart.
+
+is_registry_written_to_disk() ->
+ always_return_true().
+
+always_return_true() ->
+ %% This function is here to trick Dialyzer. We want some functions
+ %% in this initial on-disk registry to always return `true` or
+ %% `false`. However the generated regsitry will return actual
+ %% booleans. The `-spec()` correctly advertises a return type of
+ %% `boolean()`. But in the meantime, Dialyzer only knows about this
+ %% copy which, without the trick below, would always return either
+ %% `true` (e.g. in is_registry_written_to_disk/0) or `false` (e.g.
+ %% is_registry_initialized/0). This obviously causes some warnings
+ %% where the registry functions are used: Dialyzer believes that
+ %% e.g. matching the return value of is_registry_initialized/0
+ %% against `true` will never succeed.
+ %%
+ %% That's why this function makes a call which we know the result,
+ %% but not Dialyzer, to "create" that hard-coded `true` return
+ %% value.
+ rand:uniform(1) > 0.
+
+always_return_false() ->
+ not always_return_true().
diff --git a/src/rabbit_fhc_helpers.erl b/src/rabbit_fhc_helpers.erl
index 90833795da..57dbd981f1 100644
--- a/src/rabbit_fhc_helpers.erl
+++ b/src/rabbit_fhc_helpers.erl
@@ -18,7 +18,7 @@
-export([clear_read_cache/0]).
--include("rabbit.hrl"). % For #amqqueue record definition.
+-include("amqqueue.hrl").
clear_read_cache() ->
case application:get_env(rabbit, fhc_read_buffering) of
@@ -37,7 +37,9 @@ clear_vhost_read_cache([VHost | Rest]) ->
clear_queue_read_cache([]) ->
ok;
-clear_queue_read_cache([#amqqueue{pid = MPid, slave_pids = SPids} | Rest]) ->
+clear_queue_read_cache([Q | Rest]) when ?is_amqqueue(Q) ->
+ MPid = amqqueue:get_pid(Q),
+ SPids = amqqueue:get_slave_pids(Q),
%% Limit the action to the current node.
Pids = [P || P <- [MPid | SPids], node(P) =:= node()],
%% This function is executed in the context of the backing queue
diff --git a/src/rabbit_file.erl b/src/rabbit_file.erl
index 557bc6f02a..462ddb4e11 100644
--- a/src/rabbit_file.erl
+++ b/src/rabbit_file.erl
@@ -34,31 +34,10 @@
-type ok_or_error() :: rabbit_types:ok_or_error(any()).
--spec is_file((file:filename())) -> boolean().
--spec is_dir((file:filename())) -> boolean().
--spec file_size((file:filename())) -> non_neg_integer().
--spec ensure_dir((file:filename())) -> ok_or_error().
--spec wildcard(string(), file:filename()) -> [file:filename()].
--spec list_dir(file:filename()) ->
- rabbit_types:ok_or_error2([file:filename()], any()).
--spec read_term_file
- (file:filename()) -> {'ok', [any()]} | rabbit_types:error(any()).
--spec write_term_file(file:filename(), [any()]) -> ok_or_error().
--spec write_file(file:filename(), iodata()) -> ok_or_error().
--spec write_file(file:filename(), iodata(), [any()]) -> ok_or_error().
--spec append_file(file:filename(), string()) -> ok_or_error().
--spec ensure_parent_dirs_exist(string()) -> 'ok'.
--spec rename(file:filename(), file:filename()) -> ok_or_error().
--spec delete([file:filename()]) -> ok_or_error().
--spec recursive_delete([file:filename()]) ->
- rabbit_types:ok_or_error({file:filename(), any()}).
--spec recursive_copy(file:filename(), file:filename()) ->
- rabbit_types:ok_or_error({file:filename(), file:filename(), any()}).
--spec lock_file(file:filename()) -> rabbit_types:ok_or_error('eexist').
--spec filename_as_a_directory(file:filename()) -> file:filename().
-
%%----------------------------------------------------------------------------
+-spec is_file((file:filename())) -> boolean().
+
is_file(File) ->
case read_file_info(File) of
{ok, #file_info{type=regular}} -> true;
@@ -66,6 +45,8 @@ is_file(File) ->
_ -> false
end.
+-spec is_dir((file:filename())) -> boolean().
+
is_dir(Dir) -> is_dir_internal(read_file_info(Dir)).
is_dir_no_handle(Dir) -> is_dir_internal(prim_file:read_file_info(Dir)).
@@ -73,12 +54,16 @@ is_dir_no_handle(Dir) -> is_dir_internal(prim_file:read_file_info(Dir)).
is_dir_internal({ok, #file_info{type=directory}}) -> true;
is_dir_internal(_) -> false.
+-spec file_size((file:filename())) -> non_neg_integer().
+
file_size(File) ->
case read_file_info(File) of
{ok, #file_info{size=Size}} -> Size;
_ -> 0
end.
+-spec ensure_dir((file:filename())) -> ok_or_error().
+
ensure_dir(File) -> with_handle(fun () -> ensure_dir_internal(File) end).
ensure_dir_internal("/") ->
@@ -91,6 +76,8 @@ ensure_dir_internal(File) ->
prim_file:make_dir(Dir)
end.
+-spec wildcard(string(), file:filename()) -> [file:filename()].
+
wildcard(Pattern, Dir) ->
case list_dir(Dir) of
{ok, Files} -> {ok, RE} = re:compile(Pattern, [anchored]),
@@ -99,11 +86,17 @@ wildcard(Pattern, Dir) ->
{error, _} -> []
end.
+-spec list_dir(file:filename()) ->
+ rabbit_types:ok_or_error2([file:filename()], any()).
+
list_dir(Dir) -> with_handle(fun () -> prim_file:list_dir(Dir) end).
read_file_info(File) ->
with_handle(fun () -> prim_file:read_file_info(File) end).
+-spec read_term_file
+ (file:filename()) -> {'ok', [any()]} | rabbit_types:error(any()).
+
read_term_file(File) ->
try
{ok, Data} = with_handle(fun () -> prim_file:read_file(File) end),
@@ -124,12 +117,18 @@ group_tokens(Cur, []) -> [Cur];
group_tokens(Cur, [T = {dot, _} | Ts]) -> [[T | Cur] | group_tokens([], Ts)];
group_tokens(Cur, [T | Ts]) -> group_tokens([T | Cur], Ts).
+-spec write_term_file(file:filename(), [any()]) -> ok_or_error().
+
write_term_file(File, Terms) ->
write_file(File, list_to_binary([io_lib:format("~w.~n", [Term]) ||
Term <- Terms])).
+-spec write_file(file:filename(), iodata()) -> ok_or_error().
+
write_file(Path, Data) -> write_file(Path, Data, []).
+-spec write_file(file:filename(), iodata(), [any()]) -> ok_or_error().
+
write_file(Path, Data, Modes) ->
Modes1 = [binary, write | (Modes -- [binary, write])],
case make_binary(Data) of
@@ -185,6 +184,9 @@ with_synced_copy(Path, Modes, Fun) ->
end.
%% TODO the semantics of this function are rather odd. But see bug 25021.
+
+-spec append_file(file:filename(), string()) -> ok_or_error().
+
append_file(File, Suffix) ->
case read_file_info(File) of
{ok, FInfo} -> append_file(File, FInfo#file_info.size, Suffix);
@@ -209,6 +211,8 @@ append_file(File, _, Suffix) ->
Error -> Error
end.
+-spec ensure_parent_dirs_exist(string()) -> 'ok'.
+
ensure_parent_dirs_exist(Filename) ->
case ensure_dir(Filename) of
ok -> ok;
@@ -216,10 +220,17 @@ ensure_parent_dirs_exist(Filename) ->
throw({error, {cannot_create_parent_dirs, Filename, Reason}})
end.
+-spec rename(file:filename(), file:filename()) -> ok_or_error().
+
rename(Old, New) -> with_handle(fun () -> prim_file:rename(Old, New) end).
+-spec delete([file:filename()]) -> ok_or_error().
+
delete(File) -> with_handle(fun () -> prim_file:delete(File) end).
+-spec recursive_delete([file:filename()]) ->
+ rabbit_types:ok_or_error({file:filename(), any()}).
+
recursive_delete(Files) ->
with_handle(
fun () -> lists:foldl(fun (Path, ok) -> recursive_delete1(Path);
@@ -262,6 +273,9 @@ is_symlink_no_handle(File) ->
_ -> false
end.
+-spec recursive_copy(file:filename(), file:filename()) ->
+ rabbit_types:ok_or_error({file:filename(), file:filename(), any()}).
+
recursive_copy(Src, Dest) ->
%% Note that this uses the 'file' module and, hence, shouldn't be
%% run on many processes at once.
@@ -293,6 +307,9 @@ recursive_copy(Src, Dest) ->
%% TODO: When we stop supporting Erlang prior to R14, this should be
%% replaced with file:open [write, exclusive]
+
+-spec lock_file(file:filename()) -> rabbit_types:ok_or_error('eexist').
+
lock_file(Path) ->
case is_file(Path) of
true -> {error, eexist};
@@ -302,6 +319,8 @@ lock_file(Path) ->
end)
end.
+-spec filename_as_a_directory(file:filename()) -> file:filename().
+
filename_as_a_directory(FileName) ->
case lists:last(FileName) of
"/" ->
diff --git a/src/rabbit_guid.erl b/src/rabbit_guid.erl
index 386702e86b..6f03a1a04f 100644
--- a/src/rabbit_guid.erl
+++ b/src/rabbit_guid.erl
@@ -36,15 +36,10 @@
-type guid() :: binary().
--spec start_link() -> rabbit_types:ok_pid_or_error().
--spec filename() -> string().
--spec gen() -> guid().
--spec gen_secure() -> guid().
--spec string(guid(), any()) -> string().
--spec binary(guid(), any()) -> binary().
-
%%----------------------------------------------------------------------------
+-spec start_link() -> rabbit_types:ok_pid_or_error().
+
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE,
[update_disk_serial()], []).
@@ -52,6 +47,9 @@ start_link() ->
%% We use this to detect a (possibly rather old) Mnesia directory,
%% since it has existed since at least 1.7.0 (as far back as I cared
%% to go).
+
+-spec filename() -> string().
+
filename() ->
filename:join(rabbit_mnesia:dir(), ?SERIAL_FILENAME).
@@ -108,6 +106,9 @@ advance_blocks({B1, B2, B3, B4}, I) ->
%% generate a GUID. This function should be used when performance is a
%% priority and predictability is not an issue. Otherwise use
%% gen_secure/0.
+
+-spec gen() -> guid().
+
gen() ->
%% We hash a fresh GUID with md5, split it in 4 blocks, and each
%% time we need a new guid we rotate them producing a new hash
@@ -129,6 +130,9 @@ gen() ->
%% serial store hasn't been deleted.
%%
%% If you are not concerned with predictability, gen/0 is faster.
+
+-spec gen_secure() -> guid().
+
gen_secure() ->
%% Here instead of hashing once we hash the GUID and the counter
%% each time, so that the GUID is not predictable.
@@ -143,9 +147,14 @@ gen_secure() ->
%%
%% employs base64url encoding, which is safer in more contexts than
%% plain base64.
+
+-spec string(guid(), any()) -> string().
+
string(G, Prefix) ->
Prefix ++ "-" ++ rabbit_misc:base64url(G).
+-spec binary(guid(), any()) -> binary().
+
binary(G, Prefix) ->
list_to_binary(string(G, Prefix)).
diff --git a/src/rabbit_health_check.erl b/src/rabbit_health_check.erl
index 54508bfdb0..97d8d44dc3 100644
--- a/src/rabbit_health_check.erl
+++ b/src/rabbit_health_check.erl
@@ -21,19 +21,20 @@
%% Internal API
-export([local/0]).
--spec node(node(), timeout()) -> ok | {badrpc, term()} | {error_string, string()}.
--spec local() -> ok | {error_string, string()}.
-
%%----------------------------------------------------------------------------
%% External functions
%%----------------------------------------------------------------------------
+-spec node(node(), timeout()) -> ok | {badrpc, term()} | {error_string, string()}.
+
node(Node) ->
%% same default as in CLI
node(Node, 70000).
node(Node, Timeout) ->
rabbit_misc:rpc_call(Node, rabbit_health_check, local, [], Timeout).
+-spec local() -> ok | {error_string, string()}.
+
local() ->
run_checks([list_channels, list_queues, alarms, rabbit_node_monitor]).
diff --git a/src/rabbit_limiter.erl b/src/rabbit_limiter.erl
index b5f09a525b..9770172089 100644
--- a/src/rabbit_limiter.erl
+++ b/src/rabbit_limiter.erl
@@ -147,36 +147,6 @@
-type credit_mode() :: 'manual' | 'drain' | 'auto'.
--spec start_link(rabbit_types:proc_name()) ->
- rabbit_types:ok_pid_or_error().
--spec new(pid()) -> lstate().
-
--spec limit_prefetch(lstate(), non_neg_integer(), non_neg_integer()) ->
- lstate().
--spec unlimit_prefetch(lstate()) -> lstate().
--spec is_active(lstate()) -> boolean().
--spec get_prefetch_limit(lstate()) -> non_neg_integer().
--spec ack(lstate(), non_neg_integer()) -> 'ok'.
--spec pid(lstate()) -> pid().
-
--spec client(pid()) -> qstate().
--spec activate(qstate()) -> qstate().
--spec can_send(qstate(), boolean(), rabbit_types:ctag()) ->
- {'continue' | 'suspend', qstate()}.
--spec resume(qstate()) -> qstate().
--spec deactivate(qstate()) -> qstate().
--spec is_suspended(qstate()) -> boolean().
--spec is_consumer_blocked(qstate(), rabbit_types:ctag()) -> boolean().
--spec credit
- (qstate(), rabbit_types:ctag(), non_neg_integer(), credit_mode(),
- boolean()) ->
- {boolean(), qstate()}.
--spec ack_from_queue(qstate(), rabbit_types:ctag(), non_neg_integer()) ->
- {boolean(), qstate()}.
--spec drained(qstate()) ->
- {[{rabbit_types:ctag(), non_neg_integer()}], qstate()}.
--spec forget_consumer(qstate(), rabbit_types:ctag()) -> qstate().
-
%%----------------------------------------------------------------------------
-record(lim, {prefetch_count = 0,
@@ -194,41 +164,66 @@
%% API
%%----------------------------------------------------------------------------
+-spec start_link(rabbit_types:proc_name()) ->
+ rabbit_types:ok_pid_or_error().
+
start_link(ProcName) -> gen_server2:start_link(?MODULE, [ProcName], []).
+-spec new(pid()) -> lstate().
+
new(Pid) ->
%% this a 'call' to ensure that it is invoked at most once.
ok = gen_server:call(Pid, {new, self()}, infinity),
#lstate{pid = Pid, prefetch_limited = false}.
+-spec limit_prefetch(lstate(), non_neg_integer(), non_neg_integer()) ->
+ lstate().
+
limit_prefetch(L, PrefetchCount, UnackedCount) when PrefetchCount > 0 ->
ok = gen_server:call(
L#lstate.pid,
{limit_prefetch, PrefetchCount, UnackedCount}, infinity),
L#lstate{prefetch_limited = true}.
+-spec unlimit_prefetch(lstate()) -> lstate().
+
unlimit_prefetch(L) ->
ok = gen_server:call(L#lstate.pid, unlimit_prefetch, infinity),
L#lstate{prefetch_limited = false}.
+-spec is_active(lstate()) -> boolean().
+
is_active(#lstate{prefetch_limited = Limited}) -> Limited.
+-spec get_prefetch_limit(lstate()) -> non_neg_integer().
+
get_prefetch_limit(#lstate{prefetch_limited = false}) -> 0;
get_prefetch_limit(L) ->
gen_server:call(L#lstate.pid, get_prefetch_limit, infinity).
+-spec ack(lstate(), non_neg_integer()) -> 'ok'.
+
ack(#lstate{prefetch_limited = false}, _AckCount) -> ok;
ack(L, AckCount) -> gen_server:cast(L#lstate.pid, {ack, AckCount}).
+-spec pid(lstate()) -> pid().
+
pid(#lstate{pid = Pid}) -> Pid.
+-spec client(pid()) -> qstate().
+
client(Pid) -> #qstate{pid = Pid, state = dormant, credits = gb_trees:empty()}.
+-spec activate(qstate()) -> qstate().
+
activate(L = #qstate{state = dormant}) ->
ok = gen_server:cast(L#qstate.pid, {register, self()}),
L#qstate{state = active};
activate(L) -> L.
+-spec can_send(qstate(), boolean(), rabbit_types:ctag()) ->
+ {'continue' | 'suspend', qstate()}.
+
can_send(L = #qstate{pid = Pid, state = State, credits = Credits},
AckRequired, CTag) ->
case is_consumer_blocked(L, CTag) of
@@ -246,18 +241,26 @@ safe_call(Pid, Msg, ExitValue) ->
fun () -> ExitValue end,
fun () -> gen_server2:call(Pid, Msg, infinity) end).
+-spec resume(qstate()) -> qstate().
+
resume(L = #qstate{state = suspended}) ->
L#qstate{state = active};
resume(L) -> L.
+-spec deactivate(qstate()) -> qstate().
+
deactivate(L = #qstate{state = dormant}) -> L;
deactivate(L) ->
ok = gen_server:cast(L#qstate.pid, {unregister, self()}),
L#qstate{state = dormant}.
+-spec is_suspended(qstate()) -> boolean().
+
is_suspended(#qstate{state = suspended}) -> true;
is_suspended(#qstate{}) -> false.
+-spec is_consumer_blocked(qstate(), rabbit_types:ctag()) -> boolean().
+
is_consumer_blocked(#qstate{credits = Credits}, CTag) ->
case gb_trees:lookup(CTag, Credits) of
none -> false;
@@ -265,6 +268,11 @@ is_consumer_blocked(#qstate{credits = Credits}, CTag) ->
{value, #credit{}} -> true
end.
+-spec credit
+ (qstate(), rabbit_types:ctag(), non_neg_integer(), credit_mode(),
+ boolean()) ->
+ {boolean(), qstate()}.
+
credit(Limiter = #qstate{credits = Credits}, CTag, Crd, Mode, IsEmpty) ->
{Res, Cr} =
case IsEmpty andalso Mode =:= drain of
@@ -273,6 +281,9 @@ credit(Limiter = #qstate{credits = Credits}, CTag, Crd, Mode, IsEmpty) ->
end,
{Res, Limiter#qstate{credits = enter_credit(CTag, Cr, Credits)}}.
+-spec ack_from_queue(qstate(), rabbit_types:ctag(), non_neg_integer()) ->
+ {boolean(), qstate()}.
+
ack_from_queue(Limiter = #qstate{credits = Credits}, CTag, Credit) ->
{Credits1, Unblocked} =
case gb_trees:lookup(CTag, Credits) of
@@ -284,6 +295,9 @@ ack_from_queue(Limiter = #qstate{credits = Credits}, CTag, Credit) ->
end,
{Unblocked, Limiter#qstate{credits = Credits1}}.
+-spec drained(qstate()) ->
+ {[{rabbit_types:ctag(), non_neg_integer()}], qstate()}.
+
drained(Limiter = #qstate{credits = Credits}) ->
Drain = fun(C) -> C#credit{credit = 0, mode = manual} end,
{CTagCredits, Credits2} =
@@ -295,6 +309,8 @@ drained(Limiter = #qstate{credits = Credits}) ->
end, {[], Credits}, Credits),
{CTagCredits, Limiter#qstate{credits = Credits2}}.
+-spec forget_consumer(qstate(), rabbit_types:ctag()) -> qstate().
+
forget_consumer(Limiter = #qstate{credits = Credits}, CTag) ->
Limiter#qstate{credits = gb_trees:delete_any(CTag, Credits)}.
diff --git a/src/rabbit_memory_monitor.erl b/src/rabbit_memory_monitor.erl
index 62cf87fb11..0f5c9fbe3c 100644
--- a/src/rabbit_memory_monitor.erl
+++ b/src/rabbit_memory_monitor.erl
@@ -54,31 +54,33 @@
-define(EPSILON, 0.000001). %% less than this and we clamp to 0
%%----------------------------------------------------------------------------
-
--spec start_link() -> rabbit_types:ok_pid_or_error().
--spec register(pid(), {atom(),atom(),[any()]}) -> 'ok'.
--spec deregister(pid()) -> 'ok'.
--spec report_ram_duration
- (pid(), float() | 'infinity') -> number() | 'infinity'.
--spec stop() -> 'ok'.
-
-%%----------------------------------------------------------------------------
%% Public API
%%----------------------------------------------------------------------------
+-spec start_link() -> rabbit_types:ok_pid_or_error().
+
start_link() ->
gen_server2:start_link({local, ?SERVER}, ?MODULE, [], []).
+-spec register(pid(), {atom(),atom(),[any()]}) -> 'ok'.
+
register(Pid, MFA = {_M, _F, _A}) ->
gen_server2:call(?SERVER, {register, Pid, MFA}, infinity).
+-spec deregister(pid()) -> 'ok'.
+
deregister(Pid) ->
gen_server2:cast(?SERVER, {deregister, Pid}).
+-spec report_ram_duration
+ (pid(), float() | 'infinity') -> number() | 'infinity'.
+
report_ram_duration(Pid, QueueDuration) ->
gen_server2:call(?SERVER,
{report_ram_duration, Pid, QueueDuration}, infinity).
+-spec stop() -> 'ok'.
+
stop() ->
gen_server2:cast(?SERVER, stop).
diff --git a/src/rabbit_metrics.erl b/src/rabbit_metrics.erl
index 2a0a967e86..2d5f9c34e2 100644
--- a/src/rabbit_metrics.erl
+++ b/src/rabbit_metrics.erl
@@ -25,11 +25,12 @@
-define(SERVER, ?MODULE).
--spec start_link() -> rabbit_types:ok_pid_or_error().
-
%%----------------------------------------------------------------------------
%% Starts the raw metrics storage and owns the ETS tables.
%%----------------------------------------------------------------------------
+
+-spec start_link() -> rabbit_types:ok_pid_or_error().
+
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
diff --git a/src/rabbit_mirror_queue_coordinator.erl b/src/rabbit_mirror_queue_coordinator.erl
index f2426e156f..96474b0d4e 100644
--- a/src/rabbit_mirror_queue_coordinator.erl
+++ b/src/rabbit_mirror_queue_coordinator.erl
@@ -26,7 +26,8 @@
-behaviour(gen_server2).
-behaviour(gm).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-include("gm_specs.hrl").
-record(state, { q,
@@ -36,14 +37,6 @@
depth_fun
}).
--spec start_link
- (rabbit_types:amqqueue(), pid() | 'undefined',
- rabbit_mirror_queue_master:death_fun(),
- rabbit_mirror_queue_master:depth_fun()) ->
- rabbit_types:ok_pid_or_error().
--spec get_gm(pid()) -> pid().
--spec ensure_monitoring(pid(), [pid()]) -> 'ok'.
-
%%----------------------------------------------------------------------------
%%
%% Mirror Queues
@@ -306,12 +299,22 @@
%%
%%----------------------------------------------------------------------------
+-spec start_link
+ (amqqueue:amqqueue(), pid() | 'undefined',
+ rabbit_mirror_queue_master:death_fun(),
+ rabbit_mirror_queue_master:depth_fun()) ->
+ rabbit_types:ok_pid_or_error().
+
start_link(Queue, GM, DeathFun, DepthFun) ->
gen_server2:start_link(?MODULE, [Queue, GM, DeathFun, DepthFun], []).
+-spec get_gm(pid()) -> pid().
+
get_gm(CPid) ->
gen_server2:call(CPid, get_gm, infinity).
+-spec ensure_monitoring(pid(), [pid()]) -> 'ok'.
+
ensure_monitoring(CPid, Pids) ->
gen_server2:cast(CPid, {ensure_monitoring, Pids}).
@@ -319,7 +322,8 @@ ensure_monitoring(CPid, Pids) ->
%% gen_server
%% ---------------------------------------------------------------------------
-init([#amqqueue { name = QueueName } = Q, GM, DeathFun, DepthFun]) ->
+init([Q, GM, DeathFun, DepthFun]) when ?is_amqqueue(Q) ->
+ QueueName = amqqueue:get_name(Q),
?store_proc_name(QueueName),
GM1 = case GM of
undefined ->
@@ -345,9 +349,9 @@ init([#amqqueue { name = QueueName } = Q, GM, DeathFun, DepthFun]) ->
handle_call(get_gm, _From, State = #state { gm = GM }) ->
reply(GM, State).
-handle_cast({gm_deaths, DeadGMPids},
- State = #state { q = #amqqueue { name = QueueName, pid = MPid } })
- when node(MPid) =:= node() ->
+handle_cast({gm_deaths, DeadGMPids}, State = #state{q = Q}) when ?amqqueue_pid_runs_on_local_node(Q) ->
+ QueueName = amqqueue:get_name(Q),
+ MPid = amqqueue:get_pid(Q),
case rabbit_mirror_queue_misc:remove_from_queue(
QueueName, MPid, DeadGMPids) of
{ok, MPid, DeadPids, ExtraNodes} ->
@@ -373,14 +377,15 @@ handle_cast({gm_deaths, DeadGMPids},
error(unexpected_mirrored_state)
end;
-handle_cast(request_depth, State = #state { depth_fun = DepthFun,
- q = #amqqueue { name = QName, pid = MPid }}) ->
+handle_cast(request_depth, State = #state{depth_fun = DepthFun, q = QArg}) when ?is_amqqueue(QArg) ->
+ QName = amqqueue:get_name(QArg),
+ MPid = amqqueue:get_pid(QArg),
case rabbit_amqqueue:lookup(QName) of
- {ok, #amqqueue{ pid = MPid }} ->
- ok = DepthFun(),
- noreply(State);
- _ ->
- {stop, shutdown, State}
+ {ok, QFound} when ?amqqueue_pid_equals(QFound, MPid) ->
+ ok = DepthFun(),
+ noreply(State);
+ _ ->
+ {stop, shutdown, State}
end;
handle_cast({ensure_monitoring, Pids}, State = #state { monitors = Mons }) ->
diff --git a/src/rabbit_mirror_queue_master.erl b/src/rabbit_mirror_queue_master.erl
index 4cc6856442..6ab9a875c2 100644
--- a/src/rabbit_mirror_queue_master.erl
+++ b/src/rabbit_mirror_queue_master.erl
@@ -34,7 +34,8 @@
-behaviour(rabbit_backing_queue).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-record(state, { name,
gm,
@@ -61,18 +62,6 @@
confirmed :: [rabbit_guid:guid()],
known_senders :: sets:set()
}.
--spec promote_backing_queue_state
- (rabbit_amqqueue:name(), pid(), atom(), any(), pid(), [any()],
- map(), [pid()]) ->
- master_state().
-
--spec sender_death_fun() -> death_fun().
--spec depth_fun() -> depth_fun().
--spec init_with_existing_bq(rabbit_types:amqqueue(), atom(), any()) ->
- master_state().
--spec stop_mirroring(master_state()) -> {atom(), any()}.
--spec sync_mirrors(stats_fun(), stats_fun(), master_state()) ->
- {'ok', master_state()} | {stop, any(), master_state()}.
%% For general documentation of HA design, see
%% rabbit_mirror_queue_coordinator
@@ -100,45 +89,56 @@ init(Q, Recover, AsyncCallback) ->
ok = gm:broadcast(GM, {depth, BQ:depth(BQS)}),
State.
-init_with_existing_bq(Q = #amqqueue{name = QName}, BQ, BQS) ->
+-spec init_with_existing_bq(amqqueue:amqqueue(), atom(), any()) ->
+ master_state().
+
+init_with_existing_bq(Q0, BQ, BQS) when ?is_amqqueue(Q0) ->
+ QName = amqqueue:get_name(Q0),
case rabbit_mirror_queue_coordinator:start_link(
- Q, undefined, sender_death_fun(), depth_fun()) of
- {ok, CPid} ->
- GM = rabbit_mirror_queue_coordinator:get_gm(CPid),
- Self = self(),
- ok = rabbit_misc:execute_mnesia_transaction(
- fun () ->
- [Q1 = #amqqueue{gm_pids = GMPids}]
- = mnesia:read({rabbit_queue, QName}),
- ok = rabbit_amqqueue:store_queue(
- Q1#amqqueue{gm_pids = [{GM, Self} | GMPids],
- state = live})
- end),
- {_MNode, SNodes} = rabbit_mirror_queue_misc:suggested_queue_nodes(Q),
- %% We need synchronous add here (i.e. do not return until the
- %% slave is running) so that when queue declaration is finished
- %% all slaves are up; we don't want to end up with unsynced slaves
- %% just by declaring a new queue. But add can't be synchronous all
- %% the time as it can be called by slaves and that's
- %% deadlock-prone.
- rabbit_mirror_queue_misc:add_mirrors(QName, SNodes, sync),
- #state { name = QName,
- gm = GM,
- coordinator = CPid,
- backing_queue = BQ,
- backing_queue_state = BQS,
- seen_status = #{},
- confirmed = [],
- known_senders = sets:new(),
- wait_timeout = rabbit_misc:get_env(rabbit, slave_wait_timeout, 15000) };
- {error, Reason} ->
- %% The GM can shutdown before the coordinator has started up
- %% (lost membership or missing group), thus the start_link of
- %% the coordinator returns {error, shutdown} as rabbit_amqqueue_process
- % is trapping exists
- throw({coordinator_not_started, Reason})
+ Q0, undefined, sender_death_fun(), depth_fun()) of
+ {ok, CPid} ->
+ GM = rabbit_mirror_queue_coordinator:get_gm(CPid),
+ Self = self(),
+ Fun = fun () ->
+ [Q1] = mnesia:read({rabbit_queue, QName}),
+ true = amqqueue:is_amqqueue(Q1),
+ GMPids0 = amqqueue:get_gm_pids(Q1),
+ GMPids1 = [{GM, Self} | GMPids0],
+ Q2 = amqqueue:set_gm_pids(Q1, GMPids1),
+ Q3 = amqqueue:set_state(Q2, live),
+ %% amqqueue migration:
+ %% The amqqueue was read from this transaction, no
+ %% need to handle migration.
+ ok = rabbit_amqqueue:store_queue(Q3)
+ end,
+ ok = rabbit_misc:execute_mnesia_transaction(Fun),
+ {_MNode, SNodes} = rabbit_mirror_queue_misc:suggested_queue_nodes(Q0),
+ %% We need synchronous add here (i.e. do not return until the
+ %% slave is running) so that when queue declaration is finished
+ %% all slaves are up; we don't want to end up with unsynced slaves
+ %% just by declaring a new queue. But add can't be synchronous all
+ %% the time as it can be called by slaves and that's
+ %% deadlock-prone.
+ rabbit_mirror_queue_misc:add_mirrors(QName, SNodes, sync),
+ #state{name = QName,
+ gm = GM,
+ coordinator = CPid,
+ backing_queue = BQ,
+ backing_queue_state = BQS,
+ seen_status = #{},
+ confirmed = [],
+ known_senders = sets:new(),
+ wait_timeout = rabbit_misc:get_env(rabbit, slave_wait_timeout, 15000)};
+ {error, Reason} ->
+ %% The GM can shutdown before the coordinator has started up
+ %% (lost membership or missing group), thus the start_link of
+ %% the coordinator returns {error, shutdown} as rabbit_amqqueue_process
+ % is trapping exists
+ throw({coordinator_not_started, Reason})
end.
+-spec stop_mirroring(master_state()) -> {atom(), any()}.
+
stop_mirroring(State = #state { coordinator = CPid,
backing_queue = BQ,
backing_queue_state = BQS }) ->
@@ -146,6 +146,9 @@ stop_mirroring(State = #state { coordinator = CPid,
stop_all_slaves(shutdown, State),
{BQ, BQS}.
+-spec sync_mirrors(stats_fun(), stats_fun(), master_state()) ->
+ {'ok', master_state()} | {stop, any(), master_state()}.
+
sync_mirrors(HandleInfo, EmitStats,
State = #state { name = QName,
gm = GM,
@@ -156,7 +159,8 @@ sync_mirrors(HandleInfo, EmitStats,
QName, "Synchronising: " ++ Fmt ++ "~n", Params)
end,
Log("~p messages to synchronise", [BQ:len(BQS)]),
- {ok, #amqqueue{slave_pids = SPids} = Q} = rabbit_amqqueue:lookup(QName),
+ {ok, Q} = rabbit_amqqueue:lookup(QName),
+ SPids = amqqueue:get_slave_pids(Q),
SyncBatchSize = rabbit_mirror_queue_misc:sync_batch_size(Q),
Log("batch size: ~p", [SyncBatchSize]),
Ref = make_ref(),
@@ -193,8 +197,8 @@ terminate(Reason,
%% Backing queue termination. The queue is going down but
%% shouldn't be deleted. Most likely safe shutdown of this
%% node.
- {ok, Q = #amqqueue{sync_slave_pids = SSPids}} =
- rabbit_amqqueue:lookup(QName),
+ {ok, Q} = rabbit_amqqueue:lookup(QName),
+ SSPids = amqqueue:get_sync_slave_pids(Q),
case SSPids =:= [] andalso
rabbit_policy:get(<<"ha-promote-on-shutdown">>, Q) =/= <<"always">> of
true -> %% Remove the whole queue to avoid data loss
@@ -213,7 +217,8 @@ delete_and_terminate(Reason, State = #state { backing_queue = BQ,
State#state{backing_queue_state = BQ:delete_and_terminate(Reason, BQS)}.
stop_all_slaves(Reason, #state{name = QName, gm = GM, wait_timeout = WT}) ->
- {ok, #amqqueue{slave_pids = SPids}} = rabbit_amqqueue:lookup(QName),
+ {ok, Q} = rabbit_amqqueue:lookup(QName),
+ SPids = amqqueue:get_slave_pids(Q),
rabbit_mirror_queue_misc:stop_all_slaves(Reason, SPids, QName, GM, WT).
purge(State = #state { gm = GM,
@@ -497,6 +502,11 @@ zip_msgs_and_acks(Msgs, AckTags, Accumulator,
%% Other exported functions
%% ---------------------------------------------------------------------------
+-spec promote_backing_queue_state
+ (rabbit_amqqueue:name(), pid(), atom(), any(), pid(), [any()],
+ map(), [pid()]) ->
+ master_state().
+
promote_backing_queue_state(QName, CPid, BQ, BQS, GM, AckTags, Seen, KS) ->
{_MsgIds, BQS1} = BQ:requeue(AckTags, BQS),
Len = BQ:len(BQS1),
@@ -514,6 +524,8 @@ promote_backing_queue_state(QName, CPid, BQ, BQS, GM, AckTags, Seen, KS) ->
known_senders = sets:from_list(KS),
wait_timeout = WaitTimeout }.
+-spec sender_death_fun() -> death_fun().
+
sender_death_fun() ->
Self = self(),
fun (DeadPid) ->
@@ -526,6 +538,8 @@ sender_death_fun() ->
end)
end.
+-spec depth_fun() -> depth_fun().
+
depth_fun() ->
Self = self(),
fun () ->
diff --git a/src/rabbit_mirror_queue_misc.erl b/src/rabbit_mirror_queue_misc.erl
index ac933d4df4..88aef8fcdc 100644
--- a/src/rabbit_mirror_queue_misc.erl
+++ b/src/rabbit_mirror_queue_misc.erl
@@ -31,7 +31,8 @@
%% for testing only
-export([module/1]).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-define(HA_NODES_MODULE, rabbit_mirror_queue_mode_nodes).
@@ -56,29 +57,12 @@
%%----------------------------------------------------------------------------
+%% Returns {ok, NewMPid, DeadPids, ExtraNodes}
+
-spec remove_from_queue
(rabbit_amqqueue:name(), pid(), [pid()]) ->
{'ok', pid(), [pid()], [node()]} | {'error', 'not_found'}.
--spec add_mirrors(rabbit_amqqueue:name(), [node()], 'sync' | 'async') ->
- 'ok'.
--spec store_updated_slaves(rabbit_types:amqqueue()) ->
- rabbit_types:amqqueue().
--spec initial_queue_node(rabbit_types:amqqueue(), node()) -> node().
--spec suggested_queue_nodes(rabbit_types:amqqueue()) ->
- {node(), [node()]}.
--spec is_mirrored(rabbit_types:amqqueue()) -> boolean().
--spec update_mirrors
- (rabbit_types:amqqueue(), rabbit_types:amqqueue()) -> 'ok'.
--spec update_mirrors
- (rabbit_types:amqqueue()) -> 'ok'.
--spec maybe_drop_master_after_sync(rabbit_types:amqqueue()) -> 'ok'.
--spec maybe_auto_sync(rabbit_types:amqqueue()) -> 'ok'.
--spec log_info(rabbit_amqqueue:name(), string(), [any()]) -> 'ok'.
--spec log_warning(rabbit_amqqueue:name(), string(), [any()]) -> 'ok'.
-
-%%----------------------------------------------------------------------------
-%% Returns {ok, NewMPid, DeadPids, ExtraNodes}
remove_from_queue(QueueName, Self, DeadGMPids) ->
rabbit_misc:execute_mnesia_transaction(
fun () ->
@@ -86,10 +70,11 @@ remove_from_queue(QueueName, Self, DeadGMPids) ->
%% get here. Or, gm group could've altered. see rabbitmq-server#914
case mnesia:read({rabbit_queue, QueueName}) of
[] -> {error, not_found};
- [Q = #amqqueue { pid = QPid,
- slave_pids = SPids,
- sync_slave_pids = SyncSPids,
- gm_pids = GMPids }] ->
+ [Q0] when ?is_amqqueue(Q0) ->
+ QPid = amqqueue:get_pid(Q0),
+ SPids = amqqueue:get_slave_pids(Q0),
+ SyncSPids = amqqueue:get_sync_slave_pids(Q0),
+ GMPids = amqqueue:get_gm_pids(Q0),
{DeadGM, AliveGM} = lists:partition(
fun ({GM, _}) ->
lists:member(GM, DeadGMPids)
@@ -109,7 +94,7 @@ remove_from_queue(QueueName, Self, DeadGMPids) ->
_ -> promote_slave(Alive)
end,
DoNotPromote = SyncSPids =:= [] andalso
- rabbit_policy:get(<<"ha-promote-on-failure">>, Q) =:= <<"when-synced">>,
+ rabbit_policy:get(<<"ha-promote-on-failure">>, Q0) =:= <<"when-synced">>,
case {{QPid, SPids}, {QPid1, SPids1}} of
{Same, Same} ->
{ok, QPid1, DeadPids, []};
@@ -124,23 +109,23 @@ remove_from_queue(QueueName, Self, DeadGMPids) ->
%% we're ok to update mnesia; or we have
%% become the master. If gm altered,
%% we have no choice but to proceed.
- Q1 = Q#amqqueue{pid = QPid1,
- slave_pids = SPids1,
- gm_pids = AliveGM},
- store_updated_slaves(Q1),
+ Q1 = amqqueue:set_pid(Q0, QPid1),
+ Q2 = amqqueue:set_slave_pids(Q1, SPids1),
+ Q3 = amqqueue:set_gm_pids(Q2, AliveGM),
+ store_updated_slaves(Q3),
%% If we add and remove nodes at the
%% same time we might tell the old
%% master we need to sync and then
%% shut it down. So let's check if
%% the new master needs to sync.
- maybe_auto_sync(Q1),
- {ok, QPid1, DeadPids, slaves_to_start_on_failure(Q1, DeadGMPids)};
+ maybe_auto_sync(Q3),
+ {ok, QPid1, DeadPids, slaves_to_start_on_failure(Q3, DeadGMPids)};
_ ->
%% Master has changed, and we're not it.
%% [1].
- Q1 = Q#amqqueue{slave_pids = Alive,
- gm_pids = AliveGM},
- store_updated_slaves(Q1),
+ Q1 = amqqueue:set_slave_pids(Q0, Alive),
+ Q2 = amqqueue:set_gm_pids(Q1, AliveGM),
+ store_updated_slaves(Q2),
{ok, QPid1, DeadPids, []}
end
end
@@ -185,13 +170,12 @@ on_vhost_up(VHost) ->
fun () ->
mnesia:foldl(
fun
- (#amqqueue{name = #resource{virtual_host = OtherVhost}},
- QNames0) when OtherVhost =/= VHost ->
+ (Q, QNames0) when not ?amqqueue_vhost_equals(Q, VHost) ->
QNames0;
- (Q = #amqqueue{name = QName,
- pid = Pid,
- slave_pids = SPids,
- type = classic}, QNames0) ->
+ (Q, QNames0) when ?amqqueue_is_classic(Q) ->
+ QName = amqqueue:get_name(Q),
+ Pid = amqqueue:get_pid(Q),
+ SPids = amqqueue:get_slave_pids(Q),
%% We don't want to pass in the whole
%% cluster - we don't want a situation
%% where starting one node causes us to
@@ -221,7 +205,10 @@ drop_mirrors(QName, Nodes) ->
drop_mirror(QName, MirrorNode) ->
case rabbit_amqqueue:lookup(QName) of
- {ok, #amqqueue { name = Name, pid = QPid, slave_pids = SPids }} ->
+ {ok, Q} when ?is_amqqueue(Q) ->
+ Name = amqqueue:get_name(Q),
+ QPid = amqqueue:get_pid(Q),
+ SPids = amqqueue:get_slave_pids(Q),
case [Pid || Pid <- [QPid | SPids], node(Pid) =:= MirrorNode] of
[] ->
{error, {queue_not_mirrored_on_node, MirrorNode}};
@@ -237,6 +224,9 @@ drop_mirror(QName, MirrorNode) ->
E
end.
+-spec add_mirrors(rabbit_amqqueue:name(), [node()], 'sync' | 'async') ->
+ 'ok'.
+
add_mirrors(QName, Nodes, SyncMode) ->
[add_mirror(QName, Node, SyncMode) || Node <- Nodes],
ok.
@@ -247,7 +237,7 @@ add_mirror(QName, MirrorNode, SyncMode) ->
rabbit_misc:with_exit_handler(
rabbit_misc:const(ok),
fun () ->
- #amqqueue{name = #resource{virtual_host = VHost}} = Q,
+ #resource{virtual_host = VHost} = amqqueue:get_name(Q),
case rabbit_vhost_sup_sup:get_vhost_sup(VHost, MirrorNode) of
{ok, _} ->
SPid = rabbit_amqqueue_sup_sup:start_queue_process(
@@ -278,26 +268,39 @@ report_deaths(MirrorPid, IsMaster, QueueName, DeadPids) ->
rabbit_misc:pid_to_string(MirrorPid),
[[$ , rabbit_misc:pid_to_string(P)] || P <- DeadPids]]).
+-spec log_info(rabbit_amqqueue:name(), string(), [any()]) -> 'ok'.
+
log_info (QName, Fmt, Args) ->
rabbit_log_mirroring:info("Mirrored ~s: " ++ Fmt,
[rabbit_misc:rs(QName) | Args]).
+
+-spec log_warning(rabbit_amqqueue:name(), string(), [any()]) -> 'ok'.
+
log_warning(QName, Fmt, Args) ->
rabbit_log_mirroring:warning("Mirrored ~s: " ++ Fmt,
[rabbit_misc:rs(QName) | Args]).
-store_updated_slaves(Q = #amqqueue{slave_pids = SPids,
- sync_slave_pids = SSPids,
- recoverable_slaves = RS}) ->
+-spec store_updated_slaves(amqqueue:amqqueue()) ->
+ amqqueue:amqqueue().
+
+store_updated_slaves(Q0) when ?is_amqqueue(Q0) ->
+ SPids = amqqueue:get_slave_pids(Q0),
+ SSPids = amqqueue:get_sync_slave_pids(Q0),
+ RS0 = amqqueue:get_recoverable_slaves(Q0),
%% TODO now that we clear sync_slave_pids in rabbit_durable_queue,
%% do we still need this filtering?
SSPids1 = [SSPid || SSPid <- SSPids, lists:member(SSPid, SPids)],
- Q1 = Q#amqqueue{sync_slave_pids = SSPids1,
- recoverable_slaves = update_recoverable(SPids, RS),
- state = live},
- ok = rabbit_amqqueue:store_queue(Q1),
+ Q1 = amqqueue:set_sync_slave_pids(Q0, SSPids1),
+ RS1 = update_recoverable(SPids, RS0),
+ Q2 = amqqueue:set_recoverable_slaves(Q1, RS1),
+ Q3 = amqqueue:set_state(Q2, live),
+ %% amqqueue migration:
+ %% The amqqueue was read from this transaction, no need to handle
+ %% migration.
+ ok = rabbit_amqqueue:store_queue(Q3),
%% Wake it up so that we emit a stats event
- rabbit_amqqueue:notify_policy_changed(Q1),
- Q1.
+ rabbit_amqqueue:notify_policy_changed(Q3),
+ Q3.
%% Recoverable nodes are those which we could promote if the whole
%% cluster were to suddenly stop and we then lose the master; i.e. all
@@ -346,13 +349,14 @@ stop_all_slaves(Reason, SPids, QName, GM, WaitTimeout) ->
%% notice and update Mnesia. But we just removed them all, and
%% have stopped listening ourselves. So manually clean up.
rabbit_misc:execute_mnesia_transaction(fun () ->
- [Q] = mnesia:read({rabbit_queue, QName}),
- rabbit_mirror_queue_misc:store_updated_slaves(
- Q #amqqueue { gm_pids = [], slave_pids = [],
- %% Restarted slaves on running nodes can
- %% ensure old incarnations are stopped using
- %% the pending slave pids.
- slave_pids_pending_shutdown = PendingSlavePids})
+ [Q0] = mnesia:read({rabbit_queue, QName}),
+ Q1 = amqqueue:set_gm_pids(Q0, []),
+ Q2 = amqqueue:set_slave_pids(Q1, []),
+ %% Restarted slaves on running nodes can
+ %% ensure old incarnations are stopped using
+ %% the pending slave pids.
+ Q3 = amqqueue:set_slave_pids_pending_shutdown(Q2, PendingSlavePids),
+ rabbit_mirror_queue_misc:store_updated_slaves(Q3)
end),
ok = gm:forget_group(QName).
@@ -363,17 +367,23 @@ promote_slave([SPid | SPids]) ->
%% the one to promote is the oldest.
{SPid, SPids}.
+-spec initial_queue_node(amqqueue:amqqueue(), node()) -> node().
+
initial_queue_node(Q, DefNode) ->
{MNode, _SNodes} = suggested_queue_nodes(Q, DefNode, rabbit_nodes:all_running()),
MNode.
+-spec suggested_queue_nodes(amqqueue:amqqueue()) ->
+ {node(), [node()]}.
+
suggested_queue_nodes(Q) -> suggested_queue_nodes(Q, rabbit_nodes:all_running()).
suggested_queue_nodes(Q, All) -> suggested_queue_nodes(Q, node(), All).
%% The third argument exists so we can pull a call to
%% rabbit_mnesia:cluster_nodes(running) out of a loop or transaction
%% or both.
-suggested_queue_nodes(Q = #amqqueue{exclusive_owner = Owner}, DefNode, All) ->
+suggested_queue_nodes(Q, DefNode, All) when ?is_amqqueue(Q) ->
+ Owner = amqqueue:get_exclusive_owner(Q),
{MNode0, SNodes, SSNodes} = actual_queue_nodes(Q),
MNode = case MNode0 of
none -> DefNode;
@@ -395,7 +405,7 @@ policy(Policy, Q) ->
P -> P
end.
-module(#amqqueue{} = Q) ->
+module(Q) when ?is_amqqueue(Q) ->
case rabbit_policy:get(<<"ha-mode">>, Q) of
undefined -> not_mirrored;
Mode -> module(Mode)
@@ -418,6 +428,8 @@ validate_mode(Mode) ->
{error, "~p is not a valid ha-mode value", [Mode]}
end.
+-spec is_mirrored(amqqueue:amqqueue()) -> boolean().
+
is_mirrored(Q) ->
case module(Q) of
{ok, _} -> true;
@@ -430,16 +442,20 @@ is_mirrored_ha_nodes(Q) ->
_ -> false
end.
-actual_queue_nodes(#amqqueue{pid = MPid,
- slave_pids = SPids,
- sync_slave_pids = SSPids}) ->
+actual_queue_nodes(Q) when ?is_amqqueue(Q) ->
+ MPid = amqqueue:get_pid(Q),
+ SPids = amqqueue:get_slave_pids(Q),
+ SSPids = amqqueue:get_sync_slave_pids(Q),
Nodes = fun (L) -> [node(Pid) || Pid <- L] end,
{case MPid of
none -> none;
_ -> node(MPid)
end, Nodes(SPids), Nodes(SSPids)}.
-maybe_auto_sync(Q = #amqqueue{pid = QPid}) ->
+-spec maybe_auto_sync(amqqueue:amqqueue()) -> 'ok'.
+
+maybe_auto_sync(Q) when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
case policy(<<"ha-sync-mode">>, Q) of
<<"automatic">> ->
spawn(fun() -> rabbit_amqqueue:sync_mirrors(QPid) end);
@@ -447,23 +463,27 @@ maybe_auto_sync(Q = #amqqueue{pid = QPid}) ->
ok
end.
-sync_queue(Q) ->
- rabbit_amqqueue:with(
- Q, fun(#amqqueue{pid = QPid, type = classic}) ->
- rabbit_amqqueue:sync_mirrors(QPid);
- (#amqqueue{type = quorum}) ->
- {error, quorum_queue_not_supported}
- end).
-
-cancel_sync_queue(Q) ->
- rabbit_amqqueue:with(
- Q, fun(#amqqueue{pid = QPid, type = classic}) ->
- rabbit_amqqueue:cancel_sync_mirrors(QPid);
- (#amqqueue{type = quorum}) ->
- {error, quorum_queue_not_supported}
- end).
-
-sync_batch_size(#amqqueue{} = Q) ->
+sync_queue(Q0) ->
+ F = fun
+ (Q) when ?amqqueue_is_classic(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ rabbit_amqqueue:sync_mirrors(QPid);
+ (Q) when ?amqqueue_is_quorum(Q) ->
+ {error, quorum_queue_not_supported}
+ end,
+ rabbit_amqqueue:with(Q0, F).
+
+cancel_sync_queue(Q0) ->
+ F = fun
+ (Q) when ?amqqueue_is_classic(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ rabbit_amqqueue:cancel_sync_mirrors(QPid);
+ (Q) when ?amqqueue_is_quorum(Q) ->
+ {error, quorum_queue_not_supported}
+ end,
+ rabbit_amqqueue:with(Q0, F).
+
+sync_batch_size(Q) when ?is_amqqueue(Q) ->
case policy(<<"ha-sync-batch-size">>, Q) of
none -> %% we need this case because none > 1 == true
default_batch_size();
@@ -479,14 +499,23 @@ default_batch_size() ->
rabbit_misc:get_env(rabbit, mirroring_sync_batch_size,
?DEFAULT_BATCH_SIZE).
-update_mirrors(OldQ = #amqqueue{pid = QPid},
- NewQ = #amqqueue{pid = QPid}) ->
+-spec update_mirrors
+ (amqqueue:amqqueue(), amqqueue:amqqueue()) -> 'ok'.
+
+update_mirrors(OldQ, NewQ) when ?amqqueue_pids_are_equal(OldQ, NewQ) ->
+ % Note: we do want to ensure both queues have same pid
+ QPid = amqqueue:get_pid(OldQ),
+ QPid = amqqueue:get_pid(NewQ),
case {is_mirrored(OldQ), is_mirrored(NewQ)} of
{false, false} -> ok;
_ -> rabbit_amqqueue:update_mirroring(QPid)
end.
-update_mirrors(Q = #amqqueue{name = QName}) ->
+-spec update_mirrors
+ (amqqueue:amqqueue()) -> 'ok'.
+
+update_mirrors(Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
{OldMNode, OldSNodes, _} = actual_queue_nodes(Q),
{NewMNode, NewSNodes} = suggested_queue_nodes(Q),
OldNodes = [OldMNode | OldSNodes],
@@ -512,8 +541,12 @@ update_mirrors(Q = #amqqueue{name = QName}) ->
%% We don't just call update_mirrors/2 here since that could decide to
%% start a slave for some other reason, and since we are the slave ATM
%% that allows complicated deadlocks.
-maybe_drop_master_after_sync(Q = #amqqueue{name = QName,
- pid = MPid}) ->
+
+-spec maybe_drop_master_after_sync(amqqueue:amqqueue()) -> 'ok'.
+
+maybe_drop_master_after_sync(Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
+ MPid = amqqueue:get_pid(Q),
{DesiredMNode, DesiredSNodes} = suggested_queue_nodes(Q),
case node(MPid) of
DesiredMNode -> ok;
diff --git a/src/rabbit_mirror_queue_slave.erl b/src/rabbit_mirror_queue_slave.erl
index bf0a5a7ed0..3697051960 100644
--- a/src/rabbit_mirror_queue_slave.erl
+++ b/src/rabbit_mirror_queue_slave.erl
@@ -35,8 +35,9 @@
-behaviour(gen_server2).
-behaviour(gm).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-include("gm_specs.hrl").
%%----------------------------------------------------------------------------
@@ -76,8 +77,9 @@ set_maximum_since_use(QPid, Age) ->
info(QPid) -> gen_server2:call(QPid, info, infinity).
-init(Q) ->
- ?store_proc_name(Q#amqqueue.name),
+init(Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
+ ?store_proc_name(QName),
{ok, {not_started, Q}, hibernate,
{backoff, ?HIBERNATE_AFTER_MIN, ?HIBERNATE_AFTER_MIN,
?DESIRED_HIBERNATE}, ?MODULE}.
@@ -85,7 +87,8 @@ init(Q) ->
go(SPid, sync) -> gen_server2:call(SPid, go, infinity);
go(SPid, async) -> gen_server2:cast(SPid, go).
-handle_go(Q = #amqqueue{name = QName}) ->
+handle_go(Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
%% We join the GM group before we add ourselves to the amqqueue
%% record. As a result:
%% 1. We can receive msgs from GM that correspond to messages we will
@@ -119,10 +122,10 @@ handle_go(Q = #amqqueue{name = QName}) ->
ok = rabbit_memory_monitor:register(
Self, {rabbit_amqqueue, set_ram_duration_target, [Self]}),
{ok, BQ} = application:get_env(backing_queue_module),
- Q1 = Q #amqqueue { pid = QPid },
+ QPid = amqqueue:get_pid(Q),
_ = BQ:delete_crashed(Q), %% For crash recovery
- BQS = bq_init(BQ, Q1, new),
- State = #state { q = Q1,
+ BQS = bq_init(BQ, Q, new),
+ State = #state { q = Q,
gm = GM,
backing_queue = BQ,
backing_queue_state = BQS,
@@ -139,7 +142,7 @@ handle_go(Q = #amqqueue{name = QName}) ->
},
ok = gm:broadcast(GM, request_depth),
ok = gm:validate_members(GM, [GM | [G || {G, _} <- GMPids]]),
- rabbit_mirror_queue_misc:maybe_auto_sync(Q1),
+ rabbit_mirror_queue_misc:maybe_auto_sync(Q),
{ok, State};
{stale, StalePid} ->
rabbit_mirror_queue_misc:log_warning(
@@ -163,8 +166,11 @@ handle_go(Q = #amqqueue{name = QName}) ->
init_it(Self, GM, Node, QName) ->
case mnesia:read({rabbit_queue, QName}) of
- [Q = #amqqueue { pid = QPid, slave_pids = SPids, gm_pids = GMPids,
- slave_pids_pending_shutdown = PSPids}] ->
+ [Q] when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
+ SPids = amqqueue:get_slave_pids(Q),
+ GMPids = amqqueue:get_gm_pids(Q),
+ PSPids = amqqueue:get_slave_pids_pending_shutdown(Q),
case [Pid || Pid <- [QPid | SPids], node(Pid) =:= Node] of
[] -> stop_pending_slaves(QName, PSPids),
add_slave(Q, Self, GM),
@@ -175,12 +181,11 @@ init_it(Self, GM, Node, QName) ->
end;
[SPid] -> case rabbit_mnesia:is_process_alive(SPid) of
true -> existing;
- false -> GMPids1 = [T || T = {_, S} <- GMPids,
- S =/= SPid],
- Q1 = Q#amqqueue{
- slave_pids = SPids -- [SPid],
- gm_pids = GMPids1},
- add_slave(Q1, Self, GM),
+ false -> GMPids1 = [T || T = {_, S} <- GMPids, S =/= SPid],
+ SPids1 = SPids -- [SPid],
+ Q1 = amqqueue:set_slave_pids(Q, SPids1),
+ Q2 = amqqueue:set_gm_pids(Q1, GMPids1),
+ add_slave(Q2, Self, GM),
{new, QPid, GMPids1}
end
end;
@@ -212,9 +217,14 @@ stop_pending_slaves(QName, Pids) ->
%% Add to the end, so they are in descending order of age, see
%% rabbit_mirror_queue_misc:promote_slave/1
-add_slave(Q = #amqqueue { slave_pids = SPids, gm_pids = GMPids }, New, GM) ->
- rabbit_mirror_queue_misc:store_updated_slaves(
- Q#amqqueue{slave_pids = SPids ++ [New], gm_pids = [{GM, New} | GMPids]}).
+add_slave(Q0, New, GM) when ?is_amqqueue(Q0) ->
+ SPids = amqqueue:get_slave_pids(Q0),
+ GMPids = amqqueue:get_gm_pids(Q0),
+ SPids1 = SPids ++ [New],
+ GMPids1 = [{GM, New} | GMPids],
+ Q1 = amqqueue:set_slave_pids(Q0, SPids1),
+ Q2 = amqqueue:set_gm_pids(Q1, GMPids1),
+ rabbit_mirror_queue_misc:store_updated_slaves(Q2).
handle_call(go, _From, {not_started, Q} = NotStarted) ->
case handle_go(Q) of
@@ -223,10 +233,11 @@ handle_call(go, _From, {not_started, Q} = NotStarted) ->
end;
handle_call({gm_deaths, DeadGMPids}, From,
- State = #state{ gm = GM,
- q = Q = #amqqueue{ name = QName, pid = MPid },
- backing_queue = BQ,
- backing_queue_state = BQS}) ->
+ State = #state{ gm = GM, q = Q,
+ backing_queue = BQ,
+ backing_queue_state = BQS}) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
+ MPid = amqqueue:get_pid(Q),
Self = self(),
case rabbit_mirror_queue_misc:remove_from_queue(QName, Self, DeadGMPids) of
{error, not_found} ->
@@ -269,7 +280,9 @@ handle_call({gm_deaths, DeadGMPids}, From,
%% death. That is all process_death does, create
%% some traffic.
ok = gm:broadcast(GM, process_death),
- noreply(State #state { q = Q #amqqueue { pid = Pid } })
+ Q1 = amqqueue:set_pid(Q, Pid),
+ State1 = State#state{q = Q1},
+ noreply(State1)
end
end;
@@ -285,20 +298,22 @@ handle_cast(go, {not_started, Q} = NotStarted) ->
handle_cast({run_backing_queue, Mod, Fun}, State) ->
noreply(run_backing_queue(Mod, Fun, State));
-handle_cast({gm, Instruction}, State = #state{q = #amqqueue { name = QName }}) ->
+handle_cast({gm, Instruction}, State = #state{q = Q0}) when ?is_amqqueue(Q0) ->
+ QName = amqqueue:get_name(Q0),
case rabbit_amqqueue:lookup(QName) of
- {ok, #amqqueue{slave_pids = SPids}} ->
- case lists:member(self(), SPids) of
- true ->
- handle_process_result(process_instruction(Instruction, State));
- false ->
- %% Potentially a duplicated slave caused by a partial partition,
- %% will stop as a new slave could start unaware of our presence
- {stop, shutdown, State}
- end;
- {error, not_found} ->
- %% Would not expect this to happen after fixing #953
- {stop, shutdown, State}
+ {ok, Q1} when ?is_amqqueue(Q1) ->
+ SPids = amqqueue:get_slave_pids(Q1),
+ case lists:member(self(), SPids) of
+ true ->
+ handle_process_result(process_instruction(Instruction, State));
+ false ->
+ %% Potentially a duplicated slave caused by a partial partition,
+ %% will stop as a new slave could start unaware of our presence
+ {stop, shutdown, State}
+ end;
+ {error, not_found} ->
+ %% Would not expect this to happen after fixing #953
+ {stop, shutdown, State}
end;
handle_cast({deliver, Delivery = #delivery{sender = Sender, flow = Flow}, true},
@@ -521,11 +536,16 @@ handle_terminate([_SPid], _Reason) ->
infos(Items, State) -> [{Item, i(Item, State)} || Item <- Items].
-i(pid, _State) -> self();
-i(name, #state { q = #amqqueue { name = Name } }) -> Name;
-i(master_pid, #state { q = #amqqueue { pid = MPid } }) -> MPid;
-i(is_synchronised, #state { depth_delta = DD }) -> DD =:= 0;
-i(Item, _State) -> throw({bad_argument, Item}).
+i(pid, _State) ->
+ self();
+i(name, #state{q = Q}) when ?is_amqqueue(Q) ->
+ amqqueue:get_name(Q);
+i(master_pid, #state{q = Q}) when ?is_amqqueue(Q) ->
+ amqqueue:get_pid(Q);
+i(is_synchronised, #state{depth_delta = DD}) ->
+ DD =:= 0;
+i(Item, _State) ->
+ throw({bad_argument, Item}).
bq_init(BQ, Q, Recover) ->
Self = self(),
@@ -542,6 +562,18 @@ run_backing_queue(Mod, Fun, State = #state { backing_queue = BQ,
backing_queue_state = BQS }) ->
State #state { backing_queue_state = BQ:invoke(Mod, Fun, BQS) }.
+%% This feature was used by `rabbit_amqqueue_process` and
+%% `rabbit_mirror_queue_slave` up-to and including RabbitMQ 3.7.x. It is
+%% unused in 3.8.x and thus deprecated. We keep it to support in-place
+%% upgrades to 3.8.x (i.e. mixed-version clusters), but it is a no-op
+%% starting with that version.
+send_mandatory(#delivery{mandatory = false}) ->
+ ok;
+send_mandatory(#delivery{mandatory = true,
+ sender = SenderPid,
+ msg_seq_no = MsgSeqNo}) ->
+ gen_server2:cast(SenderPid, {mandatory_received, MsgSeqNo}).
+
send_or_record_confirm(_, #delivery{ confirm = false }, MS, _State) ->
MS;
send_or_record_confirm(published, #delivery { sender = ChPid,
@@ -550,7 +582,7 @@ send_or_record_confirm(published, #delivery { sender = ChPid,
message = #basic_message {
id = MsgId,
is_persistent = true } },
- MS, #state { q = #amqqueue { durable = true } }) ->
+ MS, #state{q = Q}) when ?amqqueue_is_durable(Q) ->
maps:put(MsgId, {published, ChPid, MsgSeqNo} , MS);
send_or_record_confirm(_Status, #delivery { sender = ChPid,
confirm = true,
@@ -595,7 +627,7 @@ handle_process_result({stop, State}) -> {stop, normal, State}.
-spec promote_me({pid(), term()}, #state{}) -> no_return().
-promote_me(From, #state { q = Q = #amqqueue { name = QName },
+promote_me(From, #state { q = Q0,
gm = GM,
backing_queue = BQ,
backing_queue_state = BQS,
@@ -603,13 +635,14 @@ promote_me(From, #state { q = Q = #amqqueue { name = QName },
sender_queues = SQ,
msg_id_ack = MA,
msg_id_status = MS,
- known_senders = KS }) ->
+ known_senders = KS}) when ?is_amqqueue(Q0) ->
+ QName = amqqueue:get_name(Q0),
rabbit_mirror_queue_misc:log_info(QName, "Promoting slave ~s to master~n",
[rabbit_misc:pid_to_string(self())]),
- Q1 = Q #amqqueue { pid = self() },
- {ok, CPid} = rabbit_mirror_queue_coordinator:start_link(
- Q1, GM, rabbit_mirror_queue_master:sender_death_fun(),
- rabbit_mirror_queue_master:depth_fun()),
+ Q1 = amqqueue:set_pid(Q0, self()),
+ DeathFun = rabbit_mirror_queue_master:sender_death_fun(),
+ DepthFun = rabbit_mirror_queue_master:depth_fun(),
+ {ok, CPid} = rabbit_mirror_queue_coordinator:start_link(Q1, GM, DeathFun, DepthFun),
true = unlink(GM),
gen_server2:reply(From, {promote, CPid}),
@@ -700,11 +733,13 @@ promote_me(From, #state { q = Q = #amqqueue { name = QName },
Q1, rabbit_mirror_queue_master, MasterState, RateTRef, Deliveries, KS1,
MTC).
-%% We need to send an ack for these messages since the channel is waiting
+%% We reset mandatory to false here because we will have sent the
+%% mandatory_received already as soon as we got the message. We also
+%% need to send an ack for these messages since the channel is waiting
%% for one for the via-GM case and we will not now receive one.
promote_delivery(Delivery = #delivery{sender = Sender, flow = Flow}) ->
maybe_flow_ack(Sender, Flow),
- Delivery.
+ Delivery#delivery{mandatory = false}.
noreply(State) ->
{NewState, Timeout} = next_state(State),
@@ -823,6 +858,7 @@ maybe_enqueue_message(
Delivery = #delivery { message = #basic_message { id = MsgId },
sender = ChPid },
State = #state { sender_queues = SQ, msg_id_status = MS }) ->
+ send_mandatory(Delivery), %% must do this before confirms
State1 = ensure_monitoring(ChPid, State),
%% We will never see {published, ChPid, MsgSeqNo} here.
case maps:find(MsgId, MS) of
@@ -1040,19 +1076,22 @@ update_ram_duration(BQ, BQS) ->
rabbit_memory_monitor:report_ram_duration(self(), RamDuration),
BQ:set_ram_duration_target(DesiredDuration, BQS1).
-record_synchronised(#amqqueue { name = QName }) ->
+record_synchronised(Q0) when ?is_amqqueue(Q0) ->
+ QName = amqqueue:get_name(Q0),
Self = self(),
- case rabbit_misc:execute_mnesia_transaction(
- fun () ->
- case mnesia:read({rabbit_queue, QName}) of
- [] ->
- ok;
- [Q1 = #amqqueue { sync_slave_pids = SSPids }] ->
- Q2 = Q1#amqqueue{sync_slave_pids = [Self | SSPids]},
- rabbit_mirror_queue_misc:store_updated_slaves(Q2),
- {ok, Q2}
- end
- end) of
- ok -> ok;
- {ok, Q} -> rabbit_mirror_queue_misc:maybe_drop_master_after_sync(Q)
+ F = fun () ->
+ case mnesia:read({rabbit_queue, QName}) of
+ [] ->
+ ok;
+ [Q1] when ?is_amqqueue(Q1) ->
+ SSPids = amqqueue:get_sync_slave_pids(Q1),
+ SSPids1 = [Self | SSPids],
+ Q2 = amqqueue:set_sync_slave_pids(Q1, SSPids1),
+ rabbit_mirror_queue_misc:store_updated_slaves(Q2),
+ {ok, Q2}
+ end
+ end,
+ case rabbit_misc:execute_mnesia_transaction(F) of
+ ok -> ok;
+ {ok, Q2} -> rabbit_mirror_queue_misc:maybe_drop_master_after_sync(Q2)
end.
diff --git a/src/rabbit_mirror_queue_sync.erl b/src/rabbit_mirror_queue_sync.erl
index d206650d0f..2d7ce7edb2 100644
--- a/src/rabbit_mirror_queue_sync.erl
+++ b/src/rabbit_mirror_queue_sync.erl
@@ -59,8 +59,19 @@
-type slave_sync_state() :: {[{rabbit_types:msg_id(), ack()}], timer:tref(),
bqs()}.
+%% ---------------------------------------------------------------------------
+%% Master
+
-spec master_prepare(reference(), rabbit_amqqueue:name(),
log_fun(), [pid()]) -> pid().
+
+master_prepare(Ref, QName, Log, SPids) ->
+ MPid = self(),
+ spawn_link(fun () ->
+ ?store_proc_name(QName),
+ syncer(Ref, Log, MPid, SPids)
+ end).
+
-spec master_go(pid(), reference(), log_fun(),
rabbit_mirror_queue_master:stats_fun(),
rabbit_mirror_queue_master:stats_fun(),
@@ -69,21 +80,6 @@
{'already_synced', bqs()} | {'ok', bqs()} |
{'shutdown', any(), bqs()} |
{'sync_died', any(), bqs()}.
--spec slave(non_neg_integer(), reference(), timer:tref(), pid(),
- bq(), bqs(), fun((bq(), bqs()) -> {timer:tref(), bqs()})) ->
- 'denied' |
- {'ok' | 'failed', slave_sync_state()} |
- {'stop', any(), slave_sync_state()}.
-
-%% ---------------------------------------------------------------------------
-%% Master
-
-master_prepare(Ref, QName, Log, SPids) ->
- MPid = self(),
- spawn_link(fun () ->
- ?store_proc_name(QName),
- syncer(Ref, Log, MPid, SPids)
- end).
master_go(Syncer, Ref, Log, HandleInfo, EmitStats, SyncBatchSize, BQ, BQS) ->
Args = {Syncer, Ref, Log, HandleInfo, EmitStats, rabbit_misc:get_parent()},
@@ -321,6 +317,12 @@ wait_for_resources(Ref, SPids) ->
%% ---------------------------------------------------------------------------
%% Slave
+-spec slave(non_neg_integer(), reference(), timer:tref(), pid(),
+ bq(), bqs(), fun((bq(), bqs()) -> {timer:tref(), bqs()})) ->
+ 'denied' |
+ {'ok' | 'failed', slave_sync_state()} |
+ {'stop', any(), slave_sync_state()}.
+
slave(0, Ref, _TRef, Syncer, _BQ, _BQS, _UpdateRamDuration) ->
Syncer ! {sync_deny, Ref, self()},
denied;
diff --git a/src/rabbit_mnesia.erl b/src/rabbit_mnesia.erl
index 4760a9432e..61b9e10e70 100644
--- a/src/rabbit_mnesia.erl
+++ b/src/rabbit_mnesia.erl
@@ -16,7 +16,8 @@
-module(rabbit_mnesia).
--export([init/0,
+-export([%% Main interface
+ init/0,
join_cluster/2,
reset/0,
force_reset/0,
@@ -25,6 +26,7 @@
forget_cluster_node/2,
force_load_next_boot/0,
+ %% Various queries to get the status of the db
status/0,
is_clustered/0,
on_running_node/1,
@@ -35,11 +37,13 @@
dir/0,
cluster_status_from_mnesia/0,
+ %% Operations on the db and utils, mainly used in `rabbit_upgrade' and `rabbit'
init_db_unchecked/2,
copy_db/1,
check_cluster_consistency/0,
ensure_mnesia_dir/0,
+ %% Hooks used in `rabbit_node_monitor'
on_node_up/1,
on_node_down/1
]).
@@ -61,45 +65,12 @@
-type node_type() :: disc | ram.
-type cluster_status() :: {[node()], [node()], [node()]}.
-%% Main interface
--spec init() -> 'ok'.
--spec join_cluster(node(), node_type())
- -> ok | {ok, already_member} | {error, {inconsistent_cluster, string()}}.
--spec reset() -> 'ok'.
--spec force_reset() -> 'ok'.
--spec update_cluster_nodes(node()) -> 'ok'.
--spec change_cluster_node_type(node_type()) -> 'ok'.
--spec forget_cluster_node(node(), boolean()) -> 'ok'.
--spec force_load_next_boot() -> 'ok'.
-
-%% Various queries to get the status of the db
--spec status() -> [{'nodes', [{node_type(), [node()]}]} |
- {'running_nodes', [node()]} |
- {'partitions', [{node(), [node()]}]}].
--spec is_clustered() -> boolean().
--spec on_running_node(pid()) -> boolean().
--spec is_process_alive(pid() | {atom(), node()}) -> boolean().
--spec is_registered_process_alive(atom()) -> boolean().
--spec cluster_nodes('all' | 'disc' | 'ram' | 'running') -> [node()].
--spec node_type() -> node_type().
--spec dir() -> file:filename().
--spec cluster_status_from_mnesia() -> rabbit_types:ok_or_error2(
- cluster_status(), any()).
-
-%% Operations on the db and utils, mainly used in `rabbit_upgrade' and `rabbit'
--spec init_db_unchecked([node()], node_type()) -> 'ok'.
--spec copy_db(file:filename()) -> rabbit_types:ok_or_error(any()).
--spec check_cluster_consistency() -> 'ok'.
--spec ensure_mnesia_dir() -> 'ok'.
-
-%% Hooks used in `rabbit_node_monitor'
--spec on_node_up(node()) -> 'ok'.
--spec on_node_down(node()) -> 'ok'.
-
%%----------------------------------------------------------------------------
%% Main interface
%%----------------------------------------------------------------------------
+-spec init() -> 'ok'.
+
init() ->
ensure_mnesia_running(),
ensure_mnesia_dir(),
@@ -165,20 +136,14 @@ init_from_config() ->
{DiscoveredNodes, NodeType} =
case rabbit_peer_discovery:discover_cluster_nodes() of
{ok, {Nodes, Type} = Config}
- when is_list(Nodes) andalso (Type == disc orelse Type == disk orelse Type == ram) ->
+ when is_list(Nodes) andalso
+ (Type == disc orelse Type == disk orelse Type == ram) ->
case lists:foldr(FindBadNodeNames, [], Nodes) of
[] -> Config;
BadNames -> e({invalid_cluster_node_names, BadNames})
end;
{ok, {_, BadType}} when BadType /= disc andalso BadType /= ram ->
e({invalid_cluster_node_type, BadType});
- {ok, Nodes} when is_list(Nodes) ->
- %% The legacy syntax (a nodes list without the node
- %% type) is unsupported.
- case lists:foldr(FindBadNodeNames, [], Nodes) of
- [] -> e(cluster_node_type_mandatory);
- _ -> e(invalid_cluster_nodes_conf)
- end;
{ok, _} ->
e(invalid_cluster_nodes_conf)
end,
@@ -228,6 +193,10 @@ join_discovered_peers(TryNodes, NodeType) ->
%% Note that we make no attempt to verify that the nodes provided are
%% all in the same cluster, we simply pick the first online node and
%% we cluster to its cluster.
+
+-spec join_cluster(node(), node_type())
+ -> ok | {ok, already_member} | {error, {inconsistent_cluster, string()}}.
+
join_cluster(DiscoveryNode, NodeType) ->
ensure_mnesia_not_running(),
ensure_mnesia_dir(),
@@ -275,11 +244,16 @@ join_cluster(DiscoveryNode, NodeType) ->
%% return node to its virgin state, where it is not member of any
%% cluster, has no cluster configuration, no local database, and no
%% persisted messages
+
+-spec reset() -> 'ok'.
+
reset() ->
ensure_mnesia_not_running(),
rabbit_log:info("Resetting Rabbit~n", []),
reset_gracefully().
+-spec force_reset() -> 'ok'.
+
force_reset() ->
ensure_mnesia_not_running(),
rabbit_log:info("Resetting Rabbit forcefully~n", []),
@@ -310,6 +284,8 @@ wipe() ->
ok = rabbit_node_monitor:reset_cluster_status(),
ok.
+-spec change_cluster_node_type(node_type()) -> 'ok'.
+
change_cluster_node_type(Type) ->
ensure_mnesia_not_running(),
ensure_mnesia_dir(),
@@ -327,6 +303,8 @@ change_cluster_node_type(Type) ->
ok = reset(),
ok = join_cluster(Node, Type).
+-spec update_cluster_nodes(node()) -> 'ok'.
+
update_cluster_nodes(DiscoveryNode) ->
ensure_mnesia_not_running(),
ensure_mnesia_dir(),
@@ -353,6 +331,9 @@ update_cluster_nodes(DiscoveryNode) ->
%% * This node was, at the best of our knowledge (see comment below)
%% the last or second to last after the node we're removing to go
%% down
+
+-spec forget_cluster_node(node(), boolean()) -> 'ok'.
+
forget_cluster_node(Node, RemoveWhenOffline) ->
forget_cluster_node(Node, RemoveWhenOffline, true).
@@ -407,6 +388,10 @@ remove_node_offline_node(Node) ->
%% Queries
%%----------------------------------------------------------------------------
+-spec status() -> [{'nodes', [{node_type(), [node()]}]} |
+ {'running_nodes', [node()]} |
+ {'partitions', [{node(), [node()]}]}].
+
status() ->
IfNonEmpty = fun (_, []) -> [];
(Type, Nodes) -> [{Type, Nodes}]
@@ -427,15 +412,22 @@ mnesia_partitions(Nodes) ->
is_running() -> mnesia:system_info(is_running) =:= yes.
+-spec is_clustered() -> boolean().
+
is_clustered() -> AllNodes = cluster_nodes(all),
AllNodes =/= [] andalso AllNodes =/= [node()].
+-spec on_running_node(pid()) -> boolean().
+
on_running_node(Pid) -> lists:member(node(Pid), cluster_nodes(running)).
%% This requires the process be in the same running cluster as us
%% (i.e. not partitioned or some random node).
%%
%% See also rabbit_misc:is_process_alive/1 which does not.
+
+-spec is_process_alive(pid() | {atom(), node()}) -> boolean().
+
is_process_alive(Pid) when is_pid(Pid) ->
on_running_node(Pid) andalso
rpc:call(node(Pid), erlang, is_process_alive, [Pid]) =:= true;
@@ -443,14 +435,22 @@ is_process_alive({Name, Node}) ->
lists:member(Node, cluster_nodes(running)) andalso
rpc:call(Node, rabbit_mnesia, is_registered_process_alive, [Name]) =:= true.
+-spec is_registered_process_alive(atom()) -> boolean().
+
is_registered_process_alive(Name) ->
is_pid(whereis(Name)).
+-spec cluster_nodes('all' | 'disc' | 'ram' | 'running') -> [node()].
+
cluster_nodes(WhichNodes) -> cluster_status(WhichNodes).
%% This function is the actual source of information, since it gets
%% the data from mnesia. Obviously it'll work only when mnesia is
%% running.
+
+-spec cluster_status_from_mnesia() -> rabbit_types:ok_or_error2(
+ cluster_status(), any()).
+
cluster_status_from_mnesia() ->
case is_running() of
false ->
@@ -505,6 +505,8 @@ node_info() ->
mnesia:system_info(protocol_version),
cluster_status_from_mnesia()}.
+-spec node_type() -> node_type().
+
node_type() ->
{_AllNodes, DiscNodes, _RunningNodes} =
rabbit_node_monitor:read_cluster_status(),
@@ -513,6 +515,8 @@ node_type() ->
false -> ram
end.
+-spec dir() -> file:filename().
+
dir() -> mnesia:system_info(directory).
%%----------------------------------------------------------------------------
@@ -547,10 +551,13 @@ init_db(ClusterNodes, NodeType, CheckOtherNodes) ->
ok = rabbit_table:wait_for_replicated(_Retry = true),
ok = rabbit_table:create_local_copy(NodeType)
end,
+ ensure_feature_flags_are_in_sync(Nodes),
ensure_schema_integrity(),
rabbit_node_monitor:update_cluster_status(),
ok.
+-spec init_db_unchecked([node()], node_type()) -> 'ok'.
+
init_db_unchecked(ClusterNodes, NodeType) ->
init_db(ClusterNodes, NodeType, false).
@@ -581,6 +588,8 @@ init_db_with_mnesia(ClusterNodes, NodeType,
stop_mnesia()
end.
+-spec ensure_mnesia_dir() -> 'ok'.
+
ensure_mnesia_dir() ->
MnesiaDir = dir() ++ "/",
case filelib:ensure_dir(MnesiaDir) of
@@ -612,6 +621,14 @@ ensure_mnesia_not_running() ->
throw({error, mnesia_unexpectedly_running})
end.
+ensure_feature_flags_are_in_sync(Nodes) ->
+ case rabbit_feature_flags:sync_feature_flags_with_cluster(Nodes) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ throw({error, {incompatible_feature_flags, Reason}})
+ end.
+
ensure_schema_integrity() ->
case rabbit_table:check_schema_integrity(_Retry = true) of
ok ->
@@ -620,6 +637,8 @@ ensure_schema_integrity() ->
throw({error, {schema_integrity_check_failed, Reason}})
end.
+-spec copy_db(file:filename()) -> rabbit_types:ok_or_error(any()).
+
copy_db(Destination) ->
ok = ensure_mnesia_not_running(),
rabbit_file:recursive_copy(dir(), Destination).
@@ -627,6 +646,8 @@ copy_db(Destination) ->
force_load_filename() ->
filename:join(dir(), "force_load").
+-spec force_load_next_boot() -> 'ok'.
+
force_load_next_boot() ->
rabbit_file:write_file(force_load_filename(), <<"">>).
@@ -639,6 +660,9 @@ maybe_force_load() ->
%% This does not guarantee us much, but it avoids some situations that
%% will definitely end up badly
+
+-spec check_cluster_consistency() -> 'ok'.
+
check_cluster_consistency() ->
%% We want to find 0 or 1 consistent nodes.
case lists:foldl(
@@ -708,12 +732,16 @@ remote_node_info(Node) ->
%% Hooks for `rabbit_node_monitor'
%%--------------------------------------------------------------------
+-spec on_node_up(node()) -> 'ok'.
+
on_node_up(Node) ->
case running_disc_nodes() of
[Node] -> rabbit_log:info("cluster contains disc nodes again~n");
_ -> ok
end.
+-spec on_node_down(node()) -> 'ok'.
+
on_node_down(_Node) ->
case running_disc_nodes() of
[] -> rabbit_log:info("only running disc node went down~n");
@@ -857,12 +885,12 @@ change_extra_db_nodes(ClusterNodes0, CheckOtherNodes) ->
check_consistency(Node, OTP, Rabbit, ProtocolVersion) ->
rabbit_misc:sequence_error(
[check_mnesia_or_otp_consistency(Node, ProtocolVersion, OTP),
- check_rabbit_consistency(Rabbit)]).
+ check_rabbit_consistency(Node, Rabbit)]).
check_consistency(Node, OTP, Rabbit, ProtocolVersion, Status) ->
rabbit_misc:sequence_error(
[check_mnesia_or_otp_consistency(Node, ProtocolVersion, OTP),
- check_rabbit_consistency(Rabbit),
+ check_rabbit_consistency(Node, Rabbit),
check_nodes_consistency(Node, Status)]).
check_nodes_consistency(Node, RemoteStatus = {RemoteAllNodes, _, _}) ->
@@ -923,10 +951,12 @@ with_running_or_clean_mnesia(Fun) ->
Result
end.
-check_rabbit_consistency(Remote) ->
- rabbit_version:check_version_consistency(
- rabbit_misc:version(), Remote, "Rabbit",
- fun rabbit_misc:version_minor_equivalent/2).
+check_rabbit_consistency(RemoteNode, RemoteVersion) ->
+ rabbit_misc:sequence_error(
+ [rabbit_version:check_version_consistency(
+ rabbit_misc:version(), RemoteVersion, "Rabbit",
+ fun rabbit_misc:version_minor_equivalent/2),
+ rabbit_feature_flags:check_node_compatibility(RemoteNode)]).
%% This is fairly tricky. We want to know if the node is in the state
%% that a `reset' would leave it in. We cannot simply check if the
@@ -989,6 +1019,8 @@ nodes_incl_me(Nodes) -> lists:usort([node()|Nodes]).
nodes_excl_me(Nodes) -> Nodes -- [node()].
+-spec e(any()) -> no_return().
+
e(Tag) -> throw({error, {Tag, error_description(Tag)}}).
error_description({invalid_cluster_node_names, BadNames}) ->
@@ -998,9 +1030,6 @@ error_description({invalid_cluster_node_type, BadType}) ->
"In the 'cluster_nodes' configuration key, the node type is invalid "
"(expected 'disc' or 'ram'): " ++
lists:flatten(io_lib:format("~p", [BadType]));
-error_description(cluster_node_type_mandatory) ->
- "The 'cluster_nodes' configuration key must indicate the node type: "
- "either {[...], disc} or {[...], ram}";
error_description(invalid_cluster_nodes_conf) ->
"The 'cluster_nodes' configuration key is invalid, it must be of the "
"form {[Nodes], Type}, where Nodes is a list of node names and "
diff --git a/src/rabbit_mnesia_rename.erl b/src/rabbit_mnesia_rename.erl
index c6648ac802..76e120f292 100644
--- a/src/rabbit_mnesia_rename.erl
+++ b/src/rabbit_mnesia_rename.erl
@@ -44,9 +44,6 @@
%%----------------------------------------------------------------------------
-spec rename(node(), [{node(), node()}]) -> 'ok'.
--spec maybe_finish([node()]) -> 'ok'.
-
-%%----------------------------------------------------------------------------
rename(Node, NodeMapList) ->
try
@@ -139,6 +136,8 @@ restore_backup(Backup) ->
stop_mnesia(),
rabbit_mnesia:force_load_next_boot().
+-spec maybe_finish([node()]) -> 'ok'.
+
maybe_finish(AllNodes) ->
case rabbit_file:read_term_file(rename_config_name()) of
{ok, [{FromNode, ToNode}]} -> finish(FromNode, ToNode, AllNodes);
diff --git a/src/rabbit_msg_file.erl b/src/rabbit_msg_file.erl
index f2496cd71f..f3ec7b78cc 100644
--- a/src/rabbit_msg_file.erl
+++ b/src/rabbit_msg_file.erl
@@ -41,15 +41,10 @@
fun (({rabbit_types:msg_id(), msg_size(), position(), binary()}, A) ->
A).
+%%----------------------------------------------------------------------------
+
-spec append(io_device(), rabbit_types:msg_id(), msg()) ->
rabbit_types:ok_or_error2(msg_size(), any()).
--spec read(io_device(), msg_size()) ->
- rabbit_types:ok_or_error2({rabbit_types:msg_id(), msg()},
- any()).
--spec scan(io_device(), file_size(), message_accumulator(A), A) ->
- {'ok', A, position()}.
-
-%%----------------------------------------------------------------------------
append(FileHdl, MsgId, MsgBody)
when is_binary(MsgId) andalso size(MsgId) =:= ?MSG_ID_SIZE_BYTES ->
@@ -65,6 +60,10 @@ append(FileHdl, MsgId, MsgBody)
KO -> KO
end.
+-spec read(io_device(), msg_size()) ->
+ rabbit_types:ok_or_error2({rabbit_types:msg_id(), msg()},
+ any()).
+
read(FileHdl, TotalSize) ->
Size = TotalSize - ?FILE_PACKING_ADJUSTMENT,
BodyBinSize = Size - ?MSG_ID_SIZE_BYTES,
@@ -77,6 +76,9 @@ read(FileHdl, TotalSize) ->
KO -> KO
end.
+-spec scan(io_device(), file_size(), message_accumulator(A), A) ->
+ {'ok', A, position()}.
+
scan(FileHdl, FileSize, Fun, Acc) when FileSize >= 0 ->
scan(FileHdl, FileSize, <<>>, 0, 0, Fun, Acc).
diff --git a/src/rabbit_msg_store.erl b/src/rabbit_msg_store.erl
index bae8364614..337064ad39 100644
--- a/src/rabbit_msg_store.erl
+++ b/src/rabbit_msg_store.erl
@@ -173,33 +173,6 @@
-type maybe_close_fds_fun() :: 'undefined' | fun (() -> 'ok').
-type deletion_thunk() :: fun (() -> boolean()).
--spec start_link
- (atom(), file:filename(), [binary()] | 'undefined',
- {msg_ref_delta_gen(A), A}) -> rabbit_types:ok_pid_or_error().
--spec successfully_recovered_state(server()) -> boolean().
--spec client_init(server(), client_ref(), maybe_msg_id_fun(),
- maybe_close_fds_fun()) -> client_msstate().
--spec client_terminate(client_msstate()) -> 'ok'.
--spec client_delete_and_terminate(client_msstate()) -> 'ok'.
--spec client_ref(client_msstate()) -> client_ref().
--spec close_all_indicated
- (client_msstate()) -> rabbit_types:ok(client_msstate()).
--spec write(rabbit_types:msg_id(), msg(), client_msstate()) -> 'ok'.
--spec write_flow(rabbit_types:msg_id(), msg(), client_msstate()) -> 'ok'.
--spec read(rabbit_types:msg_id(), client_msstate()) ->
- {rabbit_types:ok(msg()) | 'not_found', client_msstate()}.
--spec contains(rabbit_types:msg_id(), client_msstate()) -> boolean().
--spec remove([rabbit_types:msg_id()], client_msstate()) -> 'ok'.
-
--spec set_maximum_since_use(server(), non_neg_integer()) -> 'ok'.
--spec has_readers(non_neg_integer(), gc_state()) -> boolean().
--spec combine_files(non_neg_integer(), non_neg_integer(), gc_state()) ->
- deletion_thunk().
--spec delete_file(non_neg_integer(), gc_state()) -> deletion_thunk().
--spec force_recovery(file:filename(), server()) -> 'ok'.
--spec transform_dir(file:filename(), server(),
- fun ((any()) -> (rabbit_types:ok_or_error2(msg(), any())))) -> 'ok'.
-
%%----------------------------------------------------------------------------
%% We run GC whenever (garbage / sum_file_size) > ?GARBAGE_FRACTION
@@ -472,6 +445,10 @@
%% public API
%%----------------------------------------------------------------------------
+-spec start_link
+ (atom(), file:filename(), [binary()] | 'undefined',
+ {msg_ref_delta_gen(A), A}) -> rabbit_types:ok_pid_or_error().
+
start_link(Type, Dir, ClientRefs, StartupFunState) when is_atom(Type) ->
gen_server2:start_link(?MODULE,
[Type, Dir, ClientRefs, StartupFunState],
@@ -482,9 +459,14 @@ start_global_store_link(Type, Dir, ClientRefs, StartupFunState) when is_atom(Typ
[Type, Dir, ClientRefs, StartupFunState],
[{timeout, infinity}]).
+-spec successfully_recovered_state(server()) -> boolean().
+
successfully_recovered_state(Server) ->
gen_server2:call(Server, successfully_recovered_state, infinity).
+-spec client_init(server(), client_ref(), maybe_msg_id_fun(),
+ maybe_close_fds_fun()) -> client_msstate().
+
client_init(Server, Ref, MsgOnDiskFun, CloseFDsFun) when is_pid(Server); is_atom(Server) ->
{IState, IModule, Dir, GCPid,
FileHandlesEts, FileSummaryEts, CurFileCacheEts, FlyingEts} =
@@ -506,17 +488,25 @@ client_init(Server, Ref, MsgOnDiskFun, CloseFDsFun) when is_pid(Server); is_atom
flying_ets = FlyingEts,
credit_disc_bound = CreditDiscBound }.
+-spec client_terminate(client_msstate()) -> 'ok'.
+
client_terminate(CState = #client_msstate { client_ref = Ref }) ->
close_all_handles(CState),
ok = server_call(CState, {client_terminate, Ref}).
+-spec client_delete_and_terminate(client_msstate()) -> 'ok'.
+
client_delete_and_terminate(CState = #client_msstate { client_ref = Ref }) ->
close_all_handles(CState),
ok = server_cast(CState, {client_dying, Ref}),
ok = server_cast(CState, {client_delete, Ref}).
+-spec client_ref(client_msstate()) -> client_ref().
+
client_ref(#client_msstate { client_ref = Ref }) -> Ref.
+-spec write_flow(rabbit_types:msg_id(), msg(), client_msstate()) -> 'ok'.
+
write_flow(MsgId, Msg,
CState = #client_msstate {
server = Server,
@@ -528,8 +518,13 @@ write_flow(MsgId, Msg,
credit_flow:send(Server, CreditDiscBound),
client_write(MsgId, Msg, flow, CState).
+-spec write(rabbit_types:msg_id(), msg(), client_msstate()) -> 'ok'.
+
write(MsgId, Msg, CState) -> client_write(MsgId, Msg, noflow, CState).
+-spec read(rabbit_types:msg_id(), client_msstate()) ->
+ {rabbit_types:ok(msg()) | 'not_found', client_msstate()}.
+
read(MsgId,
CState = #client_msstate { cur_file_cache_ets = CurFileCacheEts }) ->
file_handle_cache_stats:update(msg_store_read),
@@ -545,12 +540,19 @@ read(MsgId,
{{ok, Msg}, CState}
end.
+-spec contains(rabbit_types:msg_id(), client_msstate()) -> boolean().
+
contains(MsgId, CState) -> server_call(CState, {contains, MsgId}).
+
+-spec remove([rabbit_types:msg_id()], client_msstate()) -> 'ok'.
+
remove([], _CState) -> ok;
remove(MsgIds, CState = #client_msstate { client_ref = CRef }) ->
[client_update_flying(-1, MsgId, CState) || MsgId <- MsgIds],
server_cast(CState, {remove, CRef, MsgIds}).
+-spec set_maximum_since_use(server(), non_neg_integer()) -> 'ok'.
+
set_maximum_since_use(Server, Age) when is_pid(Server); is_atom(Server) ->
gen_server2:cast(Server, {set_maximum_since_use, Age}).
@@ -1447,6 +1449,9 @@ safe_file_delete(File, Dir, FileHandlesEts) ->
true
end.
+-spec close_all_indicated
+ (client_msstate()) -> rabbit_types:ok(client_msstate()).
+
close_all_indicated(#client_msstate { file_handles_ets = FileHandlesEts,
client_ref = Ref } =
CState) ->
@@ -1965,11 +1970,16 @@ cleanup_after_file_deletion(File,
%% garbage collection / compaction / aggregation -- external
%%----------------------------------------------------------------------------
+-spec has_readers(non_neg_integer(), gc_state()) -> boolean().
+
has_readers(File, #gc_state { file_summary_ets = FileSummaryEts }) ->
[#file_summary { locked = true, readers = Count }] =
ets:lookup(FileSummaryEts, File),
Count /= 0.
+-spec combine_files(non_neg_integer(), non_neg_integer(), gc_state()) ->
+ deletion_thunk().
+
combine_files(Source, Destination,
State = #gc_state { file_summary_ets = FileSummaryEts,
file_handles_ets = FileHandlesEts,
@@ -2046,6 +2056,8 @@ combine_files(Source, Destination,
gen_server2:cast(Server, {combine_files, Source, Destination, Reclaimed}),
safe_file_delete_fun(Source, Dir, FileHandlesEts).
+-spec delete_file(non_neg_integer(), gc_state()) -> deletion_thunk().
+
delete_file(File, State = #gc_state { file_summary_ets = FileSummaryEts,
file_handles_ets = FileHandlesEts,
dir = Dir,
@@ -2127,6 +2139,8 @@ copy_messages(WorkList, InitOffset, FinalOffset, SourceHdl, DestinationHdl,
{destination, Destination}]}
end.
+-spec force_recovery(file:filename(), server()) -> 'ok'.
+
force_recovery(BaseDir, Store) ->
Dir = filename:join(BaseDir, atom_to_list(Store)),
case file:delete(filename:join(Dir, ?CLEAN_FILENAME)) of
@@ -2142,6 +2156,9 @@ foreach_file(D, Fun, Files) ->
foreach_file(D1, D2, Fun, Files) ->
[ok = Fun(filename:join(D1, File), filename:join(D2, File)) || File <- Files].
+-spec transform_dir(file:filename(), server(),
+ fun ((any()) -> (rabbit_types:ok_or_error2(msg(), any())))) -> 'ok'.
+
transform_dir(BaseDir, Store, TransformFun) ->
Dir = filename:join(BaseDir, atom_to_list(Store)),
TmpDir = filename:join(Dir, ?TRANSFORM_TMP),
diff --git a/src/rabbit_msg_store_gc.erl b/src/rabbit_msg_store_gc.erl
index d11d110abf..4b8d95b535 100644
--- a/src/rabbit_msg_store_gc.erl
+++ b/src/rabbit_msg_store_gc.erl
@@ -37,31 +37,34 @@
-spec start_link(rabbit_msg_store:gc_state()) ->
rabbit_types:ok_pid_or_error().
--spec combine(pid(), rabbit_msg_store:file_num(),
- rabbit_msg_store:file_num()) -> 'ok'.
--spec delete(pid(), rabbit_msg_store:file_num()) -> 'ok'.
--spec no_readers(pid(), rabbit_msg_store:file_num()) -> 'ok'.
--spec stop(pid()) -> 'ok'.
--spec set_maximum_since_use(pid(), non_neg_integer()) -> 'ok'.
-
-%%----------------------------------------------------------------------------
start_link(MsgStoreState) ->
gen_server2:start_link(?MODULE, [MsgStoreState],
[{timeout, infinity}]).
+-spec combine(pid(), rabbit_msg_store:file_num(),
+ rabbit_msg_store:file_num()) -> 'ok'.
+
combine(Server, Source, Destination) ->
gen_server2:cast(Server, {combine, Source, Destination}).
+-spec delete(pid(), rabbit_msg_store:file_num()) -> 'ok'.
+
delete(Server, File) ->
gen_server2:cast(Server, {delete, File}).
+-spec no_readers(pid(), rabbit_msg_store:file_num()) -> 'ok'.
+
no_readers(Server, File) ->
gen_server2:cast(Server, {no_readers, File}).
+-spec stop(pid()) -> 'ok'.
+
stop(Server) ->
gen_server2:call(Server, stop, infinity).
+-spec set_maximum_since_use(pid(), non_neg_integer()) -> 'ok'.
+
set_maximum_since_use(Pid, Age) ->
gen_server2:cast(Pid, {set_maximum_since_use, Age}).
diff --git a/src/rabbit_networking.erl b/src/rabbit_networking.erl
index 6131e2f294..809b999b98 100644
--- a/src/rabbit_networking.erl
+++ b/src/rabbit_networking.erl
@@ -35,7 +35,8 @@
connection_info/1, connection_info/2,
connection_info_all/0, connection_info_all/1,
emit_connection_info_all/4, emit_connection_info_local/3,
- close_connection/2, handshake/2, tcp_host/1]).
+ close_connection/2, force_connection_event_refresh/1,
+ handshake/2, tcp_host/1]).
%% Used by TCP-based transports, e.g. STOMP adapter
-export([tcp_listener_addresses/1, tcp_listener_spec/9,
@@ -43,6 +44,8 @@
-export([tcp_listener_started/4, tcp_listener_stopped/4]).
+-deprecated([{force_connection_event_refresh, 1, eventually}]).
+
%% Internal
-export([connections_local/0]).
@@ -67,51 +70,7 @@
-type protocol() :: atom().
-type label() :: string().
--spec start_tcp_listener(
- listener_config(), integer()) -> 'ok' | {'error', term()}.
--spec start_ssl_listener(
- listener_config(), rabbit_types:infos(), integer()) -> 'ok' | {'error', term()}.
--spec stop_tcp_listener(listener_config()) -> 'ok'.
--spec active_listeners() -> [rabbit_types:listener()].
--spec node_listeners(node()) -> [rabbit_types:listener()].
--spec register_connection(pid()) -> ok.
--spec unregister_connection(pid()) -> ok.
--spec connections() -> [rabbit_types:connection()].
--spec connections_local() -> [rabbit_types:connection()].
--spec connection_info_keys() -> rabbit_types:info_keys().
--spec connection_info(rabbit_types:connection()) -> rabbit_types:infos().
--spec connection_info(rabbit_types:connection(), rabbit_types:info_keys()) ->
- rabbit_types:infos().
--spec connection_info_all() -> [rabbit_types:infos()].
--spec connection_info_all(rabbit_types:info_keys()) ->
- [rabbit_types:infos()].
--spec close_connection(pid(), string()) -> 'ok'.
-
--spec on_node_down(node()) -> 'ok'.
--spec tcp_listener_addresses(listener_config()) -> [address()].
--spec tcp_listener_spec
- (name_prefix(), address(), [gen_tcp:listen_option()], module(), module(),
- protocol(), any(), non_neg_integer(), label()) ->
- supervisor:child_spec().
--spec ensure_ssl() -> rabbit_types:infos().
--spec poodle_check(atom()) -> 'ok' | 'danger'.
-
-spec boot() -> 'ok'.
--spec tcp_listener_started
- (_, _,
- string() |
- {byte(),byte(),byte(),byte()} |
- {char(),char(),char(),char(),char(),char(),char(),char()}, _) ->
- 'ok'.
--spec tcp_listener_stopped
- (_, _,
- string() |
- {byte(),byte(),byte(),byte()} |
- {char(),char(),char(),char(),char(),char(),char(),char()},
- _) ->
- 'ok'.
-
-%%----------------------------------------------------------------------------
boot() ->
ok = record_distribution_listener(),
@@ -156,12 +115,16 @@ boot_tls(NumAcceptors) ->
ok
end.
+-spec ensure_ssl() -> rabbit_types:infos().
+
ensure_ssl() ->
{ok, SslAppsConfig} = application:get_env(rabbit, ssl_apps),
ok = app_utils:start_applications(SslAppsConfig),
{ok, SslOptsConfig0} = application:get_env(rabbit, ssl_options),
rabbit_ssl_options:fix(SslOptsConfig0).
+-spec poodle_check(atom()) -> 'ok' | 'danger'.
+
poodle_check(Context) ->
{ok, Vsn} = application:get_key(ssl, vsn),
case rabbit_misc:version_compare(Vsn, "5.3", gte) of %% R16B01
@@ -190,6 +153,8 @@ log_poodle_fail(Context) ->
fix_ssl_options(Config) ->
rabbit_ssl_options:fix(Config).
+-spec tcp_listener_addresses(listener_config()) -> [address()].
+
tcp_listener_addresses(Port) when is_integer(Port) ->
tcp_listener_addresses_auto(Port);
tcp_listener_addresses({"auto", Port}) ->
@@ -210,6 +175,11 @@ tcp_listener_addresses_auto(Port) ->
lists:append([tcp_listener_addresses(Listener) ||
Listener <- port_to_listeners(Port)]).
+-spec tcp_listener_spec
+ (name_prefix(), address(), [gen_tcp:listen_option()], module(), module(),
+ protocol(), any(), non_neg_integer(), label()) ->
+ supervisor:child_spec().
+
tcp_listener_spec(NamePrefix, {IPAddress, Port, Family}, SocketOpts,
Transport, ProtoSup, ProtoOpts, Protocol, NumAcceptors, Label) ->
{rabbit_misc:tcp_name(NamePrefix, IPAddress, Port),
@@ -220,9 +190,15 @@ tcp_listener_spec(NamePrefix, {IPAddress, Port, Family}, SocketOpts,
NumAcceptors, Label]},
transient, infinity, supervisor, [tcp_listener_sup]}.
+-spec start_tcp_listener(
+ listener_config(), integer()) -> 'ok' | {'error', term()}.
+
start_tcp_listener(Listener, NumAcceptors) ->
start_listener(Listener, NumAcceptors, amqp, "TCP listener", tcp_opts()).
+-spec start_ssl_listener(
+ listener_config(), rabbit_types:infos(), integer()) -> 'ok' | {'error', term()}.
+
start_ssl_listener(Listener, SslOpts, NumAcceptors) ->
start_listener(Listener, NumAcceptors, 'amqp/ssl', "TLS (SSL) listener", tcp_opts() ++ SslOpts).
@@ -259,6 +235,7 @@ transport(Protocol) ->
'amqp/ssl' -> ranch_ssl
end.
+-spec stop_tcp_listener(listener_config()) -> 'ok'.
stop_tcp_listener(Listener) ->
[stop_tcp_listener0(Address) ||
@@ -270,6 +247,13 @@ stop_tcp_listener0({IPAddress, Port, _Family}) ->
ok = supervisor:terminate_child(rabbit_sup, Name),
ok = supervisor:delete_child(rabbit_sup, Name).
+-spec tcp_listener_started
+ (_, _,
+ string() |
+ {byte(),byte(),byte(),byte()} |
+ {char(),char(),char(),char(),char(),char(),char(),char()}, _) ->
+ 'ok'.
+
tcp_listener_started(Protocol, Opts, IPAddress, Port) ->
%% We need the ip to distinguish e.g. 0.0.0.0 and 127.0.0.1
%% We need the host so we can distinguish multiple instances of the above
@@ -283,6 +267,14 @@ tcp_listener_started(Protocol, Opts, IPAddress, Port) ->
port = Port,
opts = Opts}).
+-spec tcp_listener_stopped
+ (_, _,
+ string() |
+ {byte(),byte(),byte(),byte()} |
+ {char(),char(),char(),char(),char(),char(),char(),char()},
+ _) ->
+ 'ok'.
+
tcp_listener_stopped(Protocol, Opts, IPAddress, Port) ->
ok = mnesia:dirty_delete_object(
rabbit_listener,
@@ -298,12 +290,18 @@ record_distribution_listener() ->
{port, Port, _Version} = erl_epmd:port_please(Name, Host),
tcp_listener_started(clustering, [], {0,0,0,0,0,0,0,0}, Port).
+-spec active_listeners() -> [rabbit_types:listener()].
+
active_listeners() ->
rabbit_misc:dirty_read_all(rabbit_listener).
+-spec node_listeners(node()) -> [rabbit_types:listener()].
+
node_listeners(Node) ->
mnesia:dirty_read(rabbit_listener, Node).
+-spec on_node_down(node()) -> 'ok'.
+
on_node_down(Node) ->
case lists:member(Node, nodes()) of
false ->
@@ -315,22 +313,44 @@ on_node_down(Node) ->
"Keeping ~s listeners: the node is already back~n", [Node])
end.
+-spec register_connection(pid()) -> ok.
+
register_connection(Pid) -> pg_local:join(rabbit_connections, Pid).
+-spec unregister_connection(pid()) -> ok.
+
unregister_connection(Pid) -> pg_local:leave(rabbit_connections, Pid).
+-spec connections() -> [rabbit_types:connection()].
+
connections() ->
rabbit_misc:append_rpc_all_nodes(rabbit_mnesia:cluster_nodes(running),
rabbit_networking, connections_local, []).
+-spec connections_local() -> [rabbit_types:connection()].
+
connections_local() -> pg_local:get_members(rabbit_connections).
+-spec connection_info_keys() -> rabbit_types:info_keys().
+
connection_info_keys() -> rabbit_reader:info_keys().
+-spec connection_info(rabbit_types:connection()) -> rabbit_types:infos().
+
connection_info(Pid) -> rabbit_reader:info(Pid).
+
+-spec connection_info(rabbit_types:connection(), rabbit_types:info_keys()) ->
+ rabbit_types:infos().
+
connection_info(Pid, Items) -> rabbit_reader:info(Pid, Items).
+-spec connection_info_all() -> [rabbit_types:infos()].
+
connection_info_all() -> cmap(fun (Q) -> connection_info(Q) end).
+
+-spec connection_info_all(rabbit_types:info_keys()) ->
+ [rabbit_types:infos()].
+
connection_info_all(Items) -> cmap(fun (Q) -> connection_info(Q, Items) end).
emit_connection_info_all(Nodes, Items, Ref, AggregatorPid) ->
@@ -343,6 +363,8 @@ emit_connection_info_local(Items, Ref, AggregatorPid) ->
AggregatorPid, Ref, fun(Q) -> connection_info(Q, Items) end,
connections_local()).
+-spec close_connection(pid(), string()) -> 'ok'.
+
close_connection(Pid, Explanation) ->
case lists:member(Pid, connections()) of
true ->
@@ -356,6 +378,12 @@ close_connection(Pid, Explanation) ->
ok
end.
+-spec force_connection_event_refresh(reference()) -> 'ok'.
+
+force_connection_event_refresh(Ref) ->
+ [rabbit_reader:force_event_refresh(C, Ref) || C <- connections()],
+ ok.
+
handshake(Ref, ProxyProtocol) ->
case ProxyProtocol of
true ->
diff --git a/src/rabbit_node_monitor.erl b/src/rabbit_node_monitor.erl
index 4a5dea6073..cd46ade0e2 100644
--- a/src/rabbit_node_monitor.erl
+++ b/src/rabbit_node_monitor.erl
@@ -50,37 +50,11 @@
keepalive_timer, autoheal, guid, node_guids}).
%%----------------------------------------------------------------------------
-
--spec start_link() -> rabbit_types:ok_pid_or_error().
-
--spec running_nodes_filename() -> string().
--spec cluster_status_filename() -> string().
--spec prepare_cluster_status_files() -> 'ok'.
--spec write_cluster_status(rabbit_mnesia:cluster_status()) -> 'ok'.
--spec read_cluster_status() -> rabbit_mnesia:cluster_status().
--spec update_cluster_status() -> 'ok'.
--spec reset_cluster_status() -> 'ok'.
-
--spec notify_node_up() -> 'ok'.
--spec notify_joined_cluster() -> 'ok'.
--spec notify_left_cluster(node()) -> 'ok'.
-
--spec partitions() -> [node()].
--spec partitions([node()]) -> [{node(), [node()]}].
--spec status([node()]) -> {[{node(), [node()]}], [node()]}.
--spec subscribe(pid()) -> 'ok'.
--spec pause_partition_guard() -> 'ok' | 'pausing'.
-
--spec all_rabbit_nodes_up() -> boolean().
--spec run_outside_applications(fun (() -> any()), boolean()) -> pid().
--spec ping_all() -> 'ok'.
--spec alive_nodes([node()]) -> [node()].
--spec alive_rabbit_nodes([node()]) -> [node()].
-
-%%----------------------------------------------------------------------------
%% Start
%%----------------------------------------------------------------------------
+-spec start_link() -> rabbit_types:ok_pid_or_error().
+
start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%----------------------------------------------------------------------------
@@ -97,21 +71,26 @@ start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% the information we have will be outdated, but it cannot be
%% otherwise.
+-spec running_nodes_filename() -> string().
+
running_nodes_filename() ->
filename:join(rabbit_mnesia:dir(), "nodes_running_at_shutdown").
+-spec cluster_status_filename() -> string().
+
cluster_status_filename() ->
filename:join(rabbit_mnesia:dir(), "cluster_nodes.config").
quorum_filename() ->
filename:join(rabbit_mnesia:dir(), "quorum").
+-spec prepare_cluster_status_files() -> 'ok' | no_return().
+
prepare_cluster_status_files() ->
rabbit_mnesia:ensure_mnesia_dir(),
- Corrupt = fun(F) -> throw({error, corrupt_cluster_status_files, F}) end,
RunningNodes1 = case try_read_file(running_nodes_filename()) of
{ok, [Nodes]} when is_list(Nodes) -> Nodes;
- {ok, Other} -> Corrupt(Other);
+ {ok, Other} -> corrupt_cluster_status_files(Other);
{error, enoent} -> []
end,
ThisNode = [node()],
@@ -125,7 +104,7 @@ prepare_cluster_status_files() ->
{ok, [AllNodes0]} when is_list(AllNodes0) ->
{legacy_cluster_nodes(AllNodes0), legacy_disc_nodes(AllNodes0)};
{ok, Files} ->
- Corrupt(Files);
+ corrupt_cluster_status_files(Files);
{error, enoent} ->
LegacyNodes = legacy_cluster_nodes([]),
{LegacyNodes, LegacyNodes}
@@ -133,6 +112,13 @@ prepare_cluster_status_files() ->
AllNodes2 = lists:usort(AllNodes1 ++ RunningNodes2),
ok = write_cluster_status({AllNodes2, DiscNodes, RunningNodes2}).
+-spec corrupt_cluster_status_files(any()) -> no_return().
+
+corrupt_cluster_status_files(F) ->
+ throw({error, corrupt_cluster_status_files, F}).
+
+-spec write_cluster_status(rabbit_mnesia:cluster_status()) -> 'ok'.
+
write_cluster_status({All, Disc, Running}) ->
ClusterStatusFN = cluster_status_filename(),
Res = case rabbit_file:write_term_file(ClusterStatusFN, [{All, Disc}]) of
@@ -148,6 +134,8 @@ write_cluster_status({All, Disc, Running}) ->
{FN, {error, E2}} -> throw({error, {could_not_write_file, FN, E2}})
end.
+-spec read_cluster_status() -> rabbit_mnesia:cluster_status().
+
read_cluster_status() ->
case {try_read_file(cluster_status_filename()),
try_read_file(running_nodes_filename())} of
@@ -157,10 +145,14 @@ read_cluster_status() ->
throw({error, {corrupt_or_missing_cluster_files, Stat, Run}})
end.
+-spec update_cluster_status() -> 'ok'.
+
update_cluster_status() ->
{ok, Status} = rabbit_mnesia:cluster_status_from_mnesia(),
write_cluster_status(Status).
+-spec reset_cluster_status() -> 'ok'.
+
reset_cluster_status() ->
write_cluster_status({[node()], [node()], [node()]}).
@@ -168,15 +160,21 @@ reset_cluster_status() ->
%% Cluster notifications
%%----------------------------------------------------------------------------
+-spec notify_node_up() -> 'ok'.
+
notify_node_up() ->
gen_server:cast(?SERVER, notify_node_up).
+-spec notify_joined_cluster() -> 'ok'.
+
notify_joined_cluster() ->
Nodes = rabbit_mnesia:cluster_nodes(running) -- [node()],
gen_server:abcast(Nodes, ?SERVER,
{joined_cluster, node(), rabbit_mnesia:node_type()}),
ok.
+-spec notify_left_cluster(node()) -> 'ok'.
+
notify_left_cluster(Node) ->
Nodes = rabbit_mnesia:cluster_nodes(running),
gen_server:abcast(Nodes, ?SERVER, {left_cluster, Node}),
@@ -186,16 +184,24 @@ notify_left_cluster(Node) ->
%% Server calls
%%----------------------------------------------------------------------------
+-spec partitions() -> [node()].
+
partitions() ->
gen_server:call(?SERVER, partitions, infinity).
+-spec partitions([node()]) -> [{node(), [node()]}].
+
partitions(Nodes) ->
{Replies, _} = gen_server:multi_call(Nodes, ?SERVER, partitions, ?NODE_REPLY_TIMEOUT),
Replies.
+-spec status([node()]) -> {[{node(), [node()]}], [node()]}.
+
status(Nodes) ->
gen_server:multi_call(Nodes, ?SERVER, status, infinity).
+-spec subscribe(pid()) -> 'ok'.
+
subscribe(Pid) ->
gen_server:cast(?SERVER, {subscribe, Pid}).
@@ -218,6 +224,8 @@ subscribe(Pid) ->
%% So we have channels call in here before issuing confirms, to do a
%% lightweight check that we have not entered a pausing state.
+-spec pause_partition_guard() -> 'ok' | 'pausing'.
+
pause_partition_guard() ->
case get(pause_partition_guard) of
not_pause_mode ->
@@ -888,19 +896,28 @@ all_nodes_up() ->
Nodes = rabbit_mnesia:cluster_nodes(all),
length(alive_nodes(Nodes)) =:= length(Nodes).
+-spec all_rabbit_nodes_up() -> boolean().
+
all_rabbit_nodes_up() ->
Nodes = rabbit_mnesia:cluster_nodes(all),
length(alive_rabbit_nodes(Nodes)) =:= length(Nodes).
+-spec alive_nodes([node()]) -> [node()].
+
alive_nodes() -> alive_nodes(rabbit_mnesia:cluster_nodes(all)).
alive_nodes(Nodes) -> [N || N <- Nodes, lists:member(N, [node()|nodes()])].
+-spec alive_rabbit_nodes([node()]) -> [node()].
+
alive_rabbit_nodes() -> alive_rabbit_nodes(rabbit_mnesia:cluster_nodes(all)).
alive_rabbit_nodes(Nodes) ->
[N || N <- alive_nodes(Nodes), rabbit:is_running(N)].
%% This one is allowed to connect!
+
+-spec ping_all() -> 'ok'.
+
ping_all() ->
[net_adm:ping(N) || N <- rabbit_mnesia:cluster_nodes(all)],
ok.
diff --git a/src/rabbit_nodes.erl b/src/rabbit_nodes.erl
index 3a27ce8dc9..1e4abad8c9 100644
--- a/src/rabbit_nodes.erl
+++ b/src/rabbit_nodes.erl
@@ -31,28 +31,20 @@
%% Specs
%%----------------------------------------------------------------------------
--spec names(string()) ->
- rabbit_types:ok_or_error2([{string(), integer()}], term()).
--spec diagnostics([node()]) -> string().
--spec cookie_hash() -> string().
--spec is_running(node(), atom()) -> boolean().
--spec is_process_running(node(), atom()) -> boolean().
--spec cluster_name() -> binary().
--spec set_cluster_name(binary(), rabbit_types:username()) -> 'ok'.
--spec all_running() -> [node()].
--spec running_count() -> integer().
-
-%%----------------------------------------------------------------------------
-
name_type() ->
case os:getenv("RABBITMQ_USE_LONGNAME") of
"true" -> longnames;
_ -> shortnames
end.
+-spec names(string()) ->
+ rabbit_types:ok_or_error2([{string(), integer()}], term()).
+
names(Hostname) ->
rabbit_nodes_common:names(Hostname).
+-spec diagnostics([node()]) -> string().
+
diagnostics(Nodes) ->
rabbit_nodes_common:diagnostics(Nodes).
@@ -62,15 +54,23 @@ make(NodeStr) ->
parts(NodeStr) ->
rabbit_nodes_common:parts(NodeStr).
+-spec cookie_hash() -> string().
+
cookie_hash() ->
rabbit_nodes_common:cookie_hash().
+-spec is_running(node(), atom()) -> boolean().
+
is_running(Node, Application) ->
rabbit_nodes_common:is_running(Node, Application).
+-spec is_process_running(node(), atom()) -> boolean().
+
is_process_running(Node, Process) ->
rabbit_nodes_common:is_process_running(Node, Process).
+-spec cluster_name() -> binary().
+
cluster_name() ->
rabbit_runtime_parameters:value_global(
cluster_name, cluster_name_default()).
@@ -80,6 +80,8 @@ cluster_name_default() ->
FQDN = rabbit_net:hostname(),
list_to_binary(atom_to_list(make({ID, FQDN}))).
+-spec set_cluster_name(binary(), rabbit_types:username()) -> 'ok'.
+
set_cluster_name(Name, Username) ->
%% Cluster name should be binary
BinaryName = rabbit_data_coercion:to_binary(Name),
@@ -88,8 +90,12 @@ set_cluster_name(Name, Username) ->
ensure_epmd() ->
rabbit_nodes_common:ensure_epmd().
+-spec all_running() -> [node()].
+
all_running() -> rabbit_mnesia:cluster_nodes(running).
+-spec running_count() -> integer().
+
running_count() -> length(all_running()).
-spec await_running_count(integer(), integer()) -> 'ok' | {'error', atom()}.
diff --git a/src/rabbit_peer_discovery.erl b/src/rabbit_peer_discovery.erl
index 2f4acee49e..2ed36d32ec 100644
--- a/src/rabbit_peer_discovery.erl
+++ b/src/rabbit_peer_discovery.erl
@@ -105,9 +105,15 @@ maybe_init() ->
end.
--spec discover_cluster_nodes() -> {ok, Nodes :: list()} |
- {ok, {Nodes :: list(), NodeType :: rabbit_types:node_type()}} |
- {error, Reason :: string()}.
+%% This module doesn't currently sanity-check the return value of
+%% `Backend:list_nodes()`. Therefore, it could return something invalid:
+%% thus the `{œk, any()} in the spec.
+%%
+%% `rabbit_mnesia:init_from_config()` does some verifications.
+
+-spec discover_cluster_nodes() ->
+ {ok, {Nodes :: [node()], NodeType :: rabbit_types:node_type()} | any()} |
+ {error, Reason :: string()}.
discover_cluster_nodes() ->
Backend = backend(),
@@ -156,10 +162,7 @@ maybe_inject_randomized_delay() ->
-spec inject_randomized_delay() -> ok.
inject_randomized_delay() ->
- {Min, Max} = case randomized_delay_range_in_ms() of
- {A, B} -> {A, B};
- [A, B] -> {A, B}
- end,
+ {Min, Max} = randomized_delay_range_in_ms(),
case {Min, Max} of
%% When the max value is set to 0, consider the delay to be disabled.
%% In addition, `rand:uniform/1` will fail with a "no function clause"
@@ -258,12 +261,15 @@ unlock(Data) ->
%% Implementation
%%
--spec normalize(Nodes :: list() |
- {Nodes :: list(), NodeType :: rabbit_types:node_type()} |
- {ok, Nodes :: list()} |
- {ok, {Nodes :: list(), NodeType :: rabbit_types:node_type()}} |
- {error, Reason :: string()}) -> {ok, {Nodes :: list(), NodeType :: rabbit_types:node_type()}} |
- {error, Reason :: string()}.
+-spec normalize(Nodes :: [node()] |
+ {Nodes :: [node()],
+ NodeType :: rabbit_types:node_type()} |
+ {ok, Nodes :: [node()]} |
+ {ok, {Nodes :: [node()],
+ NodeType :: rabbit_types:node_type()}} |
+ {error, Reason :: string()}) ->
+ {ok, {Nodes :: [node()], NodeType :: rabbit_types:node_type()}} |
+ {error, Reason :: string()}.
normalize(Nodes) when is_list(Nodes) ->
{ok, {Nodes, disc}};
@@ -296,14 +302,12 @@ node_prefix() ->
--spec append_node_prefix(Value :: binary() | list()) -> atom().
+-spec append_node_prefix(Value :: binary() | string()) -> string().
-append_node_prefix(Value) ->
+append_node_prefix(Value) when is_binary(Value) orelse is_list(Value) ->
Val = rabbit_data_coercion:to_list(Value),
Hostname = case string:tokens(Val, ?NODENAME_PART_SEPARATOR) of
- [_ExistingPrefix, Val] ->
- Val;
- [Val] ->
- Val
+ [_ExistingPrefix, HN] -> HN;
+ [HN] -> HN
end,
string:join([node_prefix(), Hostname], ?NODENAME_PART_SEPARATOR).
diff --git a/src/rabbit_peer_discovery_classic_config.erl b/src/rabbit_peer_discovery_classic_config.erl
index 6597d77da4..16b7861f5f 100644
--- a/src/rabbit_peer_discovery_classic_config.erl
+++ b/src/rabbit_peer_discovery_classic_config.erl
@@ -26,13 +26,12 @@
%% API
%%
--spec list_nodes() -> {ok, Nodes :: list()} | {error, Reason :: string()}.
+-spec list_nodes() -> {ok, {Nodes :: [node()], rabbit_types:node_type()}}.
list_nodes() ->
- case application:get_env(rabbit, cluster_nodes) of
- {_Nodes, _NodeType} = Pair -> Pair;
- Nodes when is_list(Nodes) -> {Nodes, disc};
- undefined -> {[], disc}
+ case application:get_env(rabbit, cluster_nodes, {[], disc}) of
+ {_Nodes, _NodeType} = Pair -> {ok, Pair};
+ Nodes when is_list(Nodes) -> {ok, {Nodes, disc}}
end.
-spec supports_registration() -> boolean().
diff --git a/src/rabbit_peer_discovery_dns.erl b/src/rabbit_peer_discovery_dns.erl
index ad277e08b0..031d1290b3 100644
--- a/src/rabbit_peer_discovery_dns.erl
+++ b/src/rabbit_peer_discovery_dns.erl
@@ -28,12 +28,13 @@
%% API
%%
--spec list_nodes() -> {ok, Nodes :: list()} | {error, Reason :: string()}.
+-spec list_nodes() ->
+ {ok, {Nodes :: [node()], rabbit_types:node_type()}}.
list_nodes() ->
case application:get_env(rabbit, cluster_formation) of
undefined ->
- {[], disc};
+ {ok, {[], disc}};
{ok, ClusterFormation} ->
case proplists:get_value(peer_discovery_dns, ClusterFormation) of
undefined ->
@@ -41,11 +42,11 @@ list_nodes() ->
"but final config does not contain rabbit.cluster_formation.peer_discovery_dns. "
"Cannot discover any nodes because seed hostname is not configured!",
[?MODULE]),
- {[], disc};
+ {ok, {[], disc}};
Proplist ->
Hostname = rabbit_data_coercion:to_list(proplists:get_value(hostname, Proplist)),
- {discover_nodes(Hostname, net_kernel:longnames()), rabbit_peer_discovery:node_type()}
+ {ok, {discover_nodes(Hostname, net_kernel:longnames()), rabbit_peer_discovery:node_type()}}
end
end.
diff --git a/src/rabbit_plugins.erl b/src/rabbit_plugins.erl
index 80f70a1b7f..6adce8a24e 100644
--- a/src/rabbit_plugins.erl
+++ b/src/rabbit_plugins.erl
@@ -31,20 +31,10 @@
-type plugin_name() :: atom().
--spec setup() -> [plugin_name()].
--spec active() -> [plugin_name()].
--spec list(string()) -> [#plugin{}].
--spec list(string(), boolean()) -> [#plugin{}].
--spec read_enabled(file:filename()) -> [plugin_name()].
--spec dependencies(boolean(), [plugin_name()], [#plugin{}]) ->
- [plugin_name()].
--spec ensure(string()) -> {'ok', [atom()], [atom()]} | {error, any()}.
--spec strictly_plugins([plugin_name()], [#plugin{}]) -> [plugin_name()].
--spec strictly_plugins([plugin_name()]) -> [plugin_name()].
--spec is_strictly_plugin(#plugin{}) -> boolean().
-
%%----------------------------------------------------------------------------
+-spec ensure(string()) -> {'ok', [atom()], [atom()]} | {error, any()}.
+
ensure(FileJustChanged) ->
case rabbit:is_running() of
true -> ensure1(FileJustChanged);
@@ -128,6 +118,9 @@ enabled_plugins() ->
end.
%% @doc Prepares the file system and installs all enabled plugins.
+
+-spec setup() -> [plugin_name()].
+
setup() ->
ExpandDir = plugins_expand_dir(),
%% Eliminate the contents of the destination directory
@@ -184,15 +177,23 @@ extract_schema(#plugin{type = dir, location = Location}, SchemaDir) ->
%% @doc Lists the plugins which are currently running.
+
+-spec active() -> [plugin_name()].
+
active() ->
InstalledPlugins = plugin_names(list(plugins_dir())),
[App || {App, _, _} <- rabbit_misc:which_applications(),
lists:member(App, InstalledPlugins)].
%% @doc Get the list of plugins which are ready to be enabled.
+
+-spec list(string()) -> [#plugin{}].
+
list(PluginsPath) ->
list(PluginsPath, false).
+-spec list(string(), boolean()) -> [#plugin{}].
+
list(PluginsPath, IncludeRequiredDeps) ->
{AllPlugins, LoadingProblems} = discover_plugins(split_path(PluginsPath)),
{UniquePlugins, DuplicateProblems} = remove_duplicate_plugins(AllPlugins),
@@ -202,6 +203,9 @@ list(PluginsPath, IncludeRequiredDeps) ->
ensure_dependencies(Plugins2).
%% @doc Read the list of enabled plugins from the supplied term file.
+
+-spec read_enabled(file:filename()) -> [plugin_name()].
+
read_enabled(PluginsFile) ->
case rabbit_file:read_term_file(PluginsFile) of
{ok, [Plugins]} -> Plugins;
@@ -216,6 +220,10 @@ read_enabled(PluginsFile) ->
%% @doc Calculate the dependency graph from <i>Sources</i>.
%% When Reverse =:= true the bottom/leaf level applications are returned in
%% the resulting list, otherwise they're skipped.
+
+-spec dependencies(boolean(), [plugin_name()], [#plugin{}]) ->
+ [plugin_name()].
+
dependencies(Reverse, Sources, AllPlugins) ->
{ok, G} = rabbit_misc:build_acyclic_graph(
fun ({App, _Deps}) -> [{App, App}] end,
@@ -231,15 +239,22 @@ dependencies(Reverse, Sources, AllPlugins) ->
OrderedDests.
%% Filter real plugins from application dependencies
+
+-spec is_strictly_plugin(#plugin{}) -> boolean().
+
is_strictly_plugin(#plugin{extra_dependencies = ExtraDeps}) ->
lists:member(rabbit, ExtraDeps).
+-spec strictly_plugins([plugin_name()], [#plugin{}]) -> [plugin_name()].
+
strictly_plugins(Plugins, AllPlugins) ->
lists:filter(
fun(Name) ->
is_strictly_plugin(lists:keyfind(Name, #plugin.name, AllPlugins))
end, Plugins).
+-spec strictly_plugins([plugin_name()]) -> [plugin_name()].
+
strictly_plugins(Plugins) ->
AllPlugins = list(plugins_dir()),
lists:filter(
@@ -433,7 +448,8 @@ is_version_supported(VersionFull, ExpectedVersions) ->
%% therefore preview part should be removed
Version = remove_version_preview_part(VersionFull),
case lists:any(fun(ExpectedVersion) ->
- rabbit_misc:version_minor_equivalent(ExpectedVersion, Version)
+ rabbit_misc:strict_version_minor_equivalent(ExpectedVersion,
+ Version)
andalso
rabbit_misc:version_compare(ExpectedVersion, Version, lte)
end,
@@ -453,6 +469,7 @@ clean_plugins(Plugins) ->
clean_plugin(Plugin, ExpandDir) ->
{ok, Mods} = application:get_key(Plugin, modules),
application:unload(Plugin),
+ rabbit_feature_flags:initialize_registry(),
[begin
code:soft_purge(Mod),
code:delete(Mod),
diff --git a/src/rabbit_policy.erl b/src/rabbit_policy.erl
index 7f07b205ff..e38e23a2f1 100644
--- a/src/rabbit_policy.erl
+++ b/src/rabbit_policy.erl
@@ -36,7 +36,8 @@
-behaviour(rabbit_runtime_parameter).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-import(rabbit_misc, [pget/2, pget/3]).
@@ -59,16 +60,22 @@ register() ->
rabbit_registry:register(runtime_parameter, <<"policy">>, ?MODULE),
rabbit_registry:register(runtime_parameter, <<"operator_policy">>, ?MODULE).
-name(#amqqueue{policy = Policy}) -> name0(Policy);
+name(Q) when ?is_amqqueue(Q) ->
+ Policy = amqqueue:get_policy(Q),
+ name0(Policy);
name(#exchange{policy = Policy}) -> name0(Policy).
-name_op(#amqqueue{operator_policy = Policy}) -> name0(Policy);
+name_op(Q) when ?is_amqqueue(Q) ->
+ OpPolicy = amqqueue:get_operator_policy(Q),
+ name0(OpPolicy);
name_op(#exchange{operator_policy = Policy}) -> name0(Policy).
name0(undefined) -> none;
name0(Policy) -> pget(name, Policy).
-effective_definition(#amqqueue{policy = Policy, operator_policy = OpPolicy}) ->
+effective_definition(Q) when ?is_amqqueue(Q) ->
+ Policy = amqqueue:get_policy(Q),
+ OpPolicy = amqqueue:get_operator_policy(Q),
effective_definition0(Policy, OpPolicy);
effective_definition(#exchange{policy = Policy, operator_policy = OpPolicy}) ->
effective_definition0(Policy, OpPolicy).
@@ -90,10 +97,15 @@ effective_definition0(Policy, OpPolicy) ->
end,
lists:umerge(Keys, OpKeys)).
-set(Q = #amqqueue{name = Name}) -> Q#amqqueue{policy = match(Name),
- operator_policy = match_op(Name)};
-set(X = #exchange{name = Name}) -> X#exchange{policy = match(Name),
- operator_policy = match_op(Name)}.
+set(Q0) when ?is_amqqueue(Q0) ->
+ Name = amqqueue:get_name(Q0),
+ Policy = match(Name),
+ OpPolicy = match_op(Name),
+ Q1 = amqqueue:set_policy(Q0, Policy),
+ Q2 = amqqueue:set_operator_policy(Q1, OpPolicy),
+ Q2;
+set(X = #exchange{name = Name}) ->
+ X#exchange{policy = match(Name), operator_policy = match_op(Name)}.
match(Name = #resource{virtual_host = VHost}) ->
match(Name, list(VHost)).
@@ -101,7 +113,9 @@ match(Name = #resource{virtual_host = VHost}) ->
match_op(Name = #resource{virtual_host = VHost}) ->
match(Name, list_op(VHost)).
-get(Name, #amqqueue{policy = Policy, operator_policy = OpPolicy}) ->
+get(Name, Q) when ?is_amqqueue(Q) ->
+ Policy = amqqueue:get_policy(Q),
+ OpPolicy = amqqueue:get_operator_policy(Q),
get0(Name, Policy, OpPolicy);
get(Name, #exchange{policy = Policy, operator_policy = OpPolicy}) ->
get0(Name, Policy, OpPolicy);
@@ -170,7 +184,11 @@ recover() ->
%% variants.
recover0() ->
Xs = mnesia:dirty_match_object(rabbit_durable_exchange, #exchange{_ = '_'}),
- Qs = mnesia:dirty_match_object(rabbit_durable_queue, #amqqueue{_ = '_'}),
+ Qs = rabbit_amqqueue:list_with_possible_retry(
+ fun() ->
+ mnesia:dirty_match_object(
+ rabbit_durable_queue, amqqueue:pattern_match_all())
+ end),
Policies = list(),
OpPolicies = list_op(),
[rabbit_misc:execute_mnesia_transaction(
@@ -182,15 +200,26 @@ recover0() ->
operator_policy = match(Name, OpPolicies)}),
write)
end) || X = #exchange{name = Name} <- Xs],
- [rabbit_misc:execute_mnesia_transaction(
- fun () ->
- mnesia:write(
- rabbit_durable_queue,
- rabbit_queue_decorator:set(
- Q#amqqueue{policy = match(Name, Policies),
- operator_policy = match(Name, OpPolicies)}),
- write)
- end) || Q = #amqqueue{name = Name} <- Qs],
+ [begin
+ QName = amqqueue:get_name(Q0),
+ Policy1 = match(QName, Policies),
+ Q1 = amqqueue:set_policy(Q0, Policy1),
+ OpPolicy1 = match(QName, OpPolicies),
+ Q2 = amqqueue:set_operator_policy(Q1, OpPolicy1),
+ Q3 = rabbit_queue_decorator:set(Q2),
+ ?try_mnesia_tx_or_upgrade_amqqueue_and_retry(
+ rabbit_misc:execute_mnesia_transaction(
+ fun () ->
+ mnesia:write(rabbit_durable_queue, Q3, write)
+ end),
+ begin
+ Q4 = amqqueue:upgrade(Q3),
+ rabbit_misc:execute_mnesia_transaction(
+ fun () ->
+ mnesia:write(rabbit_durable_queue, Q4, write)
+ end)
+ end)
+ end || Q0 <- Qs],
ok.
invalid_file() ->
@@ -395,25 +424,26 @@ update_exchange(X = #exchange{name = XName,
end
end.
-update_queue(Q = #amqqueue{name = QName,
- policy = OldPolicy,
- operator_policy = OldOpPolicy},
- Policies, OpPolicies) ->
+update_queue(Q0, Policies, OpPolicies) when ?is_amqqueue(Q0) ->
+ QName = amqqueue:get_name(Q0),
+ OldPolicy = amqqueue:get_policy(Q0),
+ OldOpPolicy = amqqueue:get_operator_policy(Q0),
case {match(QName, Policies), match(QName, OpPolicies)} of
{OldPolicy, OldOpPolicy} -> no_change;
{NewPolicy, NewOpPolicy} ->
- NewQueue = rabbit_amqqueue:update(
- QName,
- fun(Q1) ->
- rabbit_queue_decorator:set(
- Q1#amqqueue{policy = NewPolicy,
- operator_policy = NewOpPolicy,
- policy_version =
- Q1#amqqueue.policy_version + 1 })
- end),
+ F = fun (QFun0) ->
+ QFun1 = amqqueue:set_policy(QFun0, NewPolicy),
+ QFun2 = amqqueue:set_operator_policy(QFun1, NewOpPolicy),
+ NewPolicyVersion = amqqueue:get_policy_version(QFun2) + 1,
+ QFun3 = amqqueue:set_policy_version(QFun2, NewPolicyVersion),
+ rabbit_queue_decorator:set(QFun3)
+ end,
+ NewQueue = rabbit_amqqueue:update(QName, F),
case NewQueue of
- #amqqueue{} = Q1 -> {Q, Q1};
- not_found -> {Q, Q }
+ Q1 when ?is_amqqueue(Q1) ->
+ {Q0, Q1};
+ not_found ->
+ {Q0, Q0}
end
end.
@@ -421,7 +451,7 @@ notify(no_change)->
ok;
notify({X1 = #exchange{}, X2 = #exchange{}}) ->
rabbit_exchange:policy_changed(X1, X2);
-notify({Q1 = #amqqueue{}, Q2 = #amqqueue{}}) ->
+notify({Q1, Q2}) when ?is_amqqueue(Q1), ?is_amqqueue(Q2) ->
rabbit_amqqueue:policy_changed(Q1, Q2).
match(Name, Policies) ->
diff --git a/src/rabbit_prelaunch.erl b/src/rabbit_prelaunch.erl
index 8d13a16e0b..b0fe6d2b04 100644
--- a/src/rabbit_prelaunch.erl
+++ b/src/rabbit_prelaunch.erl
@@ -29,13 +29,8 @@
-define(EX_USAGE, 64).
%%----------------------------------------------------------------------------
-%% Specs
-%%----------------------------------------------------------------------------
-spec start() -> no_return().
--spec stop() -> 'ok'.
-
-%%----------------------------------------------------------------------------
start() ->
case init:get_plain_arguments() of
@@ -55,6 +50,8 @@ start() ->
rabbit_misc:quit(?SET_DIST_PORT),
ok.
+-spec stop() -> 'ok'.
+
stop() ->
ok.
diff --git a/src/rabbit_prequeue.erl b/src/rabbit_prequeue.erl
index 63dbec545b..5e92013424 100644
--- a/src/rabbit_prequeue.erl
+++ b/src/rabbit_prequeue.erl
@@ -29,7 +29,8 @@
-behaviour(gen_server2).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
%%----------------------------------------------------------------------------
@@ -37,11 +38,11 @@
-type start_mode() :: 'declare' | 'recovery' | 'slave'.
--spec start_link(rabbit_types:amqqueue(), start_mode(), pid())
- -> rabbit_types:ok_pid_or_error().
-
%%----------------------------------------------------------------------------
+-spec start_link(amqqueue:amqqueue(), start_mode(), pid())
+ -> rabbit_types:ok_pid_or_error().
+
start_link(Q, StartMode, Marker) ->
gen_server2:start_link(?MODULE, {Q, StartMode, Marker}, []).
@@ -57,20 +58,22 @@ init({Q, StartMode, Marker}) ->
init(Q, master) -> rabbit_amqqueue_process:init(Q);
init(Q, slave) -> rabbit_mirror_queue_slave:init(Q);
-init(#amqqueue{name = QueueName}, restart) ->
- {ok, Q = #amqqueue{pid = QPid,
- slave_pids = SPids}} = rabbit_amqqueue:lookup(QueueName),
+init(Q0, restart) when ?is_amqqueue(Q0) ->
+ QueueName = amqqueue:get_name(Q0),
+ {ok, Q1} = rabbit_amqqueue:lookup(QueueName),
+ QPid = amqqueue:get_pid(Q1),
+ SPids = amqqueue:get_slave_pids(Q1),
LocalOrMasterDown = node(QPid) =:= node()
orelse not rabbit_mnesia:on_running_node(QPid),
Slaves = [SPid || SPid <- SPids, rabbit_mnesia:is_process_alive(SPid)],
case rabbit_mnesia:is_process_alive(QPid) of
true -> false = LocalOrMasterDown, %% assertion
rabbit_mirror_queue_slave:go(self(), async),
- rabbit_mirror_queue_slave:init(Q); %% [1]
+ rabbit_mirror_queue_slave:init(Q1); %% [1]
false -> case LocalOrMasterDown andalso Slaves =:= [] of
- true -> crash_restart(Q); %% [2]
+ true -> crash_restart(Q1); %% [2]
false -> timer:sleep(25),
- init(Q, restart) %% [3]
+ init(Q1, restart) %% [3]
end
end.
%% [1] There is a master on another node. Regardless of whether we
@@ -83,10 +86,12 @@ init(#amqqueue{name = QueueName}, restart) ->
%% not a stable situation. Sleep and wait for somebody else to make a
%% move.
-crash_restart(Q = #amqqueue{name = QueueName}) ->
+crash_restart(Q0) when ?is_amqqueue(Q0) ->
+ QueueName = amqqueue:get_name(Q0),
rabbit_log:error("Restarting crashed ~s.~n", [rabbit_misc:rs(QueueName)]),
gen_server2:cast(self(), init),
- rabbit_amqqueue_process:init(Q#amqqueue{pid = self()}).
+ Q1 = amqqueue:set_pid(Q0, self()),
+ rabbit_amqqueue_process:init(Q1).
%%----------------------------------------------------------------------------
diff --git a/src/rabbit_priority_queue.erl b/src/rabbit_priority_queue.erl
index b41511f874..621f42dafb 100644
--- a/src/rabbit_priority_queue.erl
+++ b/src/rabbit_priority_queue.erl
@@ -16,8 +16,10 @@
-module(rabbit_priority_queue).
--include_lib("rabbit.hrl").
--include_lib("rabbit_framing.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit_framing.hrl").
+-include("amqqueue.hrl").
+
-behaviour(rabbit_backing_queue).
%% enabled unconditionally. Disabling priority queueing after
@@ -102,10 +104,14 @@ stop(VHost) ->
%%----------------------------------------------------------------------------
-mutate_name(P, Q = #amqqueue{name = QName = #resource{name = QNameBin}}) ->
- Q#amqqueue{name = QName#resource{name = mutate_name_bin(P, QNameBin)}}.
+mutate_name(P, Q) when ?is_amqqueue(Q) ->
+ Res0 = #resource{name = QNameBin0} = amqqueue:get_name(Q),
+ QNameBin1 = mutate_name_bin(P, QNameBin0),
+ Res1 = Res0#resource{name = QNameBin1},
+ amqqueue:set_name(Q, Res1).
-mutate_name_bin(P, NameBin) -> <<NameBin/binary, 0, P:8>>.
+mutate_name_bin(P, NameBin) ->
+ <<NameBin/binary, 0, P:8>>.
expand_queues(QNames) ->
lists:unzip(
@@ -125,7 +131,8 @@ collapse_recovery(QNames, DupNames, Recovery) ->
end, dict:new(), lists:zip(DupNames, Recovery)),
[dict:fetch(Name, NameToTerms) || Name <- QNames].
-priorities(#amqqueue{arguments = Args}) ->
+priorities(Q) when ?is_amqqueue(Q) ->
+ Args = amqqueue:get_arguments(Q),
Ints = [long, short, signedint, byte, unsignedbyte, unsignedshort, unsignedint],
case rabbit_misc:table_lookup(Args, <<"x-max-priority">>) of
{Type, RequestedMax} ->
diff --git a/src/rabbit_queue_collector.erl b/src/rabbit_queue_collector.erl
index e52156f60d..732415b82e 100644
--- a/src/rabbit_queue_collector.erl
+++ b/src/rabbit_queue_collector.erl
@@ -33,13 +33,12 @@
%%----------------------------------------------------------------------------
-spec start_link(rabbit_types:proc_name()) -> rabbit_types:ok_pid_or_error().
--spec register(pid(), pid()) -> 'ok'.
-
-%%----------------------------------------------------------------------------
start_link(ProcName) ->
gen_server:start_link(?MODULE, [ProcName], []).
+-spec register(pid(), pid()) -> 'ok'.
+
register(CollectorPid, Q) ->
gen_server:call(CollectorPid, {register, Q}, infinity).
diff --git a/src/rabbit_queue_consumers.erl b/src/rabbit_queue_consumers.erl
index 7a0c0f98e3..2ede7b7b8e 100644
--- a/src/rabbit_queue_consumers.erl
+++ b/src/rabbit_queue_consumers.erl
@@ -66,57 +66,29 @@
-type cr_fun() :: fun ((#cr{}) -> #cr{}).
-type fetch_result() :: {rabbit_types:basic_message(), boolean(), ack()}.
--spec new() -> state().
--spec max_active_priority(state()) -> integer() | 'infinity' | 'empty'.
--spec inactive(state()) -> boolean().
--spec all(state()) -> [{ch(), rabbit_types:ctag(), boolean(),
- non_neg_integer(), rabbit_framing:amqp_table(),
- rabbit_types:username()}].
--spec count() -> non_neg_integer().
--spec unacknowledged_message_count() -> non_neg_integer().
--spec add(ch(), rabbit_types:ctag(), boolean(), pid(), boolean(),
- non_neg_integer(), rabbit_framing:amqp_table(), boolean(),
- rabbit_types:username(), state())
- -> state().
--spec remove(ch(), rabbit_types:ctag(), state()) ->
- 'not_found' | state().
--spec erase_ch(ch(), state()) ->
- 'not_found' | {[ack()], [rabbit_types:ctag()],
- state()}.
--spec send_drained() -> 'ok'.
--spec deliver(fun ((boolean()) -> {fetch_result(), T}),
- rabbit_amqqueue:name(), state(), boolean(),
- none | {ch(), rabbit_types:ctag()} | {ch(), consumer()}) ->
- {'delivered', boolean(), T, state()} |
- {'undelivered', boolean(), state()}.
--spec record_ack(ch(), pid(), ack()) -> 'ok'.
--spec subtract_acks(ch(), [ack()], state()) ->
- 'not_found' | 'unchanged' | {'unblocked', state()}.
--spec possibly_unblock(cr_fun(), ch(), state()) ->
- 'unchanged' | {'unblocked', state()}.
--spec resume_fun() -> cr_fun().
--spec notify_sent_fun(non_neg_integer()) -> cr_fun().
--spec activate_limit_fun() -> cr_fun().
--spec credit(boolean(), integer(), boolean(), ch(), rabbit_types:ctag(),
- state()) -> 'unchanged' | {'unblocked', state()}.
--spec utilisation(state()) -> ratio().
--spec get(ch(), rabbit_types:ctag(), state()) -> undefined | consumer().
--spec consumer_tag(consumer()) -> rabbit_types:ctag().
--spec get_infos(consumer()) -> term().
-
%%----------------------------------------------------------------------------
+-spec new() -> state().
+
new() -> #state{consumers = priority_queue:new(),
use = {active,
erlang:monotonic_time(micro_seconds),
1.0}}.
+-spec max_active_priority(state()) -> integer() | 'infinity' | 'empty'.
+
max_active_priority(#state{consumers = Consumers}) ->
priority_queue:highest(Consumers).
+-spec inactive(state()) -> boolean().
+
inactive(#state{consumers = Consumers}) ->
priority_queue:is_empty(Consumers).
+-spec all(state()) -> [{ch(), rabbit_types:ctag(), boolean(),
+ non_neg_integer(), rabbit_framing:amqp_table(),
+ rabbit_types:username()}].
+
all(State) ->
all(State, none, false).
@@ -146,11 +118,20 @@ consumers(Consumers, SingleActiveConsumer, SingleActiveConsumerOn, Acc) ->
[{ChPid, CTag, Ack, Prefetch, Active, ActivityStatus, Args, Username} | Acc1]
end, Acc, Consumers).
+-spec count() -> non_neg_integer().
+
count() -> lists:sum([Count || #cr{consumer_count = Count} <- all_ch_record()]).
+-spec unacknowledged_message_count() -> non_neg_integer().
+
unacknowledged_message_count() ->
lists:sum([?QUEUE:len(C#cr.acktags) || C <- all_ch_record()]).
+-spec add(ch(), rabbit_types:ctag(), boolean(), pid(), boolean(),
+ non_neg_integer(), rabbit_framing:amqp_table(), boolean(),
+ rabbit_types:username(), state())
+ -> state().
+
add(ChPid, CTag, NoAck, LimiterPid, LimiterActive, Prefetch, Args, IsEmpty,
Username, State = #state{consumers = Consumers,
use = CUInfo}) ->
@@ -176,6 +157,9 @@ add(ChPid, CTag, NoAck, LimiterPid, LimiterActive, Prefetch, Args, IsEmpty,
State#state{consumers = add_consumer({ChPid, Consumer}, Consumers),
use = update_use(CUInfo, active)}.
+-spec remove(ch(), rabbit_types:ctag(), state()) ->
+ 'not_found' | state().
+
remove(ChPid, CTag, State = #state{consumers = Consumers}) ->
case lookup_ch(ChPid) of
not_found ->
@@ -196,6 +180,10 @@ remove(ChPid, CTag, State = #state{consumers = Consumers}) ->
remove_consumer(ChPid, CTag, Consumers)}
end.
+-spec erase_ch(ch(), state()) ->
+ 'not_found' | {[ack()], [rabbit_types:ctag()],
+ state()}.
+
erase_ch(ChPid, State = #state{consumers = Consumers}) ->
case lookup_ch(ChPid) of
not_found ->
@@ -211,9 +199,17 @@ erase_ch(ChPid, State = #state{consumers = Consumers}) ->
State#state{consumers = remove_consumers(ChPid, Consumers)}}
end.
+-spec send_drained() -> 'ok'.
+
send_drained() -> [update_ch_record(send_drained(C)) || C <- all_ch_record()],
ok.
+-spec deliver(fun ((boolean()) -> {fetch_result(), T}),
+ rabbit_amqqueue:name(), state(), boolean(),
+ none | {ch(), rabbit_types:ctag()} | {ch(), consumer()}) ->
+ {'delivered', boolean(), T, state()} |
+ {'undelivered', boolean(), state()}.
+
deliver(FetchFun, QName, State, SingleActiveConsumerIsOn, ActiveConsumer) ->
deliver(FetchFun, QName, false, State, SingleActiveConsumerIsOn, ActiveConsumer).
@@ -299,11 +295,16 @@ is_blocked(Consumer = {ChPid, _C}) ->
#cr{blocked_consumers = BlockedConsumers} = lookup_ch(ChPid),
priority_queue:member(Consumer, BlockedConsumers).
+-spec record_ack(ch(), pid(), ack()) -> 'ok'.
+
record_ack(ChPid, LimiterPid, AckTag) ->
C = #cr{acktags = ChAckTags} = ch_record(ChPid, LimiterPid),
update_ch_record(C#cr{acktags = ?QUEUE:in({AckTag, none}, ChAckTags)}),
ok.
+-spec subtract_acks(ch(), [ack()], state()) ->
+ 'not_found' | 'unchanged' | {'unblocked', state()}.
+
subtract_acks(ChPid, AckTags, State) ->
case lookup_ch(ChPid) of
not_found ->
@@ -341,6 +342,9 @@ subtract_acks([T | TL] = AckTags, Prefix, CTagCounts, AckQ) ->
subtract_acks([], Prefix, CTagCounts, AckQ)
end.
+-spec possibly_unblock(cr_fun(), ch(), state()) ->
+ 'unchanged' | {'unblocked', state()}.
+
possibly_unblock(Update, ChPid, State) ->
case lookup_ch(ChPid) of
not_found -> unchanged;
@@ -370,23 +374,31 @@ unblock(C = #cr{blocked_consumers = BlockedQ, limiter = Limiter},
use = update_use(Use, active)}}
end.
+-spec resume_fun() -> cr_fun().
+
resume_fun() ->
fun (C = #cr{limiter = Limiter}) ->
C#cr{limiter = rabbit_limiter:resume(Limiter)}
end.
+-spec notify_sent_fun(non_neg_integer()) -> cr_fun().
+
notify_sent_fun(Credit) ->
fun (C = #cr{unsent_message_count = Count}) ->
C#cr{unsent_message_count = Count - Credit}
end.
+-spec activate_limit_fun() -> cr_fun().
+
activate_limit_fun() ->
fun (C = #cr{limiter = Limiter}) ->
C#cr{limiter = rabbit_limiter:activate(Limiter)}
end.
-credit(IsEmpty, Credit, Drain, ChPid, CTag, State) ->
+-spec credit(boolean(), integer(), boolean(), ch(), rabbit_types:ctag(),
+ state()) -> 'unchanged' | {'unblocked', state()}.
+credit(IsEmpty, Credit, Drain, ChPid, CTag, State) ->
case lookup_ch(ChPid) of
not_found ->
unchanged;
@@ -405,6 +417,8 @@ credit(IsEmpty, Credit, Drain, ChPid, CTag, State) ->
drain_mode(true) -> drain;
drain_mode(false) -> manual.
+-spec utilisation(state()) -> ratio().
+
utilisation(#state{use = {active, Since, Avg}}) ->
use_avg(erlang:monotonic_time(micro_seconds) - Since, 0, Avg);
utilisation(#state{use = {inactive, Since, Active, Avg}}) ->
@@ -421,6 +435,8 @@ get_consumer(#state{consumers = Consumers}) ->
{empty, _} -> undefined
end.
+-spec get(ch(), rabbit_types:ctag(), state()) -> undefined | consumer().
+
get(ChPid, ConsumerTag, #state{consumers = Consumers}) ->
Consumers1 = priority_queue:filter(fun ({CP, #consumer{tag = CT}}) ->
(CP == ChPid) and (CT == ConsumerTag)
@@ -430,10 +446,14 @@ get(ChPid, ConsumerTag, #state{consumers = Consumers}) ->
{{value, Consumer, _Priority}, _Tail} -> Consumer
end.
+-spec get_infos(consumer()) -> term().
+
get_infos(Consumer) ->
{Consumer#consumer.tag,Consumer#consumer.ack_required,
Consumer#consumer.prefetch, Consumer#consumer.args}.
+-spec consumer_tag(consumer()) -> rabbit_types:ctag().
+
consumer_tag(#consumer{tag = CTag}) ->
CTag.
diff --git a/src/rabbit_queue_decorator.erl b/src/rabbit_queue_decorator.erl
index 0c234e1072..6f40637b93 100644
--- a/src/rabbit_queue_decorator.erl
+++ b/src/rabbit_queue_decorator.erl
@@ -16,7 +16,8 @@
-module(rabbit_queue_decorator).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-export([select/1, set/1, register/2, unregister/1]).
@@ -26,18 +27,18 @@
%%----------------------------------------------------------------------------
--callback startup(rabbit_types:amqqueue()) -> 'ok'.
+-callback startup(amqqueue:amqqueue()) -> 'ok'.
--callback shutdown(rabbit_types:amqqueue()) -> 'ok'.
+-callback shutdown(amqqueue:amqqueue()) -> 'ok'.
--callback policy_changed(rabbit_types:amqqueue(), rabbit_types:amqqueue()) ->
+-callback policy_changed(amqqueue:amqqueue(), amqqueue:amqqueue()) ->
'ok'.
--callback active_for(rabbit_types:amqqueue()) -> boolean().
+-callback active_for(amqqueue:amqqueue()) -> boolean().
%% called with Queue, MaxActivePriority, IsEmpty
-callback consumer_state_changed(
- rabbit_types:amqqueue(), integer(), boolean()) -> 'ok'.
+ amqqueue:amqqueue(), integer(), boolean()) -> 'ok'.
%%----------------------------------------------------------------------------
@@ -47,7 +48,9 @@ removed_from_rabbit_registry(_Type) -> ok.
select(Modules) ->
[M || M <- Modules, code:which(M) =/= non_existing].
-set(Q) -> Q#amqqueue{decorators = [D || D <- list(), D:active_for(Q)]}.
+set(Q) when ?is_amqqueue(Q) ->
+ Decorators = [D || D <- list(), D:active_for(Q)],
+ amqqueue:set_decorators(Q, Decorators).
list() -> [M || {_, M} <- rabbit_registry:lookup_all(queue_decorator)].
@@ -61,13 +64,18 @@ unregister(TypeName) ->
[maybe_recover(Q) || Q <- rabbit_amqqueue:list()],
ok.
-maybe_recover(Q = #amqqueue{name = Name,
- decorators = Decs}) ->
- #amqqueue{decorators = Decs1} = set(Q),
- Old = lists:sort(select(Decs)),
+maybe_recover(Q0) when ?is_amqqueue(Q0) ->
+ Name = amqqueue:get_name(Q0),
+ Decs0 = amqqueue:get_decorators(Q0),
+ Q1 = set(Q0),
+ Decs1 = amqqueue:get_decorators(Q1),
+ Old = lists:sort(select(Decs0)),
New = lists:sort(select(Decs1)),
case New of
- Old -> ok;
- _ -> [M:startup(Q) || M <- New -- Old],
- rabbit_amqqueue:update_decorators(Name)
+ Old ->
+ ok;
+ _ ->
+ %% TODO LRB JSP 160169569 should startup be passed Q1 here?
+ [M:startup(Q0) || M <- New -- Old],
+ rabbit_amqqueue:update_decorators(Name)
end.
diff --git a/src/rabbit_queue_index.erl b/src/rabbit_queue_index.erl
index 9785ae170d..e4047a9902 100644
--- a/src/rabbit_queue_index.erl
+++ b/src/rabbit_queue_index.erl
@@ -258,46 +258,20 @@
{rabbit_types:msg_id(), non_neg_integer(), A}).
-type shutdown_terms() :: [term()] | 'non_clean_shutdown'.
--spec erase(rabbit_amqqueue:name()) -> 'ok'.
--spec reset_state(qistate()) -> qistate().
--spec init(rabbit_amqqueue:name(),
- on_sync_fun(), on_sync_fun()) -> qistate().
--spec recover(rabbit_amqqueue:name(), shutdown_terms(), boolean(),
- contains_predicate(),
- on_sync_fun(), on_sync_fun()) ->
- {'undefined' | non_neg_integer(),
- 'undefined' | non_neg_integer(), qistate()}.
--spec terminate(rabbit_types:vhost(), [any()], qistate()) -> qistate().
--spec delete_and_terminate(qistate()) -> qistate().
--spec publish(rabbit_types:msg_id(), seq_id(),
- rabbit_types:message_properties(), boolean(),
- non_neg_integer(), qistate()) -> qistate().
--spec deliver([seq_id()], qistate()) -> qistate().
--spec ack([seq_id()], qistate()) -> qistate().
--spec sync(qistate()) -> qistate().
--spec needs_sync(qistate()) -> 'confirms' | 'other' | 'false'.
--spec flush(qistate()) -> qistate().
--spec read(seq_id(), seq_id(), qistate()) ->
- {[{rabbit_types:msg_id(), seq_id(),
- rabbit_types:message_properties(),
- boolean(), boolean()}], qistate()}.
--spec next_segment_boundary(seq_id()) -> seq_id().
--spec bounds(qistate()) ->
- {non_neg_integer(), non_neg_integer(), qistate()}.
--spec start(rabbit_types:vhost(), [rabbit_amqqueue:name()]) -> {[[any()]], {walker(A), A}}.
-
--spec add_queue_ttl() -> 'ok'.
-
-
%%----------------------------------------------------------------------------
%% public API
%%----------------------------------------------------------------------------
+-spec erase(rabbit_amqqueue:name()) -> 'ok'.
+
erase(Name) ->
#qistate { dir = Dir } = blank_state(Name),
erase_index_dir(Dir).
%% used during variable queue purge when there are no pending acks
+
+-spec reset_state(qistate()) -> qistate().
+
reset_state(#qistate{ queue_name = Name,
dir = Dir,
on_sync = OnSyncFun,
@@ -310,12 +284,21 @@ reset_state(#qistate{ queue_name = Name,
ok = erase_index_dir(Dir),
blank_state_name_dir_funs(Name, Dir, OnSyncFun, OnSyncMsgFun).
+-spec init(rabbit_amqqueue:name(),
+ on_sync_fun(), on_sync_fun()) -> qistate().
+
init(Name, OnSyncFun, OnSyncMsgFun) ->
State = #qistate { dir = Dir } = blank_state(Name),
false = rabbit_file:is_file(Dir), %% is_file == is file or dir
State#qistate{on_sync = OnSyncFun,
on_sync_msg = OnSyncMsgFun}.
+-spec recover(rabbit_amqqueue:name(), shutdown_terms(), boolean(),
+ contains_predicate(),
+ on_sync_fun(), on_sync_fun()) ->
+ {'undefined' | non_neg_integer(),
+ 'undefined' | non_neg_integer(), qistate()}.
+
recover(Name, Terms, MsgStoreRecovered, ContainsCheckFun,
OnSyncFun, OnSyncMsgFun) ->
State = blank_state(Name),
@@ -328,12 +311,16 @@ recover(Name, Terms, MsgStoreRecovered, ContainsCheckFun,
false -> init_dirty(CleanShutdown, ContainsCheckFun, State1)
end.
+-spec terminate(rabbit_types:vhost(), [any()], qistate()) -> qistate().
+
terminate(VHost, Terms, State = #qistate { dir = Dir }) ->
{SegmentCounts, State1} = terminate(State),
rabbit_recovery_terms:store(VHost, filename:basename(Dir),
[{segments, SegmentCounts} | Terms]),
State1.
+-spec delete_and_terminate(qistate()) -> qistate().
+
delete_and_terminate(State) ->
{_SegmentCounts, State1 = #qistate { dir = Dir }} = terminate(State),
ok = rabbit_file:recursive_delete([Dir]),
@@ -396,6 +383,10 @@ flush_delivered_cache(State = #qistate{delivered_cache = DC}) ->
State1 = deliver(lists:reverse(DC), State),
State1#qistate{delivered_cache = []}.
+-spec publish(rabbit_types:msg_id(), seq_id(),
+ rabbit_types:message_properties(), boolean(),
+ non_neg_integer(), qistate()) -> qistate().
+
publish(MsgOrId, SeqId, MsgProps, IsPersistent, JournalSizeHint, State) ->
{JournalHdl, State1} =
get_journal_handle(
@@ -429,20 +420,29 @@ maybe_needs_confirming(MsgProps, MsgOrId,
{false, _} -> State
end.
+-spec deliver([seq_id()], qistate()) -> qistate().
+
deliver(SeqIds, State) ->
deliver_or_ack(del, SeqIds, State).
+-spec ack([seq_id()], qistate()) -> qistate().
+
ack(SeqIds, State) ->
deliver_or_ack(ack, SeqIds, State).
%% This is called when there are outstanding confirms or when the
%% queue is idle and the journal needs syncing (see needs_sync/1).
+
+-spec sync(qistate()) -> qistate().
+
sync(State = #qistate { journal_handle = undefined }) ->
State;
sync(State = #qistate { journal_handle = JournalHdl }) ->
ok = file_handle_cache:sync(JournalHdl),
notify_sync(State).
+-spec needs_sync(qistate()) -> 'confirms' | 'other' | 'false'.
+
needs_sync(#qistate{journal_handle = undefined}) ->
false;
needs_sync(#qistate{journal_handle = JournalHdl,
@@ -456,9 +456,16 @@ needs_sync(#qistate{journal_handle = JournalHdl,
false -> confirms
end.
+-spec flush(qistate()) -> qistate().
+
flush(State = #qistate { dirty_count = 0 }) -> State;
flush(State) -> flush_journal(State).
+-spec read(seq_id(), seq_id(), qistate()) ->
+ {[{rabbit_types:msg_id(), seq_id(),
+ rabbit_types:message_properties(),
+ boolean(), boolean()}], qistate()}.
+
read(StartEnd, StartEnd, State) ->
{[], State};
read(Start, End, State = #qistate { segments = Segments,
@@ -472,10 +479,15 @@ read(Start, End, State = #qistate { segments = Segments,
end, {[], Segments}, lists:seq(StartSeg, EndSeg)),
{Messages, State #qistate { segments = Segments1 }}.
+-spec next_segment_boundary(seq_id()) -> seq_id().
+
next_segment_boundary(SeqId) ->
{Seg, _RelSeq} = seq_id_to_seg_and_rel_seq_id(SeqId),
reconstruct_seq_id(Seg + 1, 0).
+-spec bounds(qistate()) ->
+ {non_neg_integer(), non_neg_integer(), qistate()}.
+
bounds(State = #qistate { segments = Segments }) ->
%% This is not particularly efficient, but only gets invoked on
%% queue initialisation.
@@ -498,6 +510,8 @@ bounds(State = #qistate { segments = Segments }) ->
end,
{LowSeqId, NextSeqId, State}.
+-spec start(rabbit_types:vhost(), [rabbit_amqqueue:name()]) -> {[[any()]], {walker(A), A}}.
+
start(VHost, DurableQueueNames) ->
ok = rabbit_recovery_terms:start(VHost),
{DurableTerms, DurableDirectories} =
@@ -1286,6 +1300,8 @@ journal_minus_segment1({no_pub, del, ack}, undefined) ->
%% upgrade
%%----------------------------------------------------------------------------
+-spec add_queue_ttl() -> 'ok'.
+
add_queue_ttl() ->
foreach_queue_index({fun add_queue_ttl_journal/1,
fun add_queue_ttl_segment/1}).
diff --git a/src/rabbit_queue_location_client_local.erl b/src/rabbit_queue_location_client_local.erl
index aa07637ab1..4c51bf52d2 100644
--- a/src/rabbit_queue_location_client_local.erl
+++ b/src/rabbit_queue_location_client_local.erl
@@ -17,7 +17,8 @@
-module(rabbit_queue_location_client_local).
-behaviour(rabbit_queue_master_locator).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-export([description/0, queue_master_location/1]).
@@ -37,4 +38,5 @@
description() ->
[{description, <<"Locate queue master node as the client local node">>}].
-queue_master_location(#amqqueue{}) -> {ok, node()}.
+queue_master_location(Q) when ?is_amqqueue(Q) ->
+ {ok, node()}.
diff --git a/src/rabbit_queue_location_min_masters.erl b/src/rabbit_queue_location_min_masters.erl
index a3a3021229..9462e15db9 100644
--- a/src/rabbit_queue_location_min_masters.erl
+++ b/src/rabbit_queue_location_min_masters.erl
@@ -17,7 +17,8 @@
-module(rabbit_queue_location_min_masters).
-behaviour(rabbit_queue_master_locator).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-export([description/0, queue_master_location/1]).
@@ -37,7 +38,7 @@ description() ->
[{description,
<<"Locate queue master node from cluster node with least bound queues">>}].
-queue_master_location(#amqqueue{} = Q) ->
+queue_master_location(Q) when ?is_amqqueue(Q) ->
Cluster = rabbit_queue_master_location_misc:all_nodes(Q),
QueueNames = rabbit_amqqueue:list_names(),
MastersPerNode = lists:foldl(
diff --git a/src/rabbit_queue_location_random.erl b/src/rabbit_queue_location_random.erl
index a92c178dfe..e166fa350d 100644
--- a/src/rabbit_queue_location_random.erl
+++ b/src/rabbit_queue_location_random.erl
@@ -17,7 +17,8 @@
-module(rabbit_queue_location_random).
-behaviour(rabbit_queue_master_locator).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-export([description/0, queue_master_location/1]).
@@ -37,7 +38,7 @@ description() ->
[{description,
<<"Locate queue master node from cluster in a random manner">>}].
-queue_master_location(#amqqueue{} = Q) ->
+queue_master_location(Q) when ?is_amqqueue(Q) ->
Cluster = rabbit_queue_master_location_misc:all_nodes(Q),
RandomPos = erlang:phash2(erlang:monotonic_time(), length(Cluster)),
MasterNode = lists:nth(RandomPos + 1, Cluster),
diff --git a/src/rabbit_queue_location_validator.erl b/src/rabbit_queue_location_validator.erl
index 68a4bc3e06..2c7bb41b7e 100644
--- a/src/rabbit_queue_location_validator.erl
+++ b/src/rabbit_queue_location_validator.erl
@@ -17,7 +17,8 @@
-module(rabbit_queue_location_validator).
-behaviour(rabbit_policy_validator).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-export([validate_policy/1, validate_strategy/1]).
@@ -49,7 +50,7 @@ policy(Policy, Q) ->
P -> P
end.
-module(#amqqueue{} = Q) ->
+module(Q) when ?is_amqqueue(Q) ->
case policy(<<"queue-master-locator">>, Q) of
undefined -> no_location_strategy;
Mode -> module(Mode)
diff --git a/src/rabbit_queue_master_location_misc.erl b/src/rabbit_queue_master_location_misc.erl
index 6df7e5db6f..b31fd50192 100644
--- a/src/rabbit_queue_master_location_misc.erl
+++ b/src/rabbit_queue_master_location_misc.erl
@@ -16,7 +16,8 @@
-module(rabbit_queue_master_location_misc).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include("amqqueue.hrl").
-export([lookup_master/2,
lookup_queue/2,
@@ -28,22 +29,25 @@
lookup_master(QueueNameBin, VHostPath) when is_binary(QueueNameBin),
is_binary(VHostPath) ->
- Queue = rabbit_misc:r(VHostPath, queue, QueueNameBin),
- case rabbit_amqqueue:lookup(Queue) of
- {ok, #amqqueue{pid = Pid}} when is_pid(Pid) ->
+ QueueR = rabbit_misc:r(VHostPath, queue, QueueNameBin),
+ case rabbit_amqqueue:lookup(QueueR) of
+ {ok, Queue} when ?amqqueue_has_valid_pid(Queue) ->
+ Pid = amqqueue:get_pid(Queue),
{ok, node(Pid)};
Error -> Error
end.
lookup_queue(QueueNameBin, VHostPath) when is_binary(QueueNameBin),
is_binary(VHostPath) ->
- Queue = rabbit_misc:r(VHostPath, queue, QueueNameBin),
- case rabbit_amqqueue:lookup(Queue) of
- Reply = {ok, #amqqueue{}} -> Reply;
- Error -> Error
+ QueueR = rabbit_misc:r(VHostPath, queue, QueueNameBin),
+ case rabbit_amqqueue:lookup(QueueR) of
+ Reply = {ok, Queue} when ?is_amqqueue(Queue) ->
+ Reply;
+ Error ->
+ Error
end.
-get_location(Queue=#amqqueue{})->
+get_location(Queue) when ?is_amqqueue(Queue) ->
Reply1 = case get_location_mod_by_args(Queue) of
_Err1 = {error, _} ->
case get_location_mod_by_policy(Queue) of
@@ -62,7 +66,8 @@ get_location(Queue=#amqqueue{})->
Error -> Error
end.
-get_location_mod_by_args(#amqqueue{arguments=Args}) ->
+get_location_mod_by_args(Queue) when ?is_amqqueue(Queue) ->
+ Args = amqqueue:get_arguments(Queue),
case rabbit_misc:table_lookup(Args, <<"x-queue-master-locator">>) of
{_Type, Strategy} ->
case rabbit_queue_location_validator:validate_strategy(Strategy) of
@@ -72,7 +77,7 @@ get_location_mod_by_args(#amqqueue{arguments=Args}) ->
_ -> {error, "x-queue-master-locator undefined"}
end.
-get_location_mod_by_policy(Queue=#amqqueue{}) ->
+get_location_mod_by_policy(Queue) when ?is_amqqueue(Queue) ->
case rabbit_policy:get(<<"queue-master-locator">> , Queue) of
undefined -> {error, "queue-master-locator policy undefined"};
Strategy ->
@@ -82,7 +87,7 @@ get_location_mod_by_policy(Queue=#amqqueue{}) ->
end
end.
-get_location_mod_by_config(#amqqueue{}) ->
+get_location_mod_by_config(Queue) when ?is_amqqueue(Queue) ->
case application:get_env(rabbit, queue_master_locator) of
{ok, Strategy} ->
case rabbit_queue_location_validator:validate_strategy(Strategy) of
@@ -92,7 +97,7 @@ get_location_mod_by_config(#amqqueue{}) ->
_ -> {error, "queue_master_locator undefined"}
end.
-all_nodes(Queue = #amqqueue{}) ->
+all_nodes(Queue) when ?is_amqqueue(Queue) ->
handle_is_mirrored_ha_nodes(rabbit_mirror_queue_misc:is_mirrored_ha_nodes(Queue), Queue).
handle_is_mirrored_ha_nodes(false, _Queue) ->
diff --git a/src/rabbit_queue_master_locator.erl b/src/rabbit_queue_master_locator.erl
new file mode 100644
index 0000000000..eba1e2aefa
--- /dev/null
+++ b/src/rabbit_queue_master_locator.erl
@@ -0,0 +1,28 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License at
+%% http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+%% License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2007-2017 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(rabbit_queue_master_locator).
+
+-behaviour(rabbit_registry_class).
+
+-export([added_to_rabbit_registry/2, removed_from_rabbit_registry/1]).
+
+-callback description() -> [proplists:property()].
+-callback queue_master_location(amqqueue:amqqueue()) ->
+ {'ok', node()} | {'error', term()}.
+
+added_to_rabbit_registry(_Type, _ModuleName) -> ok.
+removed_from_rabbit_registry(_Type) -> ok.
diff --git a/src/rabbit_quorum_queue.erl b/src/rabbit_quorum_queue.erl
index 5c061a529d..f83ad11b50 100644
--- a/src/rabbit_quorum_queue.erl
+++ b/src/rabbit_quorum_queue.erl
@@ -41,31 +41,11 @@
%%-include_lib("rabbit_common/include/rabbit.hrl").
-include_lib("rabbit.hrl").
-include_lib("stdlib/include/qlc.hrl").
+-include("amqqueue.hrl").
--type ra_server_id() :: {Name :: atom(), Node :: node()}.
-type msg_id() :: non_neg_integer().
-type qmsg() :: {rabbit_types:r('queue'), pid(), msg_id(), boolean(), rabbit_types:message()}.
--spec handle_event({'ra_event', ra_server_id(), any()}, rabbit_fifo_client:state()) ->
- {'internal', Correlators :: [term()], rabbit_fifo_client:state()} |
- {rabbit_fifo:client_msg(), rabbit_fifo_client:state()}.
--spec recover([rabbit_types:amqqueue()]) -> [rabbit_types:amqqueue() |
- {'absent', rabbit_types:amqqueue(), atom()}].
--spec stop(rabbit_types:vhost()) -> 'ok'.
--spec ack(rabbit_types:ctag(), [msg_id()], rabbit_fifo_client:state()) ->
- {'ok', rabbit_fifo_client:state()}.
--spec reject(Confirm :: boolean(), rabbit_types:ctag(), [msg_id()], rabbit_fifo_client:state()) ->
- {'ok', rabbit_fifo_client:state()}.
--spec basic_cancel(rabbit_types:ctag(), ChPid :: pid(), any(), rabbit_fifo_client:state()) ->
- {'ok', rabbit_fifo_client:state()}.
--spec stateless_deliver(ra_server_id(), rabbit_types:delivery()) -> 'ok'.
--spec info(rabbit_types:amqqueue()) -> rabbit_types:infos().
--spec info(rabbit_types:amqqueue(), rabbit_types:info_keys()) -> rabbit_types:infos().
--spec infos(rabbit_types:r('queue')) -> rabbit_types:infos().
--spec stat(rabbit_types:amqqueue()) -> {'ok', non_neg_integer(), non_neg_integer()}.
--spec cluster_state(Name :: atom()) -> 'down' | 'recovering' | 'running'.
--spec status(rabbit_types:vhost(), Name :: rabbit_misc:resource_name()) -> rabbit_types:infos() | {error, term()}.
-
-define(STATISTICS_KEYS,
[policy,
operator_policy,
@@ -87,14 +67,15 @@
%%----------------------------------------------------------------------------
--spec init_state(ra_server_id(), rabbit_types:r('queue')) ->
- rabbit_fifo_client:state().
+-spec init_state(amqqueue:ra_server_id(), rabbit_amqqueue:name()) ->
+ rabbit_fifo_client:state().
init_state({Name, _}, QName = #resource{}) ->
{ok, SoftLimit} = application:get_env(rabbit, quorum_commands_soft_limit),
%% This lookup could potentially return an {error, not_found}, but we do not
%% know what to do if the queue has `disappeared`. Let it crash.
- {ok, #amqqueue{pid = Leader, quorum_nodes = Nodes}} =
- rabbit_amqqueue:lookup(QName),
+ {ok, Q} = rabbit_amqqueue:lookup(QName),
+ Leader = amqqueue:get_pid(Q),
+ Nodes = amqqueue:get_quorum_nodes(Q),
%% Ensure the leader is listed first
Servers0 = [{Name, N} || N <- Nodes],
Servers = [Leader | lists:delete(Leader, Servers0)],
@@ -102,17 +83,22 @@ init_state({Name, _}, QName = #resource{}) ->
fun() -> credit_flow:block(Name), ok end,
fun() -> credit_flow:unblock(Name), ok end).
+-spec handle_event({'ra_event', amqqueue:ra_server_id(), any()}, rabbit_fifo_client:state()) ->
+ {'internal', Correlators :: [term()], rabbit_fifo_client:state()} |
+ {rabbit_fifo:client_msg(), rabbit_fifo_client:state()}.
+
handle_event({ra_event, From, Evt}, QState) ->
rabbit_fifo_client:handle_ra_event(From, Evt, QState).
--spec declare(rabbit_types:amqqueue()) ->
- {'new', rabbit_types:amqqueue()} |
- {existing, rabbit_types:amqqueue()}.
-declare(#amqqueue{name = QName,
- durable = Durable,
- auto_delete = AutoDelete,
- arguments = Arguments,
- options = Opts} = Q) ->
+-spec declare(amqqueue:amqqueue()) ->
+ {new | existing, amqqueue:amqqueue()} | rabbit_types:channel_exit().
+
+declare(Q) when ?amqqueue_is_quorum(Q) ->
+ QName = amqqueue:get_name(Q),
+ Durable = amqqueue:is_durable(Q),
+ AutoDelete = amqqueue:is_auto_delete(Q),
+ Arguments = amqqueue:get_arguments(Q),
+ Opts = amqqueue:get_options(Q),
ActingUser = maps:get(user, Opts, ?UNKNOWN_USER),
check_invalid_arguments(QName, Arguments),
check_auto_delete(Q),
@@ -122,9 +108,9 @@ declare(#amqqueue{name = QName,
RaName = qname_to_rname(QName),
Id = {RaName, node()},
Nodes = select_quorum_nodes(QuorumSize, rabbit_mnesia:cluster_nodes(all)),
- NewQ0 = Q#amqqueue{pid = Id,
- quorum_nodes = Nodes},
- case rabbit_amqqueue:internal_declare(NewQ0, false) of
+ NewQ0 = amqqueue:set_pid(Q, Id),
+ NewQ1 = amqqueue:set_quorum_nodes(NewQ0, Nodes),
+ case rabbit_amqqueue:internal_declare(NewQ1, false) of
{created, NewQ} ->
RaMachine = ra_machine(NewQ),
case ra:start_cluster(RaName, RaMachine,
@@ -150,8 +136,9 @@ declare(#amqqueue{name = QName,
ra_machine(Q) ->
{module, rabbit_fifo, ra_machine_config(Q)}.
-ra_machine_config(Q = #amqqueue{name = QName,
- pid = {Name, _}}) ->
+ra_machine_config(Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
+ {Name, _} = amqqueue:get_pid(Q),
%% take the minimum value of the policy and the queue arg if present
MaxLength = args_policy_lookup(<<"max-length">>, fun min/2, Q),
MaxBytes = args_policy_lookup(<<"max-length-bytes">>, fun min/2, Q),
@@ -163,7 +150,8 @@ ra_machine_config(Q = #amqqueue{name = QName,
max_bytes => MaxBytes,
single_active_consumer_on => single_active_consumer_on(Q)}.
-single_active_consumer_on(#amqqueue{arguments = QArguments}) ->
+single_active_consumer_on(Q) ->
+ QArguments = amqqueue:get_arguments(Q),
case rabbit_misc:table_lookup(QArguments, <<"x-single-active-consumer">>) of
{bool, true} -> true;
_ -> false
@@ -200,9 +188,10 @@ local_or_remote_handler(ChPid, Module, Function, Args) ->
end.
become_leader(QName, Name) ->
- Fun = fun(Q1) ->
- Q1#amqqueue{pid = {Name, node()},
- state = live}
+ Fun = fun (Q1) ->
+ amqqueue:set_state(
+ amqqueue:set_pid(Q1, {Name, node()}),
+ live)
end,
%% as this function is called synchronously when a ra node becomes leader
%% we need to ensure there is no chance of blocking as else the ra node
@@ -213,7 +202,8 @@ become_leader(QName, Name) ->
rabbit_amqqueue:update(QName, Fun)
end),
case rabbit_amqqueue:lookup(QName) of
- {ok, #amqqueue{quorum_nodes = Nodes}} ->
+ {ok, Q0} when ?is_amqqueue(Q0) ->
+ Nodes = amqqueue:get_quorum_nodes(Q0),
[rpc:call(Node, ?MODULE, rpc_delete_metrics,
[QName], ?TICK_TIME)
|| Node <- Nodes, Node =/= node()];
@@ -261,16 +251,20 @@ reductions(Name) ->
0
end.
+-spec recover([amqqueue:amqqueue()]) -> [amqqueue:amqqueue() |
+ {'absent', amqqueue:amqqueue(), atom()}].
+
recover(Queues) ->
[begin
+ {Name, _} = amqqueue:get_pid(Q0),
+ Nodes = amqqueue:get_quorum_nodes(Q0),
case ra:restart_server({Name, node()}) of
ok ->
-
% queue was restarted, good
ok;
- {error, Err}
- when Err == not_started orelse
- Err == name_not_registered ->
+ {error, Err1}
+ when Err1 == not_started orelse
+ Err1 == name_not_registered ->
% queue was never started on this node
% so needs to be started from scratch.
Machine = ra_machine(Q0),
@@ -298,20 +292,27 @@ recover(Queues) ->
%% So many code paths are dependent on this.
{ok, Q} = rabbit_amqqueue:ensure_rabbit_queue_record_is_initialized(Q0),
Q
- end || #amqqueue{pid = {Name, _},
- quorum_nodes = Nodes} = Q0 <- Queues].
+ end || Q0 <- Queues].
+
+-spec stop(rabbit_types:vhost()) -> 'ok'.
stop(VHost) ->
- _ = [ra:stop_server(Pid) || #amqqueue{pid = Pid} <- find_quorum_queues(VHost)],
+ _ = [begin
+ Pid = amqqueue:get_pid(Q),
+ ra:stop_server(Pid)
+ end || Q <- find_quorum_queues(VHost)],
ok.
--spec delete(#amqqueue{},
+-spec delete(amqqueue:amqqueue(),
boolean(), boolean(),
rabbit_types:username()) ->
{ok, QLen :: non_neg_integer()}.
-delete(#amqqueue{type = quorum, pid = {Name, _},
- name = QName, quorum_nodes = QNodes} = Q,
- _IfUnused, _IfEmpty, ActingUser) ->
+
+delete(Q,
+ _IfUnused, _IfEmpty, ActingUser) when ?amqqueue_is_quorum(Q) ->
+ {Name, _} = amqqueue:get_pid(Q),
+ QName = amqqueue:get_name(Q),
+ QNodes = amqqueue:get_quorum_nodes(Q),
%% TODO Quorum queue needs to support consumer tracking for IfUnused
Timeout = ?DELETE_TIMEOUT,
{ok, ReadyMsgs, _} = stat(Q),
@@ -377,9 +378,15 @@ delete_immediately(Resource, {_Name, _} = QPid) ->
rabbit_core_metrics:queue_deleted(Resource),
ok.
+-spec ack(rabbit_types:ctag(), [msg_id()], rabbit_fifo_client:state()) ->
+ {'ok', rabbit_fifo_client:state()}.
+
ack(CTag, MsgIds, QState) ->
rabbit_fifo_client:settle(quorum_ctag(CTag), MsgIds, QState).
+-spec reject(Confirm :: boolean(), rabbit_types:ctag(), [msg_id()], rabbit_fifo_client:state()) ->
+ {'ok', rabbit_fifo_client:state()}.
+
reject(true, CTag, MsgIds, QState) ->
rabbit_fifo_client:return(quorum_ctag(CTag), MsgIds, QState);
reject(false, CTag, MsgIds, QState) ->
@@ -388,12 +395,15 @@ reject(false, CTag, MsgIds, QState) ->
credit(CTag, Credit, Drain, QState) ->
rabbit_fifo_client:credit(quorum_ctag(CTag), Credit, Drain, QState).
--spec basic_get(#amqqueue{}, NoAck :: boolean(), rabbit_types:ctag(),
+-spec basic_get(amqqueue:amqqueue(), NoAck :: boolean(), rabbit_types:ctag(),
rabbit_fifo_client:state()) ->
{'ok', 'empty', rabbit_fifo_client:state()} |
- {'ok', QLen :: non_neg_integer(), qmsg(), rabbit_fifo_client:state()}.
-basic_get(#amqqueue{name = QName, pid = Id, type = quorum}, NoAck,
- CTag0, QState0) ->
+ {'ok', QLen :: non_neg_integer(), qmsg(), rabbit_fifo_client:state()} |
+ {error, timeout | term()}.
+
+basic_get(Q, NoAck, CTag0, QState0) when ?amqqueue_is_quorum(Q) ->
+ QName = amqqueue:get_name(Q),
+ Id = amqqueue:get_pid(Q),
CTag = quorum_ctag(CTag0),
Settlement = case NoAck of
true ->
@@ -409,21 +419,26 @@ basic_get(#amqqueue{name = QName, pid = Id, type = quorum}, NoAck,
IsDelivered = Count > 0,
Msg = rabbit_basic:add_header(<<"x-delivery-count">>, long, Count, Msg0),
{ok, MsgsReady, {QName, Id, MsgId, IsDelivered, Msg}, QState};
+ {error, _} = Err ->
+ Err;
{timeout, _} ->
{error, timeout}
end.
--spec basic_consume(rabbit_types:amqqueue(), NoAck :: boolean(), ChPid :: pid(),
+-spec basic_consume(amqqueue:amqqueue(), NoAck :: boolean(), ChPid :: pid(),
ConsumerPrefetchCount :: non_neg_integer(),
rabbit_types:ctag(), ExclusiveConsume :: boolean(),
Args :: rabbit_framing:amqp_table(), ActingUser :: binary(),
any(), rabbit_fifo_client:state()) ->
{'ok', rabbit_fifo_client:state()}.
-basic_consume(#amqqueue{name = QName, pid = QPid, type = quorum} = Q, NoAck, ChPid,
+
+basic_consume(Q, NoAck, ChPid,
ConsumerPrefetchCount, ConsumerTag0, ExclusiveConsume, Args,
- ActingUser, OkMsg, QState0) ->
+ ActingUser, OkMsg, QState0) when ?amqqueue_is_quorum(Q) ->
%% TODO: validate consumer arguments
%% currently quorum queues do not support any arguments
+ QName = amqqueue:get_name(Q),
+ QPid = amqqueue:get_pid(Q),
maybe_send_reply(ChPid, OkMsg),
ConsumerTag = quorum_ctag(ConsumerTag0),
%% A prefetch count of 0 means no limitation,
@@ -461,10 +476,15 @@ basic_consume(#amqqueue{name = QName, pid = QPid, type = quorum} = Q, NoAck, ChP
ActivityStatus, Args),
{ok, QState}.
+-spec basic_cancel(rabbit_types:ctag(), ChPid :: pid(), any(), rabbit_fifo_client:state()) ->
+ {'ok', rabbit_fifo_client:state()}.
+
basic_cancel(ConsumerTag, ChPid, OkMsg, QState0) ->
maybe_send_reply(ChPid, OkMsg),
rabbit_fifo_client:cancel_checkout(quorum_ctag(ConsumerTag), QState0).
+-spec stateless_deliver(amqqueue:ra_server_id(), rabbit_types:delivery()) -> 'ok'.
+
stateless_deliver(ServerId, Delivery) ->
ok = rabbit_fifo_client:untracked_enqueue([ServerId],
Delivery#delivery.message).
@@ -472,16 +492,21 @@ stateless_deliver(ServerId, Delivery) ->
-spec deliver(Confirm :: boolean(), rabbit_types:delivery(),
rabbit_fifo_client:state()) ->
{ok | slow, rabbit_fifo_client:state()}.
+
deliver(false, Delivery, QState0) ->
rabbit_fifo_client:enqueue(Delivery#delivery.message, QState0);
deliver(true, Delivery, QState0) ->
rabbit_fifo_client:enqueue(Delivery#delivery.msg_seq_no,
Delivery#delivery.message, QState0).
+-spec info(amqqueue:amqqueue()) -> rabbit_types:infos().
+
info(Q) ->
info(Q, [name, durable, auto_delete, arguments, pid, state, messages,
messages_ready, messages_unacknowledged]).
+-spec infos(rabbit_types:r('queue')) -> rabbit_types:infos().
+
infos(QName) ->
case rabbit_amqqueue:lookup(QName) of
{ok, Q} ->
@@ -490,10 +515,15 @@ infos(QName) ->
[]
end.
+-spec info(amqqueue:amqqueue(), rabbit_types:info_keys()) -> rabbit_types:infos().
+
info(Q, Items) ->
[{Item, i(Item, Q)} || Item <- Items].
-stat(#amqqueue{pid = Leader}) ->
+-spec stat(amqqueue:amqqueue()) -> {'ok', non_neg_integer(), non_neg_integer()}.
+
+stat(Q) when ?is_amqqueue(Q) ->
+ Leader = amqqueue:get_pid(Q),
try
case rabbit_fifo_client:stat(Leader) of
{ok, _, _} = Stat ->
@@ -514,9 +544,12 @@ requeue(ConsumerTag, MsgIds, QState) ->
rabbit_fifo_client:return(quorum_ctag(ConsumerTag), MsgIds, QState).
cleanup_data_dir() ->
- Names = [Name || #amqqueue{pid = {Name, _}, quorum_nodes = Nodes}
- <- rabbit_amqqueue:list_by_type(quorum),
- lists:member(node(), Nodes)],
+ Names = [begin
+ {Name, _} = amqqueue:get_pid(Q),
+ Name
+ end
+ || Q <- rabbit_amqqueue:list_by_type(quorum),
+ lists:member(node(), amqqueue:get_quorum_nodes(Q))],
Registered = ra_directory:list_registered(),
_ = [maybe_delete_data_dir(UId) || {Name, UId} <- Registered,
not lists:member(Name, Names)],
@@ -537,6 +570,8 @@ policy_changed(QName, Node) ->
{ok, Q} = rabbit_amqqueue:lookup(QName),
rabbit_fifo_client:update_machine_state(Node, ra_machine_config(Q)).
+-spec cluster_state(Name :: atom()) -> 'down' | 'recovering' | 'running'.
+
cluster_state(Name) ->
case whereis(Name) of
undefined -> down;
@@ -547,14 +582,18 @@ cluster_state(Name) ->
end
end.
+-spec status(rabbit_types:vhost(), Name :: rabbit_misc:resource_name()) -> rabbit_types:infos() | {error, term()}.
+
status(Vhost, QueueName) ->
%% Handle not found queues
QName = #resource{virtual_host = Vhost, name = QueueName, kind = queue},
RName = qname_to_rname(QName),
case rabbit_amqqueue:lookup(QName) of
- {ok, #amqqueue{type = classic}} ->
+ {ok, Q} when ?amqqueue_is_classic(Q) ->
{error, classic_queue_not_supported};
- {ok, #amqqueue{pid = {_, Leader}, quorum_nodes = Nodes}} ->
+ {ok, Q} when ?amqqueue_is_quorum(Q) ->
+ {_, Leader} = amqqueue:get_pid(Q),
+ Nodes = amqqueue:get_quorum_nodes(Q),
Info = [{leader, Leader}, {members, Nodes}],
case ets:lookup(ra_state, RName) of
[{_, State}] ->
@@ -569,9 +608,10 @@ status(Vhost, QueueName) ->
add_member(VHost, Name, Node) ->
QName = #resource{virtual_host = VHost, name = Name, kind = queue},
case rabbit_amqqueue:lookup(QName) of
- {ok, #amqqueue{type = classic}} ->
+ {ok, Q} when ?amqqueue_is_classic(Q) ->
{error, classic_queue_not_supported};
- {ok, #amqqueue{quorum_nodes = QNodes} = Q} ->
+ {ok, Q} when ?amqqueue_is_quorum(Q) ->
+ QNodes = amqqueue:get_quorum_nodes(Q),
case lists:member(Node, rabbit_mnesia:cluster_nodes(running)) of
false ->
{error, node_not_running};
@@ -587,8 +627,10 @@ add_member(VHost, Name, Node) ->
E
end.
-add_member(#amqqueue{pid = {RaName, _} = ServerRef, name = QName,
- quorum_nodes = QNodes} = Q, Node) ->
+add_member(Q, Node) when ?amqqueue_is_quorum(Q) ->
+ {RaName, _} = ServerRef = amqqueue:get_pid(Q),
+ QName = amqqueue:get_name(Q),
+ QNodes = amqqueue:get_quorum_nodes(Q),
%% TODO parallel calls might crash this, or add a duplicate in quorum_nodes
ServerId = {RaName, Node},
case ra:start_server(RaName, ServerId, ra_machine(Q),
@@ -597,9 +639,10 @@ add_member(#amqqueue{pid = {RaName, _} = ServerRef, name = QName,
case ra:add_member(ServerRef, ServerId) of
{ok, _, Leader} ->
Fun = fun(Q1) ->
- Q1#amqqueue{quorum_nodes =
- [Node | Q1#amqqueue.quorum_nodes],
- pid = Leader}
+ Q2 = amqqueue:set_quorum_nodes(
+ Q1,
+ [Node | amqqueue:get_quorum_nodes(Q1)]),
+ amqqueue:set_pid(Q2, Leader)
end,
rabbit_misc:execute_mnesia_transaction(
fun() -> rabbit_amqqueue:update(QName, Fun) end),
@@ -615,9 +658,10 @@ add_member(#amqqueue{pid = {RaName, _} = ServerRef, name = QName,
delete_member(VHost, Name, Node) ->
QName = #resource{virtual_host = VHost, name = Name, kind = queue},
case rabbit_amqqueue:lookup(QName) of
- {ok, #amqqueue{type = classic}} ->
+ {ok, Q} when ?amqqueue_is_classic(Q) ->
{error, classic_queue_not_supported};
- {ok, #amqqueue{quorum_nodes = QNodes} = Q} ->
+ {ok, Q} when ?amqqueue_is_quorum(Q) ->
+ QNodes = amqqueue:get_quorum_nodes(Q),
case lists:member(Node, rabbit_mnesia:cluster_nodes(running)) of
false ->
{error, node_not_running};
@@ -633,13 +677,16 @@ delete_member(VHost, Name, Node) ->
E
end.
-delete_member(#amqqueue{pid = {RaName, _}, name = QName}, Node) ->
+delete_member(Q, Node) when ?amqqueue_is_quorum(Q) ->
+ QName = amqqueue:get_name(Q),
+ {RaName, _} = amqqueue:get_pid(Q),
ServerId = {RaName, Node},
case ra:leave_and_delete_server(ServerId) of
ok ->
Fun = fun(Q1) ->
- Q1#amqqueue{quorum_nodes =
- lists:delete(Node, Q1#amqqueue.quorum_nodes)}
+ amqqueue:set_quorum_nodes(
+ Q1,
+ lists:delete(Node, amqqueue:get_quorum_nodes(Q1)))
end,
rabbit_misc:execute_mnesia_transaction(
fun() -> rabbit_amqqueue:update(QName, Fun) end),
@@ -654,16 +701,18 @@ dlx_mfa(Q) ->
fun res_arg/2, Q), Q),
DLXRKey = args_policy_lookup(<<"dead-letter-routing-key">>,
fun res_arg/2, Q),
- {?MODULE, dead_letter_publish, [DLX, DLXRKey, Q#amqqueue.name]}.
+ {?MODULE, dead_letter_publish, [DLX, DLXRKey, amqqueue:get_name(Q)]}.
init_dlx(undefined, _Q) ->
undefined;
-init_dlx(DLX, #amqqueue{name = QName}) ->
+init_dlx(DLX, Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
rabbit_misc:r(QName, exchange, DLX).
res_arg(_PolVal, ArgVal) -> ArgVal.
-args_policy_lookup(Name, Resolve, Q = #amqqueue{arguments = Args}) ->
+args_policy_lookup(Name, Resolve, Q) when ?is_amqqueue(Q) ->
+ Args = amqqueue:get_arguments(Q),
AName = <<"x-", Name/binary>>,
case {rabbit_policy:get(Name, Q), rabbit_misc:table_lookup(Args, AName)} of
{undefined, undefined} -> undefined;
@@ -693,29 +742,32 @@ find_quorum_queues(VHost) ->
Node = node(),
mnesia:async_dirty(
fun () ->
- qlc:e(qlc:q([Q || Q = #amqqueue{vhost = VH,
- pid = Pid,
- type = quorum}
- <- mnesia:table(rabbit_durable_queue),
- VH =:= VHost,
- qnode(Pid) == Node]))
+ qlc:e(qlc:q([Q || Q <- mnesia:table(rabbit_durable_queue),
+ ?amqqueue_is_quorum(Q),
+ amqqueue:get_vhost(Q) =:= VHost,
+ amqqueue:qnode(Q) == Node]))
end).
-i(name, #amqqueue{name = Name}) -> Name;
-i(durable, #amqqueue{durable = Dur}) -> Dur;
-i(auto_delete, #amqqueue{auto_delete = AD}) -> AD;
-i(arguments, #amqqueue{arguments = Args}) -> Args;
-i(pid, #amqqueue{pid = {Name, _}}) -> whereis(Name);
-i(messages, #amqqueue{name = Name}) ->
- quorum_messages(Name);
-i(messages_ready, #amqqueue{name = QName}) ->
+i(name, Q) when ?is_amqqueue(Q) -> amqqueue:get_name(Q);
+i(durable, Q) when ?is_amqqueue(Q) -> amqqueue:is_durable(Q);
+i(auto_delete, Q) when ?is_amqqueue(Q) -> amqqueue:is_auto_delete(Q);
+i(arguments, Q) when ?is_amqqueue(Q) -> amqqueue:get_arguments(Q);
+i(pid, Q) when ?is_amqqueue(Q) ->
+ {Name, _} = amqqueue:get_pid(Q),
+ whereis(Name);
+i(messages, Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
+ quorum_messages(QName);
+i(messages_ready, Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
case ets:lookup(queue_coarse_metrics, QName) of
[{_, MR, _, _, _}] ->
MR;
[] ->
0
end;
-i(messages_unacknowledged, #amqqueue{name = QName}) ->
+i(messages_unacknowledged, Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
case ets:lookup(queue_coarse_metrics, QName) of
[{_, _, MU, _, _}] ->
MU;
@@ -737,14 +789,16 @@ i(effective_policy_definition, Q) ->
undefined -> [];
Def -> Def
end;
-i(consumers, #amqqueue{name = QName}) ->
+i(consumers, Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
case ets:lookup(queue_metrics, QName) of
[{_, M, _}] ->
proplists:get_value(consumers, M, 0);
[] ->
0
end;
-i(memory, #amqqueue{pid = {Name, _}}) ->
+i(memory, Q) when ?is_amqqueue(Q) ->
+ {Name, _} = amqqueue:get_pid(Q),
try
{memory, M} = process_info(whereis(Name), memory),
M
@@ -752,33 +806,38 @@ i(memory, #amqqueue{pid = {Name, _}}) ->
error:badarg ->
0
end;
-i(state, #amqqueue{pid = {Name, Node}}) ->
+i(state, Q) when ?is_amqqueue(Q) ->
+ {Name, Node} = amqqueue:get_pid(Q),
%% Check against the leader or last known leader
case rpc:call(Node, ?MODULE, cluster_state, [Name], ?TICK_TIME) of
{badrpc, _} -> down;
State -> State
end;
-i(local_state, #amqqueue{pid = {Name, _}}) ->
+i(local_state, Q) when ?is_amqqueue(Q) ->
+ {Name, _} = amqqueue:get_pid(Q),
case ets:lookup(ra_state, Name) of
[{_, State}] -> State;
_ -> not_member
end;
-i(garbage_collection, #amqqueue{pid = {Name, _}}) ->
+i(garbage_collection, Q) when ?is_amqqueue(Q) ->
+ {Name, _} = amqqueue:get_pid(Q),
try
rabbit_misc:get_gc_info(whereis(Name))
catch
error:badarg ->
[]
end;
-i(members, #amqqueue{quorum_nodes = Nodes}) ->
- Nodes;
+i(members, Q) when ?is_amqqueue(Q) ->
+ amqqueue:get_quorum_nodes(Q);
i(online, Q) -> online(Q);
i(leader, Q) -> leader(Q);
-i(open_files, #amqqueue{pid = {Name, _},
- quorum_nodes = Nodes}) ->
+i(open_files, Q) when ?is_amqqueue(Q) ->
+ {Name, _} = amqqueue:get_pid(Q),
+ Nodes = amqqueue:get_quorum_nodes(Q),
{Data, _} = rpc:multicall(Nodes, rabbit_quorum_queue, open_files, [Name]),
lists:flatten(Data);
-i(single_active_consumer_pid, #amqqueue{pid = QPid}) ->
+i(single_active_consumer_pid, Q) when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
{ok, {_, SacResult}, _} = ra:local_query(QPid,
fun rabbit_fifo:query_single_active_consumer/1),
case SacResult of
@@ -787,7 +846,8 @@ i(single_active_consumer_pid, #amqqueue{pid = QPid}) ->
_ ->
''
end;
-i(single_active_consumer_ctag, #amqqueue{pid = QPid}) ->
+i(single_active_consumer_ctag, Q) when ?is_amqqueue(Q) ->
+ QPid = amqqueue:get_pid(Q),
{ok, {_, SacResult}, _} = ra:local_query(QPid,
fun rabbit_fifo:query_single_active_consumer/1),
case SacResult of
@@ -807,22 +867,27 @@ open_files(Name) ->
end
end.
-leader(#amqqueue{pid = {Name, Leader}}) ->
+leader(Q) when ?is_amqqueue(Q) ->
+ {Name, Leader} = amqqueue:get_pid(Q),
case is_process_alive(Name, Leader) of
true -> Leader;
false -> ''
end.
-online(#amqqueue{quorum_nodes = Nodes,
- pid = {Name, _Leader}}) ->
+online(Q) when ?is_amqqueue(Q) ->
+ Nodes = amqqueue:get_quorum_nodes(Q),
+ {Name, _} = amqqueue:get_pid(Q),
[Node || Node <- Nodes, is_process_alive(Name, Node)].
-format(#amqqueue{quorum_nodes = Nodes} = Q) ->
+format(Q) when ?is_amqqueue(Q) ->
+ Nodes = amqqueue:get_quorum_nodes(Q),
[{members, Nodes}, {online, online(Q)}, {leader, leader(Q)}].
is_process_alive(Name, Node) ->
erlang:is_pid(rpc:call(Node, erlang, whereis, [Name], ?TICK_TIME)).
+-spec quorum_messages(atom()) -> non_neg_integer().
+
quorum_messages(QName) ->
case ets:lookup(queue_coarse_metrics, QName) of
[{_, _, _, M, _}] ->
@@ -839,11 +904,6 @@ quorum_ctag(Other) ->
maybe_send_reply(_ChPid, undefined) -> ok;
maybe_send_reply(ChPid, Msg) -> ok = rabbit_channel:send_command(ChPid, Msg).
-qnode(QPid) when is_pid(QPid) ->
- node(QPid);
-qnode({_, Node}) ->
- Node.
-
check_invalid_arguments(QueueName, Args) ->
Keys = [<<"x-expires">>, <<"x-message-ttl">>,
<<"x-max-priority">>, <<"x-queue-mode">>, <<"x-overflow">>],
@@ -856,7 +916,8 @@ check_invalid_arguments(QueueName, Args) ->
end || Key <- Keys],
ok.
-check_auto_delete(#amqqueue{auto_delete = true, name = Name}) ->
+check_auto_delete(Q) when ?amqqueue_is_auto_delete(Q) ->
+ Name = amqqueue:get_name(Q),
rabbit_misc:protocol_error(
precondition_failed,
"invalid property 'auto-delete' for ~s",
@@ -864,18 +925,19 @@ check_auto_delete(#amqqueue{auto_delete = true, name = Name}) ->
check_auto_delete(_) ->
ok.
-check_exclusive(#amqqueue{exclusive_owner = none}) ->
+check_exclusive(Q) when ?amqqueue_exclusive_owner_is(Q, none) ->
ok;
-check_exclusive(#amqqueue{name = Name}) ->
+check_exclusive(Q) when ?is_amqqueue(Q) ->
+ Name = amqqueue:get_name(Q),
rabbit_misc:protocol_error(
precondition_failed,
"invalid property 'exclusive-owner' for ~s",
[rabbit_misc:rs(Name)]).
-check_non_durable(#amqqueue{durable = true}) ->
+check_non_durable(Q) when ?amqqueue_is_durable(Q) ->
ok;
-check_non_durable(#amqqueue{name = Name,
- durable = false}) ->
+check_non_durable(Q) when not ?amqqueue_is_durable(Q) ->
+ Name = amqqueue:get_name(Q),
rabbit_misc:protocol_error(
precondition_failed,
"invalid property 'non-durable' for ~s",
diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl
index 9a4c143c54..95a6b185c2 100644
--- a/src/rabbit_reader.erl
+++ b/src/rabbit_reader.erl
@@ -57,7 +57,7 @@
-include("rabbit_framing.hrl").
-include("rabbit.hrl").
--export([start_link/2, info_keys/0, info/1, info/2,
+-export([start_link/2, info_keys/0, info/1, info/2, force_event_refresh/2,
shutdown/2]).
-export([system_continue/3, system_terminate/4, system_code_change/4]).
@@ -66,6 +66,8 @@
-export([conserve_resources/3, server_properties/1]).
+-deprecated([{force_event_refresh, 2, eventually}]).
+
-define(NORMAL_TIMEOUT, 3).
-define(CLOSING_TIMEOUT, 30).
-define(CHANNEL_TERMINATION_TIMEOUT, 3).
@@ -157,38 +159,26 @@
%%--------------------------------------------------------------------------
--spec start_link(pid(), any()) -> rabbit_types:ok(pid()).
--spec info_keys() -> rabbit_types:info_keys().
--spec info(pid()) -> rabbit_types:infos().
--spec info(pid(), rabbit_types:info_keys()) -> rabbit_types:infos().
--spec shutdown(pid(), string()) -> 'ok'.
-type resource_alert() :: {WasAlarmSetForNode :: boolean(),
IsThereAnyAlarmsWithSameSourceInTheCluster :: boolean(),
NodeForWhichAlarmWasSetOrCleared :: node()}.
--spec conserve_resources(pid(), atom(), resource_alert()) -> 'ok'.
--spec server_properties(rabbit_types:protocol()) ->
- rabbit_framing:amqp_table().
-
-%% These specs only exists to add no_return() to keep dialyzer happy
--spec init(pid(), pid(), any()) -> no_return().
--spec start_connection(pid(), pid(), any(), rabbit_net:socket()) ->
- no_return().
-
--spec mainloop(_,[binary()], non_neg_integer(), #v1{}) -> any().
--spec system_code_change(_,_,_,_) -> {'ok',_}.
--spec system_continue(_,_,{[binary()], non_neg_integer(), #v1{}}) -> any().
--spec system_terminate(_,_,_,_) -> none().
%%--------------------------------------------------------------------------
+-spec start_link(pid(), any()) -> rabbit_types:ok(pid()).
+
start_link(HelperSup, Ref) ->
Pid = proc_lib:spawn_link(?MODULE, init, [self(), HelperSup, Ref]),
{ok, Pid}.
+-spec shutdown(pid(), string()) -> 'ok'.
+
shutdown(Pid, Explanation) ->
gen_server:call(Pid, {shutdown, Explanation}, infinity).
+-spec init(pid(), pid(), any()) -> no_return().
+
init(Parent, HelperSup, Ref) ->
?LG_PROCESS_TYPE(reader),
{ok, Sock} = rabbit_networking:handshake(Ref,
@@ -196,30 +186,52 @@ init(Parent, HelperSup, Ref) ->
Deb = sys:debug_options([]),
start_connection(Parent, HelperSup, Deb, Sock).
+-spec system_continue(_,_,{[binary()], non_neg_integer(), #v1{}}) -> any().
+
system_continue(Parent, Deb, {Buf, BufLen, State}) ->
mainloop(Deb, Buf, BufLen, State#v1{parent = Parent}).
+-spec system_terminate(_,_,_,_) -> none().
+
system_terminate(Reason, _Parent, _Deb, _State) ->
exit(Reason).
+-spec system_code_change(_,_,_,_) -> {'ok',_}.
+
system_code_change(Misc, _Module, _OldVsn, _Extra) ->
{ok, Misc}.
+-spec info_keys() -> rabbit_types:info_keys().
+
info_keys() -> ?INFO_KEYS.
+-spec info(pid()) -> rabbit_types:infos().
+
info(Pid) ->
gen_server:call(Pid, info, infinity).
+-spec info(pid(), rabbit_types:info_keys()) -> rabbit_types:infos().
+
info(Pid, Items) ->
case gen_server:call(Pid, {info, Items}, infinity) of
{ok, Res} -> Res;
{error, Error} -> throw(Error)
end.
+-spec force_event_refresh(pid(), reference()) -> 'ok'.
+
+force_event_refresh(Pid, Ref) ->
+ gen_server:cast(Pid, {force_event_refresh, Ref}).
+
+-spec conserve_resources(pid(), atom(), resource_alert()) -> 'ok'.
+
conserve_resources(Pid, Source, {_, Conserve, _}) ->
Pid ! {conserve_resources, Source, Conserve},
ok.
+-spec server_properties(rabbit_types:protocol()) ->
+ rabbit_framing:amqp_table().
+
server_properties(Protocol) ->
{ok, Product} = application:get_key(rabbit, description),
{ok, Version} = application:get_key(rabbit, vsn),
@@ -297,6 +309,9 @@ socket_op(Sock, Fun) ->
exit(normal)
end.
+-spec start_connection(pid(), pid(), any(), rabbit_net:socket()) ->
+ no_return().
+
start_connection(Parent, HelperSup, Deb, Sock) ->
process_flag(trap_exit, true),
RealSocket = rabbit_net:unwrap_socket(Sock),
@@ -487,6 +502,8 @@ binlist_split(Len, L, [Acc0|Acc]) when Len < 0 ->
binlist_split(Len, [H|T], Acc) ->
binlist_split(Len - size(H), T, [H|Acc]).
+-spec mainloop(_,[binary()], non_neg_integer(), #v1{}) -> any().
+
mainloop(Deb, Buf, BufLen, State = #v1{sock = Sock,
connection_state = CS,
connection = #connection{
@@ -615,6 +632,17 @@ handle_other({'$gen_call', From, {info, Items}}, State) ->
catch Error -> {error, Error}
end),
State;
+handle_other({'$gen_cast', {force_event_refresh, Ref}}, State)
+ when ?IS_RUNNING(State) ->
+ rabbit_event:notify(
+ connection_created,
+ augment_infos_with_user_provided_connection_name(
+ [{type, network} | infos(?CREATION_EVENT_KEYS, State)], State),
+ Ref),
+ rabbit_event:init_stats_timer(State, #v1.stats_timer);
+handle_other({'$gen_cast', {force_event_refresh, _Ref}}, State) ->
+ %% Ignore, we will emit a created event once we start running.
+ State;
handle_other(ensure_stats, State) ->
ensure_stats_timer(State);
handle_other(emit_stats, State) ->
diff --git a/src/rabbit_recovery_terms.erl b/src/rabbit_recovery_terms.erl
index ab70fa2be7..28bd9fc23a 100644
--- a/src/rabbit_recovery_terms.erl
+++ b/src/rabbit_recovery_terms.erl
@@ -40,12 +40,6 @@
%%----------------------------------------------------------------------------
-spec start(rabbit_types:vhost()) -> rabbit_types:ok_or_error(term()).
--spec stop(rabbit_types:vhost()) -> rabbit_types:ok_or_error(term()).
--spec store(rabbit_types:vhost(), file:filename(), term()) -> rabbit_types:ok_or_error(term()).
--spec read(rabbit_types:vhost(), file:filename()) -> rabbit_types:ok_or_error2(term(), not_found).
--spec clear(rabbit_types:vhost()) -> 'ok'.
-
-%%----------------------------------------------------------------------------
start(VHost) ->
case rabbit_vhost_sup_sup:get_vhost_sup(VHost) of
@@ -64,6 +58,8 @@ start(VHost) ->
end,
ok.
+-spec stop(rabbit_types:vhost()) -> rabbit_types:ok_or_error(term()).
+
stop(VHost) ->
case rabbit_vhost_sup_sup:get_vhost_sup(VHost) of
{ok, VHostSup} ->
@@ -79,15 +75,21 @@ stop(VHost) ->
ok
end.
+-spec store(rabbit_types:vhost(), file:filename(), term()) -> rabbit_types:ok_or_error(term()).
+
store(VHost, DirBaseName, Terms) ->
dets:insert(VHost, {DirBaseName, Terms}).
+-spec read(rabbit_types:vhost(), file:filename()) -> rabbit_types:ok_or_error2(term(), not_found).
+
read(VHost, DirBaseName) ->
case dets:lookup(VHost, DirBaseName) of
[{_, Terms}] -> {ok, Terms};
_ -> {error, not_found}
end.
+-spec clear(rabbit_types:vhost()) -> 'ok'.
+
clear(VHost) ->
try
dets:delete_all_objects(VHost)
@@ -115,7 +117,7 @@ upgrade_recovery_terms() ->
[begin
File = filename:join([QueuesDir, Dir, "clean.dot"]),
case rabbit_file:read_term_file(File) of
- {ok, Terms} -> ok = store(?MODULE, Dir, Terms);
+ {ok, Terms} -> ok = store_global_table(Dir, Terms);
{error, _} -> ok
end,
file:delete(File)
@@ -132,7 +134,7 @@ dets_upgrade(Fun)->
open_global_table(),
try
ok = dets:foldl(fun ({DirBaseName, Terms}, Acc) ->
- store(?MODULE, DirBaseName, Fun(Terms)),
+ store_global_table(DirBaseName, Fun(Terms)),
Acc
end, ok, ?MODULE),
ok
@@ -158,8 +160,14 @@ close_global_table() ->
ok
end.
+store_global_table(DirBaseName, Terms) ->
+ dets:insert(?MODULE, {DirBaseName, Terms}).
+
read_global(DirBaseName) ->
- read(?MODULE, DirBaseName).
+ case dets:lookup(?MODULE, DirBaseName) of
+ [{_, Terms}] -> {ok, Terms};
+ _ -> {error, not_found}
+ end.
delete_global_table() ->
file:delete(filename:join(rabbit_mnesia:dir(), "recovery.dets")).
diff --git a/src/rabbit_restartable_sup.erl b/src/rabbit_restartable_sup.erl
index ecbd10a3d7..dbf5a24b50 100644
--- a/src/rabbit_restartable_sup.erl
+++ b/src/rabbit_restartable_sup.erl
@@ -31,8 +31,6 @@
-spec start_link(atom(), rabbit_types:mfargs(), boolean()) ->
rabbit_types:ok_pid_or_error().
-%%----------------------------------------------------------------------------
-
start_link(Name, {_M, _F, _A} = Fun, Delay) ->
supervisor2:start_link({local, Name}, ?MODULE, [Fun, Delay]).
diff --git a/src/rabbit_sup.erl b/src/rabbit_sup.erl
index a1a0e4897d..2a0ae34e3c 100644
--- a/src/rabbit_sup.erl
+++ b/src/rabbit_sup.erl
@@ -34,53 +34,63 @@
%%----------------------------------------------------------------------------
-spec start_link() -> rabbit_types:ok_pid_or_error().
--spec start_child(atom()) -> 'ok'.
--spec start_child(atom(), [any()]) -> 'ok'.
--spec start_child(atom(), atom(), [any()]) -> 'ok'.
--spec start_child(atom(), atom(), atom(), [any()]) -> 'ok'.
--spec start_supervisor_child(atom()) -> 'ok'.
--spec start_supervisor_child(atom(), [any()]) -> 'ok'.
--spec start_supervisor_child(atom(), atom(), [any()]) -> 'ok'.
--spec start_restartable_child(atom()) -> 'ok'.
--spec start_restartable_child(atom(), [any()]) -> 'ok'.
--spec start_delayed_restartable_child(atom()) -> 'ok'.
--spec start_delayed_restartable_child(atom(), [any()]) -> 'ok'.
--spec stop_child(atom()) -> rabbit_types:ok_or_error(any()).
-
-%%----------------------------------------------------------------------------
start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []).
+-spec start_child(atom()) -> 'ok'.
+
start_child(Mod) -> start_child(Mod, []).
+-spec start_child(atom(), [any()]) -> 'ok'.
+
start_child(Mod, Args) -> start_child(Mod, Mod, Args).
+-spec start_child(atom(), atom(), [any()]) -> 'ok'.
+
start_child(ChildId, Mod, Args) ->
child_reply(supervisor:start_child(
?SERVER,
{ChildId, {Mod, start_link, Args},
transient, ?WORKER_WAIT, worker, [Mod]})).
+-spec start_child(atom(), atom(), atom(), [any()]) -> 'ok'.
+
start_child(ChildId, Mod, Fun, Args) ->
child_reply(supervisor:start_child(
?SERVER,
{ChildId, {Mod, Fun, Args},
transient, ?WORKER_WAIT, worker, [Mod]})).
+-spec start_supervisor_child(atom()) -> 'ok'.
start_supervisor_child(Mod) -> start_supervisor_child(Mod, []).
+-spec start_supervisor_child(atom(), [any()]) -> 'ok'.
+
start_supervisor_child(Mod, Args) -> start_supervisor_child(Mod, Mod, Args).
+-spec start_supervisor_child(atom(), atom(), [any()]) -> 'ok'.
+
start_supervisor_child(ChildId, Mod, Args) ->
child_reply(supervisor:start_child(
?SERVER,
{ChildId, {Mod, start_link, Args},
transient, infinity, supervisor, [Mod]})).
+-spec start_restartable_child(atom()) -> 'ok'.
+
start_restartable_child(M) -> start_restartable_child(M, [], false).
+
+-spec start_restartable_child(atom(), [any()]) -> 'ok'.
+
start_restartable_child(M, A) -> start_restartable_child(M, A, false).
+
+-spec start_delayed_restartable_child(atom()) -> 'ok'.
+
start_delayed_restartable_child(M) -> start_restartable_child(M, [], true).
+
+-spec start_delayed_restartable_child(atom(), [any()]) -> 'ok'.
+
start_delayed_restartable_child(M, A) -> start_restartable_child(M, A, true).
start_restartable_child(Mod, Args, Delay) ->
@@ -91,6 +101,8 @@ start_restartable_child(Mod, Args, Delay) ->
[Name, {Mod, start_link, Args}, Delay]},
transient, infinity, supervisor, [rabbit_restartable_sup]})).
+-spec stop_child(atom()) -> rabbit_types:ok_or_error(any()).
+
stop_child(ChildId) ->
case supervisor:terminate_child(?SERVER, ChildId) of
ok -> supervisor:delete_child(?SERVER, ChildId);
diff --git a/src/rabbit_table.erl b/src/rabbit_table.erl
index 1fab94fe34..9bf1d2c3f6 100644
--- a/src/rabbit_table.erl
+++ b/src/rabbit_table.erl
@@ -24,28 +24,18 @@
%% for testing purposes
-export([definitions/0]).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
%%----------------------------------------------------------------------------
--type retry() :: boolean().
--spec create() -> 'ok'.
--spec create_local_copy('disc' | 'ram') -> 'ok'.
--spec wait_for_replicated(retry()) -> 'ok'.
--spec wait_for_replicated() -> 'ok'.
--spec wait([atom()]) -> 'ok'.
--spec retry_timeout() -> {non_neg_integer() | infinity, non_neg_integer()}.
--spec force_load() -> 'ok'.
--spec is_present() -> boolean().
--spec is_empty() -> boolean().
--spec needs_default_data() -> boolean().
--spec check_schema_integrity(retry()) -> rabbit_types:ok_or_error(any()).
--spec clear_ram_only_tables() -> 'ok'.
+-type retry() :: boolean().
%%----------------------------------------------------------------------------
%% Main interface
%%----------------------------------------------------------------------------
+-spec create() -> 'ok'.
+
create() ->
lists:foreach(fun ({Tab, TabDef}) ->
TabDef1 = proplists:delete(match, TabDef),
@@ -74,6 +64,9 @@ ensure_secondary_index(Table, Field) ->
%% tables is important: if we delete the schema first when moving to
%% RAM mnesia will loudly complain since it doesn't make much sense to
%% do that. But when moving to disc, we need to move the schema first.
+
+-spec create_local_copy('disc' | 'ram') -> 'ok'.
+
create_local_copy(disc) ->
create_local_copy(schema, disc_copies),
create_local_copies(disc);
@@ -83,13 +76,20 @@ create_local_copy(ram) ->
%% This arity only exists for backwards compatibility with certain
%% plugins. See https://github.com/rabbitmq/rabbitmq-clusterer/issues/19.
+
+-spec wait_for_replicated() -> 'ok'.
+
wait_for_replicated() ->
wait_for_replicated(false).
+-spec wait_for_replicated(retry()) -> 'ok'.
+
wait_for_replicated(Retry) ->
wait([Tab || {Tab, TabDef} <- definitions(),
not lists:member({local_content, true}, TabDef)], Retry).
+-spec wait([atom()]) -> 'ok'.
+
wait(TableNames) ->
wait(TableNames, _Retry = false).
@@ -117,8 +117,6 @@ wait(TableNames, Timeout, Retries) ->
throw(Error);
{_, {error, Error}} ->
rabbit_log:warning("Error while waiting for Mnesia tables: ~p~n", [Error]),
- wait(TableNames, Timeout, Retries - 1);
- _ ->
wait(TableNames, Timeout, Retries - 1)
end.
@@ -131,17 +129,28 @@ retry_timeout(_Retry = true) ->
end,
{retry_timeout(), Retries}.
+-spec retry_timeout() -> non_neg_integer() | infinity.
+
retry_timeout() ->
case application:get_env(rabbit, mnesia_table_loading_retry_timeout) of
{ok, T} -> T;
undefined -> 30000
end.
+-spec force_load() -> 'ok'.
+
force_load() -> [mnesia:force_load_table(T) || T <- names()], ok.
+-spec is_present() -> boolean().
+
is_present() -> names() -- mnesia:system_info(tables) =:= [].
+-spec is_empty() -> boolean().
+
is_empty() -> is_empty(names()).
+
+-spec needs_default_data() -> boolean().
+
needs_default_data() -> is_empty([rabbit_user, rabbit_user_permission,
rabbit_vhost]).
@@ -149,6 +158,8 @@ is_empty(Names) ->
lists:all(fun (Tab) -> mnesia:dirty_first(Tab) == '$end_of_table' end,
Names).
+-spec check_schema_integrity(retry()) -> rabbit_types:ok_or_error(any()).
+
check_schema_integrity(Retry) ->
Tables = mnesia:system_info(tables),
case check(fun (Tab, TabDef) ->
@@ -162,6 +173,8 @@ check_schema_integrity(Retry) ->
Other -> Other
end.
+-spec clear_ram_only_tables() -> 'ok'.
+
clear_ram_only_tables() ->
Node = node(),
lists:foreach(
@@ -349,13 +362,13 @@ definitions() ->
{match, #runtime_parameters{_='_'}}]},
{rabbit_durable_queue,
[{record_name, amqqueue},
- {attributes, record_info(fields, amqqueue)},
+ {attributes, amqqueue:fields()},
{disc_copies, [node()]},
- {match, #amqqueue{name = queue_name_match(), _='_'}}]},
+ {match, amqqueue:pattern_match_on_name(queue_name_match())}]},
{rabbit_queue,
[{record_name, amqqueue},
- {attributes, record_info(fields, amqqueue)},
- {match, #amqqueue{name = queue_name_match(), _='_'}}]}]
+ {attributes, amqqueue:fields()},
+ {match, amqqueue:pattern_match_on_name(queue_name_match())}]}]
++ gm:table_definitions()
++ mirrored_supervisor:table_definitions().
diff --git a/src/rabbit_trace.erl b/src/rabbit_trace.erl
index 6047eb24a3..2c85de2f3a 100644
--- a/src/rabbit_trace.erl
+++ b/src/rabbit_trace.erl
@@ -28,20 +28,10 @@
-type state() :: rabbit_types:exchange() | 'none'.
--spec init(rabbit_types:vhost()) -> state().
--spec enabled(rabbit_types:vhost()) -> boolean().
--spec tap_in(rabbit_types:basic_message(), [rabbit_amqqueue:name()],
- binary(), rabbit_channel:channel_number(),
- rabbit_types:username(), state()) -> 'ok'.
--spec tap_out(rabbit_amqqueue:qmsg(), binary(),
- rabbit_channel:channel_number(),
- rabbit_types:username(), state()) -> 'ok'.
-
--spec start(rabbit_types:vhost()) -> 'ok'.
--spec stop(rabbit_types:vhost()) -> 'ok'.
-
%%----------------------------------------------------------------------------
+-spec init(rabbit_types:vhost()) -> state().
+
init(VHost) ->
case enabled(VHost) of
false -> none;
@@ -50,10 +40,16 @@ init(VHost) ->
X
end.
+-spec enabled(rabbit_types:vhost()) -> boolean().
+
enabled(VHost) ->
{ok, VHosts} = application:get_env(rabbit, ?TRACE_VHOSTS),
lists:member(VHost, VHosts).
+-spec tap_in(rabbit_types:basic_message(), [rabbit_amqqueue:name()],
+ binary(), rabbit_channel:channel_number(),
+ rabbit_types:username(), state()) -> 'ok'.
+
tap_in(_Msg, _QNames, _ConnName, _ChannelNum, _Username, none) -> ok;
tap_in(Msg = #basic_message{exchange_name = #resource{name = XName,
virtual_host = VHost}},
@@ -66,6 +62,10 @@ tap_in(Msg = #basic_message{exchange_name = #resource{name = XName,
{<<"routed_queues">>, array,
[{longstr, QName#resource.name} || QName <- QNames]}]).
+-spec tap_out(rabbit_amqqueue:qmsg(), binary(),
+ rabbit_channel:channel_number(),
+ rabbit_types:username(), state()) -> 'ok'.
+
tap_out(_Msg, _ConnName, _ChannelNum, _Username, none) -> ok;
tap_out({#resource{name = QName, virtual_host = VHost},
_QPid, _QMsgId, Redelivered, Msg},
@@ -80,10 +80,14 @@ tap_out({#resource{name = QName, virtual_host = VHost},
%%----------------------------------------------------------------------------
+-spec start(rabbit_types:vhost()) -> 'ok'.
+
start(VHost) ->
rabbit_log:info("Enabling tracing for vhost '~s'~n", [VHost]),
update_config(fun (VHosts) -> [VHost | VHosts -- [VHost]] end).
+-spec stop(rabbit_types:vhost()) -> 'ok'.
+
stop(VHost) ->
rabbit_log:info("Disabling tracing for vhost '~s'~n", [VHost]),
update_config(fun (VHosts) -> VHosts -- [VHost] end).
diff --git a/src/rabbit_upgrade.erl b/src/rabbit_upgrade.erl
index 07eef293d3..f452d5c92f 100644
--- a/src/rabbit_upgrade.erl
+++ b/src/rabbit_upgrade.erl
@@ -27,14 +27,6 @@
%% -------------------------------------------------------------------
--spec maybe_upgrade_mnesia() -> 'ok'.
--spec maybe_upgrade_local() ->
- 'ok' |
- 'version_not_available' |
- 'starting_from_scratch'.
-
-%% -------------------------------------------------------------------
-
%% The upgrade logic is quite involved, due to the existence of
%% clusters.
%%
@@ -125,6 +117,8 @@ remove_backup() ->
ok = rabbit_file:recursive_delete([backup_dir()]),
info("upgrades: Mnesia backup removed~n", []).
+-spec maybe_upgrade_mnesia() -> 'ok'.
+
maybe_upgrade_mnesia() ->
AllNodes = rabbit_mnesia:cluster_nodes(all),
ok = rabbit_mnesia_rename:maybe_finish(AllNodes),
@@ -180,25 +174,34 @@ upgrade_mode(AllNodes) ->
end;
[Another|_] ->
MyVersion = rabbit_version:desired_for_scope(mnesia),
- ErrFun = fun (ClusterVersion) ->
- %% The other node(s) are running an
- %% unexpected version.
- die("Cluster upgrade needed but other nodes are "
- "running ~p~nand I want ~p",
- [ClusterVersion, MyVersion])
- end,
case rpc:call(Another, rabbit_version, desired_for_scope,
[mnesia]) of
- {badrpc, {'EXIT', {undef, _}}} -> ErrFun(unknown_old_version);
- {badrpc, Reason} -> ErrFun({unknown, Reason});
- CV -> case rabbit_version:matches(
- MyVersion, CV) of
- true -> secondary;
- false -> ErrFun(CV)
- end
+ {badrpc, {'EXIT', {undef, _}}} ->
+ die_because_cluster_upgrade_needed(unknown_old_version,
+ MyVersion);
+ {badrpc, Reason} ->
+ die_because_cluster_upgrade_needed({unknown, Reason},
+ MyVersion);
+ CV -> case rabbit_version:matches(
+ MyVersion, CV) of
+ true -> secondary;
+ false -> die_because_cluster_upgrade_needed(
+ CV, MyVersion)
+ end
end
end.
+-spec die_because_cluster_upgrade_needed(any(), any()) -> no_return().
+
+die_because_cluster_upgrade_needed(ClusterVersion, MyVersion) ->
+ %% The other node(s) are running an
+ %% unexpected version.
+ die("Cluster upgrade needed but other nodes are "
+ "running ~p~nand I want ~p",
+ [ClusterVersion, MyVersion]).
+
+-spec die(string(), list()) -> no_return().
+
die(Msg, Args) ->
%% We don't throw or exit here since that gets thrown
%% straight out into do_boot, generating an erl_crash.dump
@@ -244,6 +247,11 @@ nodes_running(Nodes) ->
%% -------------------------------------------------------------------
+-spec maybe_upgrade_local() ->
+ 'ok' |
+ 'version_not_available' |
+ 'starting_from_scratch'.
+
maybe_upgrade_local() ->
case rabbit_version:upgrades_required(local) of
{error, version_not_available} -> version_not_available;
diff --git a/src/rabbit_upgrade_functions.erl b/src/rabbit_upgrade_functions.erl
index 6be812dad3..afbcb863aa 100644
--- a/src/rabbit_upgrade_functions.erl
+++ b/src/rabbit_upgrade_functions.erl
@@ -60,60 +60,16 @@
-rabbit_upgrade({queue_vhost_field, mnesia, [operator_policies]}).
-rabbit_upgrade({topic_permission, mnesia, []}).
-rabbit_upgrade({queue_options, mnesia, [queue_vhost_field]}).
--rabbit_upgrade({queue_type, mnesia, [queue_options]}).
--rabbit_upgrade({queue_quorum_nodes, mnesia, [queue_type]}).
-rabbit_upgrade({exchange_options, mnesia, [operator_policies]}).
-%% TODO: move that to feature flags
--rabbit_upgrade({remove_explicit_default_exchange_bindings, mnesia, [queue_state]}).
-
%% -------------------------------------------------------------------
--spec remove_user_scope() -> 'ok'.
--spec hash_passwords() -> 'ok'.
--spec add_ip_to_listener() -> 'ok'.
--spec add_opts_to_listener() -> 'ok'.
--spec internal_exchanges() -> 'ok'.
--spec user_to_internal_user() -> 'ok'.
--spec topic_trie() -> 'ok'.
--spec semi_durable_route() -> 'ok'.
--spec exchange_event_serial() -> 'ok'.
--spec trace_exchanges() -> 'ok'.
--spec user_admin_to_tags() -> 'ok'.
--spec ha_mirrors() -> 'ok'.
--spec gm() -> 'ok'.
--spec exchange_scratch() -> 'ok'.
--spec mirrored_supervisor() -> 'ok'.
--spec topic_trie_node() -> 'ok'.
--spec runtime_parameters() -> 'ok'.
--spec policy() -> 'ok'.
--spec sync_slave_pids() -> 'ok'.
--spec no_mirror_nodes() -> 'ok'.
--spec gm_pids() -> 'ok'.
--spec exchange_decorators() -> 'ok'.
--spec policy_apply_to() -> 'ok'.
--spec queue_decorators() -> 'ok'.
--spec internal_system_x() -> 'ok'.
--spec cluster_name() -> 'ok'.
--spec down_slave_nodes() -> 'ok'.
--spec queue_state() -> 'ok'.
--spec recoverable_slaves() -> 'ok'.
--spec user_password_hashing() -> 'ok'.
--spec vhost_limits() -> 'ok'.
--spec operator_policies() -> 'ok'.
--spec queue_vhost_field() -> 'ok'.
--spec queue_options() -> 'ok'.
--spec queue_type() -> 'ok'.
--spec queue_quorum_nodes() -> 'ok'.
--spec exchange_options() -> 'ok'.
-
--spec remove_explicit_default_exchange_bindings() -> 'ok'.
-
-%%--------------------------------------------------------------------
-
%% replaces vhost.dummy (used to avoid having a single-field record
%% which Mnesia doesn't like) with vhost.limits (which is actually
%% used)
+
+-spec vhost_limits() -> 'ok'.
+
vhost_limits() ->
transform(
rabbit_vhost,
@@ -128,6 +84,8 @@ vhost_limits() ->
%% would be messy to have to go back and fix old transforms at that
%% point.
+-spec remove_user_scope() -> 'ok'.
+
remove_user_scope() ->
transform(
rabbit_user_permission,
@@ -140,6 +98,9 @@ remove_user_scope() ->
%% only relevant to those migrating from 2.1.1.
%% all users created after in 3.6.0 or later will use SHA-256 (unless configured
%% otherwise)
+
+-spec hash_passwords() -> 'ok'.
+
hash_passwords() ->
transform(
rabbit_user,
@@ -149,6 +110,8 @@ hash_passwords() ->
end,
[username, password_hash, is_admin]).
+-spec add_ip_to_listener() -> 'ok'.
+
add_ip_to_listener() ->
transform(
rabbit_listener,
@@ -157,6 +120,8 @@ add_ip_to_listener() ->
end,
[node, protocol, host, ip_address, port]).
+-spec add_opts_to_listener() -> 'ok'.
+
add_opts_to_listener() ->
transform(
rabbit_listener,
@@ -165,6 +130,8 @@ add_opts_to_listener() ->
end,
[node, protocol, host, ip_address, port, opts]).
+-spec internal_exchanges() -> 'ok'.
+
internal_exchanges() ->
Tables = [rabbit_exchange, rabbit_durable_exchange],
AddInternalFun =
@@ -177,6 +144,8 @@ internal_exchanges() ->
|| T <- Tables ],
ok.
+-spec user_to_internal_user() -> 'ok'.
+
user_to_internal_user() ->
transform(
rabbit_user,
@@ -185,6 +154,8 @@ user_to_internal_user() ->
end,
[username, password_hash, is_admin], internal_user).
+-spec topic_trie() -> 'ok'.
+
topic_trie() ->
create(rabbit_topic_trie_edge, [{record_name, topic_trie_edge},
{attributes, [trie_edge, node_id]},
@@ -193,20 +164,28 @@ topic_trie() ->
{attributes, [trie_binding, value]},
{type, ordered_set}]).
+-spec semi_durable_route() -> 'ok'.
+
semi_durable_route() ->
create(rabbit_semi_durable_route, [{record_name, route},
{attributes, [binding, value]}]).
+-spec exchange_event_serial() -> 'ok'.
+
exchange_event_serial() ->
create(rabbit_exchange_serial, [{record_name, exchange_serial},
{attributes, [name, next]}]).
+-spec trace_exchanges() -> 'ok'.
+
trace_exchanges() ->
[declare_exchange(
rabbit_misc:r(VHost, exchange, <<"amq.rabbitmq.trace">>), topic) ||
VHost <- rabbit_vhost:list()],
ok.
+-spec user_admin_to_tags() -> 'ok'.
+
user_admin_to_tags() ->
transform(
rabbit_user,
@@ -217,6 +196,8 @@ user_admin_to_tags() ->
end,
[username, password_hash, tags], internal_user).
+-spec ha_mirrors() -> 'ok'.
+
ha_mirrors() ->
Tables = [rabbit_queue, rabbit_durable_queue],
AddMirrorPidsFun =
@@ -231,10 +212,14 @@ ha_mirrors() ->
|| T <- Tables ],
ok.
+-spec gm() -> 'ok'.
+
gm() ->
create(gm_group, [{record_name, gm_group},
{attributes, [name, version, members]}]).
+-spec exchange_scratch() -> 'ok'.
+
exchange_scratch() ->
ok = exchange_scratch(rabbit_exchange),
ok = exchange_scratch(rabbit_durable_exchange).
@@ -247,17 +232,23 @@ exchange_scratch(Table) ->
end,
[name, type, durable, auto_delete, internal, arguments, scratch]).
+-spec mirrored_supervisor() -> 'ok'.
+
mirrored_supervisor() ->
create(mirrored_sup_childspec,
[{record_name, mirrored_sup_childspec},
{attributes, [key, mirroring_pid, childspec]}]).
+-spec topic_trie_node() -> 'ok'.
+
topic_trie_node() ->
create(rabbit_topic_trie_node,
[{record_name, topic_trie_node},
{attributes, [trie_node, edge_count, binding_count]},
{type, ordered_set}]).
+-spec runtime_parameters() -> 'ok'.
+
runtime_parameters() ->
create(rabbit_runtime_parameters,
[{record_name, runtime_parameters},
@@ -281,6 +272,8 @@ exchange_scratches(Table) ->
end,
[name, type, durable, auto_delete, internal, arguments, scratches]).
+-spec policy() -> 'ok'.
+
policy() ->
ok = exchange_policy(rabbit_exchange),
ok = exchange_policy(rabbit_durable_exchange),
@@ -307,6 +300,8 @@ queue_policy(Table) ->
[name, durable, auto_delete, exclusive_owner, arguments, pid,
slave_pids, mirror_nodes, policy]).
+-spec sync_slave_pids() -> 'ok'.
+
sync_slave_pids() ->
Tables = [rabbit_queue, rabbit_durable_queue],
AddSyncSlavesFun =
@@ -319,6 +314,8 @@ sync_slave_pids() ->
|| T <- Tables],
ok.
+-spec no_mirror_nodes() -> 'ok'.
+
no_mirror_nodes() ->
Tables = [rabbit_queue, rabbit_durable_queue],
RemoveMirrorNodesFun =
@@ -331,6 +328,8 @@ no_mirror_nodes() ->
|| T <- Tables],
ok.
+-spec gm_pids() -> 'ok'.
+
gm_pids() ->
Tables = [rabbit_queue, rabbit_durable_queue],
AddGMPidsFun =
@@ -343,6 +342,8 @@ gm_pids() ->
|| T <- Tables],
ok.
+-spec exchange_decorators() -> 'ok'.
+
exchange_decorators() ->
ok = exchange_decorators(rabbit_exchange),
ok = exchange_decorators(rabbit_durable_exchange).
@@ -358,6 +359,8 @@ exchange_decorators(Table) ->
[name, type, durable, auto_delete, internal, arguments, scratches, policy,
decorators]).
+-spec policy_apply_to() -> 'ok'.
+
policy_apply_to() ->
transform(
rabbit_runtime_parameters,
@@ -380,6 +383,8 @@ apply_to(Def) ->
[_, _] -> <<"all">>
end.
+-spec queue_decorators() -> 'ok'.
+
queue_decorators() ->
ok = queue_decorators(rabbit_queue),
ok = queue_decorators(rabbit_durable_queue).
@@ -395,6 +400,8 @@ queue_decorators(Table) ->
[name, durable, auto_delete, exclusive_owner, arguments, pid, slave_pids,
sync_slave_pids, policy, gm_pids, decorators]).
+-spec internal_system_x() -> 'ok'.
+
internal_system_x() ->
transform(
rabbit_durable_exchange,
@@ -408,6 +415,8 @@ internal_system_x() ->
[name, type, durable, auto_delete, internal, arguments, scratches, policy,
decorators]).
+-spec cluster_name() -> 'ok'.
+
cluster_name() ->
{atomic, ok} = mnesia:transaction(fun cluster_name_tx/0),
ok.
@@ -434,6 +443,8 @@ cluster_name_tx() ->
[mnesia:delete(T, K, write) || K <- Ks],
ok.
+-spec down_slave_nodes() -> 'ok'.
+
down_slave_nodes() ->
ok = down_slave_nodes(rabbit_queue),
ok = down_slave_nodes(rabbit_durable_queue).
@@ -449,6 +460,8 @@ down_slave_nodes(Table) ->
[name, durable, auto_delete, exclusive_owner, arguments, pid, slave_pids,
sync_slave_pids, down_slave_nodes, policy, gm_pids, decorators]).
+-spec queue_state() -> 'ok'.
+
queue_state() ->
ok = queue_state(rabbit_queue),
ok = queue_state(rabbit_durable_queue).
@@ -465,6 +478,8 @@ queue_state(Table) ->
[name, durable, auto_delete, exclusive_owner, arguments, pid, slave_pids,
sync_slave_pids, down_slave_nodes, policy, gm_pids, decorators, state]).
+-spec recoverable_slaves() -> 'ok'.
+
recoverable_slaves() ->
ok = recoverable_slaves(rabbit_queue),
ok = recoverable_slaves(rabbit_durable_queue).
@@ -512,6 +527,8 @@ slave_pids_pending_shutdown(Table) ->
sync_slave_pids, recoverable_slaves, policy, gm_pids, decorators, state,
policy_version, slave_pids_pending_shutdown]).
+-spec operator_policies() -> 'ok'.
+
operator_policies() ->
ok = exchange_operator_policies(rabbit_exchange),
ok = exchange_operator_policies(rabbit_durable_exchange),
@@ -543,6 +560,7 @@ queue_operator_policies(Table) ->
sync_slave_pids, recoverable_slaves, policy, operator_policy,
gm_pids, decorators, state, policy_version, slave_pids_pending_shutdown]).
+-spec queue_vhost_field() -> 'ok'.
queue_vhost_field() ->
ok = queue_vhost_field(rabbit_queue),
@@ -565,6 +583,8 @@ queue_vhost_field(Table) ->
sync_slave_pids, recoverable_slaves, policy, operator_policy,
gm_pids, decorators, state, policy_version, slave_pids_pending_shutdown, vhost]).
+-spec queue_options() -> 'ok'.
+
queue_options() ->
ok = queue_options(rabbit_queue),
ok = queue_options(rabbit_durable_queue),
@@ -584,51 +604,13 @@ queue_options(Table) ->
sync_slave_pids, recoverable_slaves, policy, operator_policy,
gm_pids, decorators, state, policy_version, slave_pids_pending_shutdown, vhost, options]).
-queue_type() ->
- ok = queue_type(rabbit_queue),
- ok = queue_type(rabbit_durable_queue),
- ok.
-
-queue_type(Table) ->
- transform(
- Table,
- fun ({amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments,
- Pid, SlavePids, SyncSlavePids, DSN, Policy, OperatorPolicy, GmPids, Decorators,
- State, PolicyVersion, SlavePidsPendingShutdown, VHost, Options}) ->
- {amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments,
- Pid, SlavePids, SyncSlavePids, DSN, Policy, OperatorPolicy, GmPids, Decorators,
- State, PolicyVersion, SlavePidsPendingShutdown, VHost, Options, classic}
- end,
- [name, durable, auto_delete, exclusive_owner, arguments, pid, slave_pids,
- sync_slave_pids, recoverable_slaves, policy, operator_policy,
- gm_pids, decorators, state, policy_version, slave_pids_pending_shutdown, vhost, options,
- type]).
-
-queue_quorum_nodes() ->
- ok = queue_quorum_nodes(rabbit_queue),
- ok = queue_quorum_nodes(rabbit_durable_queue),
- ok.
-
-queue_quorum_nodes(Table) ->
- transform(
- Table,
- fun ({amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments,
- Pid, SlavePids, SyncSlavePids, DSN, Policy, OperatorPolicy, GmPids, Decorators,
- State, PolicyVersion, SlavePidsPendingShutdown, VHost, Options, Type}) ->
- {amqqueue, Name, Durable, AutoDelete, ExclusiveOwner, Arguments,
- Pid, SlavePids, SyncSlavePids, DSN, Policy, OperatorPolicy, GmPids, Decorators,
- State, PolicyVersion, SlavePidsPendingShutdown, VHost, Options, Type,
- undefined}
- end,
- [name, durable, auto_delete, exclusive_owner, arguments, pid, slave_pids,
- sync_slave_pids, recoverable_slaves, policy, operator_policy,
- gm_pids, decorators, state, policy_version, slave_pids_pending_shutdown, vhost, options,
- type, quorum_nodes]).
-
%% Prior to 3.6.0, passwords were hashed using MD5, this populates
%% existing records with said default. Users created with 3.6.0+ will
%% have internal_user.hashing_algorithm populated by the internal
%% authn backend.
+
+-spec user_password_hashing() -> 'ok'.
+
user_password_hashing() ->
transform(
rabbit_user,
@@ -643,6 +625,8 @@ topic_permission() ->
{attributes, [topic_permission_key, permission]},
{disc_copies, [node()]}]).
+-spec exchange_options() -> 'ok'.
+
exchange_options() ->
ok = exchange_options(rabbit_exchange),
ok = exchange_options(rabbit_durable_exchange).
@@ -658,24 +642,6 @@ exchange_options(Table) ->
[name, type, durable, auto_delete, internal, arguments, scratches, policy,
operator_policy, decorators, options]).
-remove_explicit_default_exchange_bindings() ->
- Tab = rabbit_durable_queue,
- rabbit_table:wait([Tab]),
- %% Default exchange bindings are now implicit
- %% (not stored in the route tables).
- %% It should be safe to remove them outside of a
- %% transaction.
- Queues = mnesia:dirty_all_keys(Tab),
- N = length(Queues),
- case N of
- 0 -> ok;
- _ ->
- error_logger:info_msg("Will delete explicit default exchange bindings for ~p queues. "
- "This can take some time...", [N]),
- [rabbit_binding:remove_default_exchange_binding_rows_of(Q) || Q <- Queues]
- end,
- ok.
-
%%--------------------------------------------------------------------
transform(TableName, Fun, FieldList) ->
diff --git a/src/rabbit_variable_queue.erl b/src/rabbit_variable_queue.erl
index 4da073a518..8b773f2cc2 100644
--- a/src/rabbit_variable_queue.erl
+++ b/src/rabbit_variable_queue.erl
@@ -356,8 +356,9 @@
-define(QUEUE, lqueue).
--include("rabbit.hrl").
--include("rabbit_framing.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit_framing.hrl").
+-include("amqqueue.hrl").
%%----------------------------------------------------------------------------
@@ -427,10 +428,6 @@
io_batch_size :: pos_integer(),
mode :: 'default' | 'lazy',
memory_reduction_run_count :: non_neg_integer()}.
-%% Duplicated from rabbit_backing_queue
--spec ack([ack()], state()) -> {[rabbit_guid:guid()], state()}.
-
--spec multiple_routing_keys() -> 'ok'.
-define(BLANK_DELTA, #delta { start_seq_id = undefined,
count = 0,
@@ -532,8 +529,9 @@ init(Queue, Recover, Callback) ->
fun (MsgIds) -> msg_indices_written_to_disk(Callback, MsgIds) end,
fun (MsgIds) -> msgs_and_indices_written_to_disk(Callback, MsgIds) end).
-init(#amqqueue { name = QueueName, durable = IsDurable }, new,
- AsyncCallback, MsgOnDiskFun, MsgIdxOnDiskFun, MsgAndIdxOnDiskFun) ->
+init(Q, new, AsyncCallback, MsgOnDiskFun, MsgIdxOnDiskFun, MsgAndIdxOnDiskFun) when ?is_amqqueue(Q) ->
+ QueueName = amqqueue:get_name(Q),
+ IsDurable = amqqueue:is_durable(Q),
IndexState = rabbit_queue_index:init(QueueName,
MsgIdxOnDiskFun, MsgAndIdxOnDiskFun),
VHost = QueueName#resource.virtual_host,
@@ -547,8 +545,9 @@ init(#amqqueue { name = QueueName, durable = IsDurable }, new,
AsyncCallback, VHost), VHost);
%% We can be recovering a transient queue if it crashed
-init(#amqqueue { name = QueueName, durable = IsDurable }, Terms,
- AsyncCallback, MsgOnDiskFun, MsgIdxOnDiskFun, MsgAndIdxOnDiskFun) ->
+init(Q, Terms, AsyncCallback, MsgOnDiskFun, MsgIdxOnDiskFun, MsgAndIdxOnDiskFun) when ?is_amqqueue(Q) ->
+ QueueName = amqqueue:get_name(Q),
+ IsDurable = amqqueue:is_durable(Q),
{PRef, RecoveryTerms} = process_recovery_terms(Terms),
VHost = QueueName#resource.virtual_host,
{PersistentClient, ContainsCheckFun} =
@@ -620,7 +619,8 @@ delete_and_terminate(_Reason, State) ->
rabbit_msg_store:client_delete_and_terminate(MSCStateT),
a(State2 #vqstate { msg_store_clients = undefined }).
-delete_crashed(#amqqueue{name = QName}) ->
+delete_crashed(Q) when ?is_amqqueue(Q) ->
+ QName = amqqueue:get_name(Q),
ok = rabbit_queue_index:erase(QName).
purge(State = #vqstate { len = Len }) ->
@@ -700,6 +700,9 @@ drop(AckRequired, State) ->
{{MsgStatus#msg_status.msg_id, AckTag}, a(State2)}
end.
+%% Duplicated from rabbit_backing_queue
+-spec ack([ack()], state()) -> {[rabbit_guid:guid()], state()}.
+
ack([], State) ->
{[], State};
%% optimisation: this head is essentially a partial evaluation of the
@@ -2788,6 +2791,8 @@ ui(#vqstate{index_state = IndexState,
%% Upgrading
%%----------------------------------------------------------------------------
+-spec multiple_routing_keys() -> 'ok'.
+
multiple_routing_keys() ->
transform_storage(
fun ({basic_message, ExchangeName, Routing_Key, Content,
@@ -2825,7 +2830,7 @@ move_messages_to_vhost_store(Queues) ->
%% Move the queue index for each persistent queue to the new store
lists:foreach(
fun(Queue) ->
- #amqqueue{name = QueueName} = Queue,
+ QueueName = amqqueue:get_name(Queue),
rabbit_queue_index:move_to_per_vhost_stores(QueueName)
end,
Queues),
@@ -2938,17 +2943,16 @@ list_persistent_queues() ->
Node = node(),
mnesia:async_dirty(
fun () ->
- qlc:e(qlc:q([Q || Q = #amqqueue{name = Name,
- pid = Pid}
- <- mnesia:table(rabbit_durable_queue),
- node(Pid) == Node,
- mnesia:read(rabbit_queue, Name, read) =:= []]))
+ qlc:e(qlc:q([Q || Q <- mnesia:table(rabbit_durable_queue),
+ ?amqqueue_is_classic(Q),
+ amqqueue:qnode(Q) == Node,
+ mnesia:read(rabbit_queue, amqqueue:get_name(Q), read) =:= []]))
end).
read_old_recovery_terms([]) ->
{[], [], ?EMPTY_START_FUN_STATE};
read_old_recovery_terms(Queues) ->
- QueueNames = [Name || #amqqueue{name = Name} <- Queues],
+ QueueNames = [amqqueue:get_name(Q) || Q <- Queues],
{AllTerms, StartFunState} = rabbit_queue_index:read_global_recovery_terms(QueueNames),
Refs = [Ref || Terms <- AllTerms,
Terms /= non_clean_shutdown,
diff --git a/src/rabbit_version.erl b/src/rabbit_version.erl
index 5b1722fdbf..4a79ef9938 100644
--- a/src/rabbit_version.erl
+++ b/src/rabbit_version.erl
@@ -33,23 +33,6 @@
-type version() :: [atom()].
--spec recorded() -> rabbit_types:ok_or_error2(version(), any()).
--spec matches([A], [A]) -> boolean().
--spec desired() -> version().
--spec desired_for_scope(scope()) -> scope_version().
--spec record_desired() -> 'ok'.
--spec record_desired_for_scope
- (scope()) -> rabbit_types:ok_or_error(any()).
--spec upgrades_required
- (scope()) -> rabbit_types:ok_or_error2([step()], any()).
--spec check_version_consistency
- (string(), string(), string()) -> rabbit_types:ok_or_error(any()).
--spec check_version_consistency
- (string(), string(), string(), string()) ->
- rabbit_types:ok_or_error(any()).
--spec check_otp_consistency
- (string()) -> rabbit_types:ok_or_error(any()).
-
%% -------------------------------------------------------------------
-define(VERSION_FILENAME, "schema_version").
@@ -57,6 +40,8 @@
%% -------------------------------------------------------------------
+-spec recorded() -> rabbit_types:ok_or_error2(version(), any()).
+
recorded() -> case rabbit_file:read_term_file(schema_filename()) of
{ok, [V]} -> {ok, V};
{error, _} = Err -> Err
@@ -87,20 +72,34 @@ record_for_scope(Scope, ScopeVersion) ->
%% -------------------------------------------------------------------
+-spec matches([A], [A]) -> boolean().
+
matches(VerA, VerB) ->
lists:usort(VerA) =:= lists:usort(VerB).
%% -------------------------------------------------------------------
+-spec desired() -> version().
+
desired() -> [Name || Scope <- ?SCOPES, Name <- desired_for_scope(Scope)].
+-spec desired_for_scope(scope()) -> scope_version().
+
desired_for_scope(Scope) -> with_upgrade_graph(fun heads/1, Scope).
+-spec record_desired() -> 'ok'.
+
record_desired() -> record(desired()).
+-spec record_desired_for_scope
+ (scope()) -> rabbit_types:ok_or_error(any()).
+
record_desired_for_scope(Scope) ->
record_for_scope(Scope, desired_for_scope(Scope)).
+-spec upgrades_required
+ (scope()) -> rabbit_types:ok_or_error2([step()], any()).
+
upgrades_required(Scope) ->
case recorded_for_scope(Scope) of
{error, enoent} ->
@@ -208,9 +207,17 @@ schema_filename() -> filename:join(dir(), ?VERSION_FILENAME).
%% --------------------------------------------------------------------
+-spec check_version_consistency
+ (string(), string(), string()) -> rabbit_types:ok_or_error(any()).
+
check_version_consistency(This, Remote, Name) ->
check_version_consistency(This, Remote, Name, fun (A, B) -> A =:= B end).
+-spec check_version_consistency
+ (string(), string(), string(),
+ fun((string(), string()) -> boolean())) ->
+ rabbit_types:ok_or_error(any()).
+
check_version_consistency(This, Remote, Name, Comp) ->
case Comp(This, Remote) of
true -> ok;
@@ -222,5 +229,8 @@ version_error(Name, This, Remote) ->
rabbit_misc:format("~s version mismatch: local node is ~s, "
"remote node ~s", [Name, This, Remote])}}.
+-spec check_otp_consistency
+ (string()) -> rabbit_types:ok_or_error(any()).
+
check_otp_consistency(Remote) ->
check_version_consistency(rabbit_misc:otp_release(), Remote, "OTP").
diff --git a/src/rabbit_vhost.erl b/src/rabbit_vhost.erl
index cf12d04ce8..9180f9ca0a 100644
--- a/src/rabbit_vhost.erl
+++ b/src/rabbit_vhost.erl
@@ -16,7 +16,7 @@
-module(rabbit_vhost).
--include("rabbit.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
%%----------------------------------------------------------------------------
@@ -28,24 +28,6 @@
-export([delete_storage/1]).
-export([vhost_down/1]).
--spec add(rabbit_types:vhost(), rabbit_types:username()) -> rabbit_types:ok_or_error(any()).
--spec delete(rabbit_types:vhost(), rabbit_types:username()) -> rabbit_types:ok_or_error(any()).
--spec update(rabbit_types:vhost(), rabbit_misc:thunk(A)) -> A.
--spec exists(rabbit_types:vhost()) -> boolean().
--spec list() -> [rabbit_types:vhost()].
--spec with(rabbit_types:vhost(), rabbit_misc:thunk(A)) -> A.
--spec with_user_and_vhost
- (rabbit_types:username(), rabbit_types:vhost(), rabbit_misc:thunk(A)) -> A.
--spec assert(rabbit_types:vhost()) -> 'ok'.
-
--spec info(rabbit_types:vhost()) -> rabbit_types:infos().
--spec info(rabbit_types:vhost(), rabbit_types:info_keys())
- -> rabbit_types:infos().
--spec info_all() -> [rabbit_types:infos()].
--spec info_all(rabbit_types:info_keys()) -> [rabbit_types:infos()].
--spec info_all(rabbit_types:info_keys(), reference(), pid()) ->
- 'ok'.
-
recover() ->
%% Clear out remnants of old incarnation, in case we restarted
%% faster than other nodes handled DOWN messages from us.
@@ -72,8 +54,8 @@ recover(VHost) ->
ok = rabbit_file:ensure_dir(VHostStubFile),
ok = file:write_file(VHostStubFile, VHost),
Qs = rabbit_amqqueue:recover(VHost),
- ok = rabbit_binding:recover(rabbit_exchange:recover(VHost),
- [QName || #amqqueue{name = QName} <- Qs]),
+ QNames = [amqqueue:get_name(Q) || Q <- Qs],
+ ok = rabbit_binding:recover(rabbit_exchange:recover(VHost), QNames),
ok = rabbit_amqqueue:start(Qs),
%% Start queue mirrors.
ok = rabbit_mirror_queue_misc:on_vhost_up(VHost),
@@ -83,6 +65,8 @@ recover(VHost) ->
-define(INFO_KEYS, [name, tracing, cluster_state]).
+-spec add(rabbit_types:vhost(), rabbit_types:username()) -> rabbit_types:ok_or_error(any()).
+
add(VHost, ActingUser) ->
case exists(VHost) of
true -> ok;
@@ -124,16 +108,14 @@ do_add(VHostPath, ActingUser) ->
rabbit_event:notify(vhost_created, info(VHostPath)
++ [{user_who_performed_action, ActingUser}]),
R;
- {error, {no_such_vhost, VHostPath}} ->
- Msg = rabbit_misc:format("failed to set up vhost '~s': it was concurrently deleted!",
- [VHostPath]),
- {error, Msg};
{error, Reason} ->
Msg = rabbit_misc:format("failed to set up vhost '~s': ~p",
[VHostPath, Reason]),
{error, Msg}
end.
+-spec delete(rabbit_types:vhost(), rabbit_types:username()) -> rabbit_types:ok_or_error(any()).
+
delete(VHostPath, ActingUser) ->
%% FIXME: We are forced to delete the queues and exchanges outside
%% the TX below. Queue deletion involves sending messages to the queue
@@ -142,8 +124,10 @@ delete(VHostPath, ActingUser) ->
%% notifications which must be sent outside the TX
rabbit_log:info("Deleting vhost '~s'~n", [VHostPath]),
QDelFun = fun (Q) -> rabbit_amqqueue:delete(Q, false, false, ActingUser) end,
- [assert_benign(rabbit_amqqueue:with(Name, QDelFun), ActingUser) ||
- #amqqueue{name = Name} <- rabbit_amqqueue:list(VHostPath)],
+ [begin
+ Name = amqqueue:get_name(Q),
+ assert_benign(rabbit_amqqueue:with(Name, QDelFun), ActingUser)
+ end || Q <- rabbit_amqqueue:list(VHostPath)],
[assert_benign(rabbit_exchange:delete(Name, false, ActingUser), ActingUser) ||
#exchange{name = Name} <- rabbit_exchange:list(VHostPath)],
Funs = rabbit_misc:execute_mnesia_transaction(
@@ -226,10 +210,8 @@ assert_benign({error, not_found}, _) -> ok;
assert_benign({error, {absent, Q, _}}, ActingUser) ->
%% Removing the mnesia entries here is safe. If/when the down node
%% restarts, it will clear out the on-disk storage of the queue.
- case rabbit_amqqueue:internal_delete(Q#amqqueue.name, ActingUser) of
- ok -> ok;
- {error, not_found} -> ok
- end.
+ QName = amqqueue:get_name(Q),
+ rabbit_amqqueue:internal_delete(QName, ActingUser).
internal_delete(VHostPath, ActingUser) ->
[ok = rabbit_auth_backend_internal:clear_permissions(
@@ -249,12 +231,18 @@ internal_delete(VHostPath, ActingUser) ->
ok = mnesia:delete({rabbit_vhost, VHostPath}),
Fs1 ++ Fs2.
+-spec exists(rabbit_types:vhost()) -> boolean().
+
exists(VHostPath) ->
mnesia:dirty_read({rabbit_vhost, VHostPath}) /= [].
+-spec list() -> [rabbit_types:vhost()].
+
list() ->
mnesia:dirty_all_keys(rabbit_vhost).
+-spec with(rabbit_types:vhost(), rabbit_misc:thunk(A)) -> A.
+
with(VHostPath, Thunk) ->
fun () ->
case mnesia:read({rabbit_vhost, VHostPath}) of
@@ -265,15 +253,23 @@ with(VHostPath, Thunk) ->
end
end.
+-spec with_user_and_vhost
+ (rabbit_types:username(), rabbit_types:vhost(), rabbit_misc:thunk(A)) -> A.
+
with_user_and_vhost(Username, VHostPath, Thunk) ->
rabbit_misc:with_user(Username, with(VHostPath, Thunk)).
%% Like with/2 but outside an Mnesia tx
+
+-spec assert(rabbit_types:vhost()) -> 'ok'.
+
assert(VHostPath) -> case exists(VHostPath) of
true -> ok;
false -> throw({error, {no_such_vhost, VHostPath}})
end.
+-spec update(rabbit_types:vhost(), fun((#vhost{}) -> #vhost{})) -> #vhost{}.
+
update(VHostPath, Fun) ->
case mnesia:read({rabbit_vhost, VHostPath}) of
[] ->
@@ -326,13 +322,27 @@ i(tracing, VHost) -> rabbit_trace:enabled(VHost);
i(cluster_state, VHost) -> vhost_cluster_state(VHost);
i(Item, _) -> throw({bad_argument, Item}).
+-spec info(rabbit_types:vhost()) -> rabbit_types:infos().
+
info(VHost) -> infos(?INFO_KEYS, VHost).
+
+-spec info(rabbit_types:vhost(), rabbit_types:info_keys())
+ -> rabbit_types:infos().
+
info(VHost, Items) -> infos(Items, VHost).
+-spec info_all() -> [rabbit_types:infos()].
+
info_all() -> info_all(?INFO_KEYS).
+
+-spec info_all(rabbit_types:info_keys()) -> [rabbit_types:infos()].
+
info_all(Items) -> [info(VHost, Items) || VHost <- list()].
info_all(Ref, AggregatorPid) -> info_all(?INFO_KEYS, Ref, AggregatorPid).
+
+-spec info_all(rabbit_types:info_keys(), reference(), pid()) ->
+ 'ok'.
info_all(Items, Ref, AggregatorPid) ->
rabbit_control_misc:emitting_map(
AggregatorPid, Ref, fun(VHost) -> info(VHost, Items) end, list()).
diff --git a/src/rabbit_vm.erl b/src/rabbit_vm.erl
index 1b11017076..e78aca0a54 100644
--- a/src/rabbit_vm.erl
+++ b/src/rabbit_vm.erl
@@ -23,12 +23,6 @@
%%----------------------------------------------------------------------------
-spec memory() -> rabbit_types:infos().
--spec binary() -> rabbit_types:infos().
--spec ets_tables_memory(Owners) -> rabbit_types:infos()
- when Owners :: all | OwnerProcessName | [OwnerProcessName],
- OwnerProcessName :: atom().
-
-%%----------------------------------------------------------------------------
memory() ->
All = interesting_sups(),
@@ -115,6 +109,8 @@ memory() ->
%% claims about negative memory. See
%% http://erlang.org/pipermail/erlang-questions/2012-September/069320.html
+-spec binary() -> rabbit_types:infos().
+
binary() ->
All = interesting_sups(),
{Sums, Rest} =
@@ -153,6 +149,10 @@ mnesia_memory() ->
ets_memory(Owners) ->
lists:sum([V || {_K, V} <- ets_tables_memory(Owners)]).
+-spec ets_tables_memory(Owners) -> rabbit_types:infos()
+ when Owners :: all | OwnerProcessName | [OwnerProcessName],
+ OwnerProcessName :: atom().
+
ets_tables_memory(all) ->
[{ets:info(T, name), bytes(ets:info(T, memory))}
|| T <- ets:all(),
diff --git a/src/supervised_lifecycle.erl b/src/supervised_lifecycle.erl
index 82f6728f17..4be7922125 100644
--- a/src/supervised_lifecycle.erl
+++ b/src/supervised_lifecycle.erl
@@ -39,8 +39,6 @@
-spec start_link(atom(), rabbit_types:mfargs(), rabbit_types:mfargs()) ->
rabbit_types:ok_pid_or_error().
-%%----------------------------------------------------------------------------
-
start_link(Name, StartMFA, StopMFA) ->
gen_server:start_link({local, Name}, ?MODULE, [StartMFA, StopMFA], []).
diff --git a/src/tcp_listener.erl b/src/tcp_listener.erl
index d6c615e3b6..d5fb900198 100644
--- a/src/tcp_listener.erl
+++ b/src/tcp_listener.erl
@@ -64,8 +64,6 @@
mfargs(), mfargs(), string()) ->
rabbit_types:ok_pid_or_error().
-%%--------------------------------------------------------------------
-
start_link(IPAddress, Port,
OnStartup, OnShutdown, Label) ->
gen_server:start_link(
diff --git a/src/tcp_listener_sup.erl b/src/tcp_listener_sup.erl
index 074a4e4d6a..a31a60e505 100644
--- a/src/tcp_listener_sup.erl
+++ b/src/tcp_listener_sup.erl
@@ -38,8 +38,6 @@
module(), any(), mfargs(), mfargs(), integer(), string()) ->
rabbit_types:ok_pid_or_error().
-%%----------------------------------------------------------------------------
-
start_link(IPAddress, Port, Transport, SocketOpts, ProtoSup, ProtoOpts, OnStartup, OnShutdown,
ConcurrentAcceptorCount, Label) ->
supervisor:start_link(