|
|
@@ -32,8 +32,7 @@
|
|
|
|
|
|
-export([
|
|
|
delete_message/2,
|
|
|
- store_retained/2,
|
|
|
- get_backend_module/0
|
|
|
+ store_retained/2
|
|
|
]).
|
|
|
|
|
|
-export([
|
|
|
@@ -45,7 +44,15 @@
|
|
|
page_read/3,
|
|
|
post_config_update/5,
|
|
|
stats_fun/0,
|
|
|
- retained_count/0
|
|
|
+ retained_count/0,
|
|
|
+ backend_module/0,
|
|
|
+ backend_module/1,
|
|
|
+ enabled/0
|
|
|
+]).
|
|
|
+
|
|
|
+%% For testing only
|
|
|
+-export([
|
|
|
+ context/0
|
|
|
]).
|
|
|
|
|
|
%% gen_server callbacks
|
|
|
@@ -54,8 +61,7 @@
|
|
|
handle_call/3,
|
|
|
handle_cast/2,
|
|
|
handle_info/2,
|
|
|
- terminate/2,
|
|
|
- code_change/3
|
|
|
+ terminate/2
|
|
|
]).
|
|
|
|
|
|
-export_type([
|
|
|
@@ -71,29 +77,35 @@
|
|
|
context := undefined | context(),
|
|
|
clear_timer := undefined | reference()
|
|
|
}.
|
|
|
--type context() :: term().
|
|
|
+
|
|
|
+-type backend_state() :: term().
|
|
|
+
|
|
|
+-type context() :: #{
|
|
|
+ module := module(),
|
|
|
+ state := backend_state()
|
|
|
+}.
|
|
|
|
|
|
-type topic() :: emqx_types:topic().
|
|
|
-type message() :: emqx_types:message().
|
|
|
-type cursor() :: undefined | term().
|
|
|
--type backend() :: emqx_retainer_mnesia | module().
|
|
|
+-type has_next() :: boolean().
|
|
|
|
|
|
-define(DEF_MAX_PAYLOAD_SIZE, (1024 * 1024)).
|
|
|
-define(DEF_EXPIRY_INTERVAL, 0).
|
|
|
-define(MAX_PAYLOAD_SIZE_CONFIG_PATH, [retainer, max_payload_size]).
|
|
|
|
|
|
--callback create(map()) -> context().
|
|
|
--callback update(context(), map()) -> ok | need_recreate.
|
|
|
--callback close(context()) -> ok.
|
|
|
--callback delete_message(context(), topic()) -> ok.
|
|
|
--callback store_retained(context(), message()) -> ok.
|
|
|
--callback read_message(context(), topic()) -> {ok, list()}.
|
|
|
--callback page_read(context(), topic(), non_neg_integer(), non_neg_integer()) ->
|
|
|
- {ok, HasNext :: boolean(), list()}.
|
|
|
--callback match_messages(context(), topic(), cursor()) -> {ok, list(), cursor()}.
|
|
|
--callback clear_expired(context()) -> ok.
|
|
|
--callback clean(context()) -> ok.
|
|
|
--callback size(context()) -> non_neg_integer().
|
|
|
+-callback create(hocon:config()) -> backend_state().
|
|
|
+-callback update(backend_state(), hocon:config()) -> ok | need_recreate.
|
|
|
+-callback close(backend_state()) -> ok.
|
|
|
+-callback delete_message(backend_state(), topic()) -> ok.
|
|
|
+-callback store_retained(backend_state(), message()) -> ok.
|
|
|
+-callback read_message(backend_state(), topic()) -> {ok, list(message())}.
|
|
|
+-callback page_read(backend_state(), emqx_maybe:t(topic()), non_neg_integer(), non_neg_integer()) ->
|
|
|
+ {ok, has_next(), list(message())}.
|
|
|
+-callback match_messages(backend_state(), topic(), cursor()) -> {ok, list(message()), cursor()}.
|
|
|
+-callback clear_expired(backend_state()) -> ok.
|
|
|
+-callback clean(backend_state()) -> ok.
|
|
|
+-callback size(backend_state()) -> non_neg_integer().
|
|
|
|
|
|
%%------------------------------------------------------------------------------
|
|
|
%% Hook API
|
|
|
@@ -131,6 +143,13 @@ on_message_publish(Msg = #message{flags = #{retain := true}}, Context) ->
|
|
|
on_message_publish(Msg, _) ->
|
|
|
{ok, Msg}.
|
|
|
|
|
|
+%%------------------------------------------------------------------------------
|
|
|
+%% Config API
|
|
|
+%%------------------------------------------------------------------------------
|
|
|
+
|
|
|
+post_config_update(_, _UpdateReq, NewConf, OldConf, _AppEnvs) ->
|
|
|
+ call({update_config, NewConf, OldConf}).
|
|
|
+
|
|
|
%%------------------------------------------------------------------------------
|
|
|
%% APIs
|
|
|
%%------------------------------------------------------------------------------
|
|
|
@@ -140,6 +159,7 @@ on_message_publish(Msg, _) ->
|
|
|
start_link() ->
|
|
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
|
|
|
|
|
+-spec get_expiry_time(message()) -> non_neg_integer().
|
|
|
get_expiry_time(#message{headers = #{properties := #{'Message-Expiry-Interval' := 0}}}) ->
|
|
|
0;
|
|
|
get_expiry_time(#message{
|
|
|
@@ -154,41 +174,46 @@ get_expiry_time(#message{timestamp = Ts}) ->
|
|
|
_ -> Ts + Interval
|
|
|
end.
|
|
|
|
|
|
-get_stop_publish_clear_msg() ->
|
|
|
- emqx_conf:get([retainer, stop_publish_clear_msg], false).
|
|
|
-
|
|
|
-spec update_config(hocon:config()) -> {ok, _} | {error, _}.
|
|
|
update_config(Conf) ->
|
|
|
emqx_conf:update([retainer], Conf, #{override_to => cluster}).
|
|
|
|
|
|
+-spec clean() -> ok.
|
|
|
clean() ->
|
|
|
call(?FUNCTION_NAME).
|
|
|
|
|
|
+-spec delete(topic()) -> ok.
|
|
|
delete(Topic) ->
|
|
|
call({?FUNCTION_NAME, Topic}).
|
|
|
|
|
|
+-spec retained_count() -> non_neg_integer().
|
|
|
retained_count() ->
|
|
|
call(?FUNCTION_NAME).
|
|
|
|
|
|
+-spec read_message(topic()) -> {ok, list(message())}.
|
|
|
read_message(Topic) ->
|
|
|
call({?FUNCTION_NAME, Topic}).
|
|
|
|
|
|
+-spec page_read(emqx_maybe:t(topic()), non_neg_integer(), non_neg_integer()) ->
|
|
|
+ {ok, has_next(), list(message())}.
|
|
|
page_read(Topic, Page, Limit) ->
|
|
|
call({?FUNCTION_NAME, Topic, Page, Limit}).
|
|
|
|
|
|
-post_config_update(_, _UpdateReq, NewConf, OldConf, _AppEnvs) ->
|
|
|
- call({update_config, NewConf, OldConf}).
|
|
|
-
|
|
|
-call(Req) ->
|
|
|
- gen_server:call(?MODULE, Req, infinity).
|
|
|
+-spec enabled() -> boolean().
|
|
|
+enabled() ->
|
|
|
+ call(?FUNCTION_NAME).
|
|
|
|
|
|
-stats_fun() ->
|
|
|
- gen_server:cast(?MODULE, ?FUNCTION_NAME).
|
|
|
+-spec context() -> ok.
|
|
|
+context() ->
|
|
|
+ call(?FUNCTION_NAME).
|
|
|
|
|
|
%%------------------------------------------------------------------------------
|
|
|
-%% APIs
|
|
|
+%% Internal APIs
|
|
|
%%------------------------------------------------------------------------------
|
|
|
|
|
|
+stats_fun() ->
|
|
|
+ gen_server:cast(?MODULE, ?FUNCTION_NAME).
|
|
|
+
|
|
|
-spec get_basic_usage_info() -> #{retained_messages => non_neg_integer()}.
|
|
|
get_basic_usage_info() ->
|
|
|
try
|
|
|
@@ -206,13 +231,14 @@ init([]) ->
|
|
|
erlang:process_flag(trap_exit, true),
|
|
|
emqx_conf:add_handler([retainer], ?MODULE),
|
|
|
State = new_state(),
|
|
|
- #{enable := Enable} = Cfg = emqx:get_config([retainer]),
|
|
|
+ RetainerConfig = emqx:get_config([retainer]),
|
|
|
{ok,
|
|
|
- case Enable of
|
|
|
+ case maps:get(enable, RetainerConfig) of
|
|
|
+ false ->
|
|
|
+ State;
|
|
|
true ->
|
|
|
- enable_retainer(State, Cfg);
|
|
|
- _ ->
|
|
|
- State
|
|
|
+ BackendConfig = enabled_backend_config(RetainerConfig),
|
|
|
+ enable_retainer(State, RetainerConfig, BackendConfig)
|
|
|
end}.
|
|
|
|
|
|
handle_call({update_config, NewConf, OldConf}, _, State) ->
|
|
|
@@ -220,39 +246,34 @@ handle_call({update_config, NewConf, OldConf}, _, State) ->
|
|
|
emqx_retainer_dispatcher:refresh_limiter(NewConf),
|
|
|
{reply, ok, State2};
|
|
|
handle_call(clean, _, #{context := Context} = State) ->
|
|
|
- clean(Context),
|
|
|
+ _ = clean(Context),
|
|
|
{reply, ok, State};
|
|
|
handle_call({delete, Topic}, _, #{context := Context} = State) ->
|
|
|
- delete_message(Context, Topic),
|
|
|
+ _ = delete_message(Context, Topic),
|
|
|
{reply, ok, State};
|
|
|
handle_call({read_message, Topic}, _, #{context := Context} = State) ->
|
|
|
- Mod = get_backend_module(),
|
|
|
- Result = Mod:read_message(Context, Topic),
|
|
|
- {reply, Result, State};
|
|
|
+ {reply, read_message(Context, Topic), State};
|
|
|
handle_call({page_read, Topic, Page, Limit}, _, #{context := Context} = State) ->
|
|
|
- Mod = get_backend_module(),
|
|
|
- Result = Mod:page_read(Context, Topic, Page, Limit),
|
|
|
- {reply, Result, State};
|
|
|
+ {reply, page_read(Context, Topic, Page, Limit), State};
|
|
|
handle_call(retained_count, _From, State = #{context := Context}) ->
|
|
|
- Mod = get_backend_module(),
|
|
|
- RetainedCount = Mod:size(Context),
|
|
|
- {reply, RetainedCount, State};
|
|
|
+ {reply, count(Context), State};
|
|
|
+handle_call(enabled, _From, State = #{enable := Enable}) ->
|
|
|
+ {reply, Enable, State};
|
|
|
+handle_call(context, _From, State = #{context := Context}) ->
|
|
|
+ {reply, Context, State};
|
|
|
handle_call(Req, _From, State) ->
|
|
|
?SLOG(error, #{msg => "unexpected_call", call => Req}),
|
|
|
{reply, ignored, State}.
|
|
|
|
|
|
handle_cast(stats_fun, #{context := Context} = State) ->
|
|
|
- Mod = get_backend_module(),
|
|
|
- Size = Mod:size(Context),
|
|
|
- emqx_stats:setstat('retained.count', 'retained.max', Size),
|
|
|
+ emqx_stats:setstat('retained.count', 'retained.max', count(Context)),
|
|
|
{noreply, State};
|
|
|
handle_cast(Msg, State) ->
|
|
|
?SLOG(error, #{msg => "unexpected_cast", cast => Msg}),
|
|
|
{noreply, State}.
|
|
|
|
|
|
handle_info(clear_expired, #{context := Context} = State) ->
|
|
|
- Mod = get_backend_module(),
|
|
|
- Mod:clear_expired(Context),
|
|
|
+ ok = clear_expired(Context),
|
|
|
Interval = emqx_conf:get([retainer, msg_clear_interval], ?DEF_EXPIRY_INTERVAL),
|
|
|
{noreply, State#{clear_timer := add_timer(Interval, clear_expired)}, hibernate};
|
|
|
handle_info(Info, State) ->
|
|
|
@@ -264,12 +285,16 @@ terminate(_Reason, #{clear_timer := ClearTimer}) ->
|
|
|
_ = stop_timer(ClearTimer),
|
|
|
ok.
|
|
|
|
|
|
-code_change(_OldVsn, State, _Extra) ->
|
|
|
- {ok, State}.
|
|
|
-
|
|
|
%%------------------------------------------------------------------------------
|
|
|
%% Internal functions
|
|
|
%%------------------------------------------------------------------------------
|
|
|
+
|
|
|
+call(Req) ->
|
|
|
+ gen_server:call(?MODULE, Req, infinity).
|
|
|
+
|
|
|
+get_stop_publish_clear_msg() ->
|
|
|
+ emqx_conf:get([retainer, stop_publish_clear_msg], false).
|
|
|
+
|
|
|
-spec new_state() -> state().
|
|
|
new_state() ->
|
|
|
#{
|
|
|
@@ -287,8 +312,34 @@ dispatch(Context, Topic) ->
|
|
|
|
|
|
-spec delete_message(context(), topic()) -> ok.
|
|
|
delete_message(Context, Topic) ->
|
|
|
- Mod = get_backend_module(),
|
|
|
- Mod:delete_message(Context, Topic).
|
|
|
+ Mod = backend_module(Context),
|
|
|
+ BackendState = backend_state(Context),
|
|
|
+ Mod:delete_message(BackendState, Topic).
|
|
|
+
|
|
|
+-spec read_message(context(), topic()) -> {ok, list(message())}.
|
|
|
+read_message(Context, Topic) ->
|
|
|
+ Mod = backend_module(Context),
|
|
|
+ BackendState = backend_state(Context),
|
|
|
+ Mod:read_message(BackendState, Topic).
|
|
|
+
|
|
|
+-spec page_read(context(), emqx_maybe:t(topic()), non_neg_integer(), non_neg_integer()) ->
|
|
|
+ {ok, has_next(), list(message())}.
|
|
|
+page_read(Context, Topic, Page, Limit) ->
|
|
|
+ Mod = backend_module(Context),
|
|
|
+ BackendState = backend_state(Context),
|
|
|
+ Mod:page_read(BackendState, Topic, Page, Limit).
|
|
|
+
|
|
|
+-spec count(context()) -> non_neg_integer().
|
|
|
+count(Context) ->
|
|
|
+ Mod = backend_module(Context),
|
|
|
+ BackendState = backend_state(Context),
|
|
|
+ Mod:size(BackendState).
|
|
|
+
|
|
|
+-spec clear_expired(context()) -> ok.
|
|
|
+clear_expired(Context) ->
|
|
|
+ Mod = backend_module(Context),
|
|
|
+ BackendState = backend_state(Context),
|
|
|
+ Mod:clear_expired(BackendState).
|
|
|
|
|
|
-spec store_retained(context(), message()) -> ok.
|
|
|
store_retained(Context, #message{topic = Topic, payload = Payload} = Msg) ->
|
|
|
@@ -303,52 +354,47 @@ store_retained(Context, #message{topic = Topic, payload = Payload} = Msg) ->
|
|
|
limit => Limit
|
|
|
});
|
|
|
_ ->
|
|
|
- Mod = get_backend_module(),
|
|
|
- Mod:store_retained(Context, Msg)
|
|
|
+ Mod = backend_module(Context),
|
|
|
+ BackendState = backend_state(Context),
|
|
|
+ Mod:store_retained(BackendState, Msg)
|
|
|
end.
|
|
|
|
|
|
-spec clean(context()) -> ok.
|
|
|
clean(Context) ->
|
|
|
- Mod = get_backend_module(),
|
|
|
- Mod:clean(Context).
|
|
|
+ Mod = backend_module(Context),
|
|
|
+ BackendState = backend_state(Context),
|
|
|
+ Mod:clean(BackendState).
|
|
|
|
|
|
-spec update_config(state(), hocon:config(), hocon:config()) -> state().
|
|
|
-update_config(State, Conf, OldConf) ->
|
|
|
+update_config(State, NewConfig, OldConfig) ->
|
|
|
update_config(
|
|
|
- maps:get(enable, Conf),
|
|
|
- maps:get(enable, OldConf),
|
|
|
+ maps:get(enable, NewConfig),
|
|
|
+ maps:get(enable, OldConfig),
|
|
|
State,
|
|
|
- Conf,
|
|
|
- OldConf
|
|
|
+ NewConfig,
|
|
|
+ OldConfig
|
|
|
).
|
|
|
|
|
|
-spec update_config(boolean(), boolean(), state(), hocon:config(), hocon:config()) -> state().
|
|
|
update_config(false, _, State, _, _) ->
|
|
|
disable_retainer(State);
|
|
|
-update_config(true, false, State, NewConf, _) ->
|
|
|
- enable_retainer(State, NewConf);
|
|
|
+update_config(true, false, State, NewConfig, _) ->
|
|
|
+ enable_retainer(State, NewConfig, enabled_backend_config(NewConfig));
|
|
|
update_config(
|
|
|
true,
|
|
|
true,
|
|
|
#{clear_timer := ClearTimer, context := Context} = State,
|
|
|
- NewConf,
|
|
|
- OldConf
|
|
|
+ NewConfig,
|
|
|
+ OldConfig
|
|
|
) ->
|
|
|
- #{
|
|
|
- backend := #{
|
|
|
- type := BackendType
|
|
|
- } = NewBackendConfig,
|
|
|
- msg_clear_interval := ClearInterval
|
|
|
- } = NewConf,
|
|
|
-
|
|
|
- #{
|
|
|
- backend := #{
|
|
|
- type := OldBackendType
|
|
|
- }
|
|
|
- } = OldConf,
|
|
|
- SameBackendType = BackendType =:= OldBackendType,
|
|
|
- Mod = get_backend_module(),
|
|
|
- case SameBackendType andalso ok =:= Mod:update(Context, NewBackendConfig) of
|
|
|
+ #{msg_clear_interval := ClearInterval} = NewConfig,
|
|
|
+ OldBackendConfig = enabled_backend_config(OldConfig),
|
|
|
+ NewBackendConfig = enabled_backend_config(NewConfig),
|
|
|
+ OldMod = config_backend_module(OldBackendConfig),
|
|
|
+ NewMod = config_backend_module(NewBackendConfig),
|
|
|
+
|
|
|
+ SameBackendType = NewMod =:= OldMod,
|
|
|
+ case SameBackendType andalso ok =:= OldMod:update(Context, NewBackendConfig) of
|
|
|
true ->
|
|
|
State#{
|
|
|
clear_timer := check_timer(
|
|
|
@@ -359,18 +405,16 @@ update_config(
|
|
|
};
|
|
|
false ->
|
|
|
State2 = disable_retainer(State),
|
|
|
- enable_retainer(State2, NewConf)
|
|
|
+ enable_retainer(State2, NewConfig, NewBackendConfig)
|
|
|
end.
|
|
|
|
|
|
--spec enable_retainer(state(), hocon:config()) -> state().
|
|
|
+-spec enable_retainer(state(), hocon:config(), hocon:config()) -> state().
|
|
|
enable_retainer(
|
|
|
State,
|
|
|
- #{
|
|
|
- msg_clear_interval := ClearInterval,
|
|
|
- backend := BackendCfg
|
|
|
- }
|
|
|
+ #{msg_clear_interval := ClearInterval} = _RetainerConfig,
|
|
|
+ BackendConfig
|
|
|
) ->
|
|
|
- Context = create(BackendCfg),
|
|
|
+ Context = create(BackendConfig),
|
|
|
ok = load(Context),
|
|
|
State#{
|
|
|
enable := true,
|
|
|
@@ -415,20 +459,43 @@ check_timer(Timer, undefined, _) ->
|
|
|
check_timer(Timer, _, _) ->
|
|
|
Timer.
|
|
|
|
|
|
--spec get_backend_module() -> backend().
|
|
|
-get_backend_module() ->
|
|
|
- case emqx:get_config([retainer, backend]) of
|
|
|
+-spec enabled_backend_config(hocon:config()) -> hocon:config() | no_return().
|
|
|
+enabled_backend_config(#{backend := Backend, external_backends := ExternalBackends} = Config) ->
|
|
|
+ AllBackends = [Backend | maps:values(ExternalBackends)],
|
|
|
+ case lists:search(fun(#{enable := Enable}) -> Enable end, AllBackends) of
|
|
|
+ {value, EnabledBackend} -> EnabledBackend;
|
|
|
+ false -> error({no_enabled_backend, Config})
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec config_backend_module(hocon:config()) -> module().
|
|
|
+config_backend_module(Config) ->
|
|
|
+ case Config of
|
|
|
#{type := built_in_database} -> emqx_retainer_mnesia;
|
|
|
- #{type := Backend} -> Backend
|
|
|
+ #{module := Module} -> Module
|
|
|
end.
|
|
|
|
|
|
-create(#{type := built_in_database} = Cfg) ->
|
|
|
- Mod = get_backend_module(),
|
|
|
- Mod:create(Cfg).
|
|
|
+-spec backend_module(context()) -> module().
|
|
|
+backend_module(#{module := Module}) -> Module.
|
|
|
+
|
|
|
+-spec backend_state(context()) -> backend_state().
|
|
|
+backend_state(#{state := State}) -> State.
|
|
|
+
|
|
|
+-spec backend_module() -> module() | no_return().
|
|
|
+backend_module() ->
|
|
|
+ Config = enabled_backend_config(emqx:get_config([retainer])),
|
|
|
+ config_backend_module(Config).
|
|
|
+
|
|
|
+-spec create(hocon:config()) -> context().
|
|
|
+create(Cfg) ->
|
|
|
+ Mod = config_backend_module(Cfg),
|
|
|
+ #{
|
|
|
+ module => Mod,
|
|
|
+ state => Mod:create(Cfg)
|
|
|
+ }.
|
|
|
|
|
|
-spec close(context()) -> ok | {error, term()}.
|
|
|
close(Context) ->
|
|
|
- Mod = get_backend_module(),
|
|
|
+ Mod = backend_module(Context),
|
|
|
Mod:close(Context).
|
|
|
|
|
|
-spec load(context()) -> ok.
|