|
|
@@ -27,10 +27,10 @@
|
|
|
, on_message_publish/2
|
|
|
]).
|
|
|
|
|
|
--export([ dispatch/4
|
|
|
- , delete_message/2
|
|
|
+-export([ delete_message/2
|
|
|
, store_retained/2
|
|
|
- , deliver/5]).
|
|
|
+ , get_backend_module/0
|
|
|
+ ]).
|
|
|
|
|
|
-export([ get_expiry_time/1
|
|
|
, update_config/1
|
|
|
@@ -54,8 +54,6 @@
|
|
|
, context_id := non_neg_integer()
|
|
|
, context := undefined | context()
|
|
|
, clear_timer := undefined | reference()
|
|
|
- , release_quota_timer := undefined | reference()
|
|
|
- , wait_quotas := list()
|
|
|
}.
|
|
|
|
|
|
-define(DEF_MAX_PAYLOAD_SIZE, (1024 * 1024)).
|
|
|
@@ -116,45 +114,6 @@ on_message_publish(Msg, _) ->
|
|
|
start_link() ->
|
|
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
|
|
|
|
|
--spec dispatch(context(), pid(), topic(), cursor()) -> ok.
|
|
|
-dispatch(Context, Pid, Topic, Cursor) ->
|
|
|
- Mod = get_backend_module(),
|
|
|
- case Cursor =/= undefined orelse emqx_topic:wildcard(Topic) of
|
|
|
- false ->
|
|
|
- {ok, Result} = Mod:read_message(Context, Topic),
|
|
|
- deliver(Result, Context, Pid, Topic, undefined);
|
|
|
- true ->
|
|
|
- {ok, Result, NewCursor} = Mod:match_messages(Context, Topic, Cursor),
|
|
|
- deliver(Result, Context, Pid, Topic, NewCursor)
|
|
|
- end.
|
|
|
-
|
|
|
-deliver([], Context, Pid, Topic, Cursor) ->
|
|
|
- case Cursor of
|
|
|
- undefined ->
|
|
|
- ok;
|
|
|
- _ ->
|
|
|
- dispatch(Context, Pid, Topic, Cursor)
|
|
|
- end;
|
|
|
-deliver(Result, #{context_id := Id} = Context, Pid, Topic, Cursor) ->
|
|
|
- case erlang:is_process_alive(Pid) of
|
|
|
- false ->
|
|
|
- ok;
|
|
|
- _ ->
|
|
|
- #{msg_deliver_quota := MaxDeliverNum} = emqx:get_config([retainer, flow_control]),
|
|
|
- case MaxDeliverNum of
|
|
|
- 0 ->
|
|
|
- _ = [Pid ! {deliver, Topic, Msg} || Msg <- Result],
|
|
|
- ok;
|
|
|
- _ ->
|
|
|
- case do_deliver(Result, Id, Pid, Topic) of
|
|
|
- ok ->
|
|
|
- deliver([], Context, Pid, Topic, Cursor);
|
|
|
- abort ->
|
|
|
- ok
|
|
|
- end
|
|
|
- end
|
|
|
- end.
|
|
|
-
|
|
|
get_expiry_time(#message{headers = #{properties := #{'Message-Expiry-Interval' := 0}}}) ->
|
|
|
0;
|
|
|
get_expiry_time(#message{headers = #{properties := #{'Message-Expiry-Interval' := Interval}},
|
|
|
@@ -198,7 +157,6 @@ stats_fun() ->
|
|
|
|
|
|
init([]) ->
|
|
|
emqx_conf:add_handler([retainer], ?MODULE),
|
|
|
- init_shared_context(),
|
|
|
State = new_state(),
|
|
|
#{enable := Enable} = Cfg = emqx:get_config([retainer]),
|
|
|
{ok,
|
|
|
@@ -213,9 +171,6 @@ handle_call({update_config, NewConf, OldConf}, _, State) ->
|
|
|
State2 = update_config(State, NewConf, OldConf),
|
|
|
{reply, ok, State2};
|
|
|
|
|
|
-handle_call({wait_semaphore, Id}, From, #{wait_quotas := Waits} = State) ->
|
|
|
- {noreply, State#{wait_quotas := [{Id, From} | Waits]}};
|
|
|
-
|
|
|
handle_call(clean, _, #{context := Context} = State) ->
|
|
|
clean(Context),
|
|
|
{reply, ok, State};
|
|
|
@@ -249,30 +204,12 @@ handle_info(clear_expired, #{context := Context} = State) ->
|
|
|
Interval = emqx_conf:get([retainer, msg_clear_interval], ?DEF_EXPIRY_INTERVAL),
|
|
|
{noreply, State#{clear_timer := add_timer(Interval, clear_expired)}, hibernate};
|
|
|
|
|
|
-handle_info(release_deliver_quota, #{context := Context, wait_quotas := Waits} = State) ->
|
|
|
- insert_shared_context(?DELIVER_SEMAPHORE, get_msg_deliver_quota()),
|
|
|
- case Waits of
|
|
|
- [] ->
|
|
|
- ok;
|
|
|
- _ ->
|
|
|
- #{context_id := NowId} = Context,
|
|
|
- Waits2 = lists:reverse(Waits),
|
|
|
- lists:foreach(fun({Id, From}) ->
|
|
|
- gen_server:reply(From, Id =:= NowId)
|
|
|
- end,
|
|
|
- Waits2)
|
|
|
- end,
|
|
|
- Interval = emqx:get_config([retainer, flow_control, quota_release_interval]),
|
|
|
- {noreply, State#{release_quota_timer := add_timer(Interval, release_deliver_quota),
|
|
|
- wait_quotas := []}};
|
|
|
-
|
|
|
handle_info(Info, State) ->
|
|
|
?SLOG(error, #{msg => "unexpected_info", info => Info}),
|
|
|
{noreply, State}.
|
|
|
|
|
|
-terminate(_Reason, #{clear_timer := TRef1, release_quota_timer := TRef2}) ->
|
|
|
- _ = stop_timer(TRef1),
|
|
|
- _ = stop_timer(TRef2),
|
|
|
+terminate(_Reason, #{clear_timer := ClearTimer}) ->
|
|
|
+ _ = stop_timer(ClearTimer),
|
|
|
ok.
|
|
|
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
|
@@ -286,22 +223,19 @@ new_state() ->
|
|
|
#{enable => false,
|
|
|
context_id => 0,
|
|
|
context => undefined,
|
|
|
- clear_timer => undefined,
|
|
|
- release_quota_timer => undefined,
|
|
|
- wait_quotas => []}.
|
|
|
+ clear_timer => undefined
|
|
|
+ }.
|
|
|
|
|
|
-spec new_context(pos_integer()) -> context().
|
|
|
new_context(Id) ->
|
|
|
#{context_id => Id}.
|
|
|
|
|
|
-
|
|
|
payload_size_limit() ->
|
|
|
emqx_conf:get(?MAX_PAYLOAD_SIZE_CONFIG_PATH, ?DEF_MAX_PAYLOAD_SIZE).
|
|
|
|
|
|
%% @private
|
|
|
dispatch(Context, Topic) ->
|
|
|
- emqx_retainer_pool:async_submit(fun ?MODULE:dispatch/4,
|
|
|
- [Context, self(), Topic, undefined]).
|
|
|
+ emqx_retainer_dispatcher:dispatch(Context, Topic).
|
|
|
|
|
|
-spec delete_message(context(), topic()) -> ok.
|
|
|
delete_message(Context, Topic) ->
|
|
|
@@ -328,53 +262,6 @@ clean(Context) ->
|
|
|
Mod = get_backend_module(),
|
|
|
Mod:clean(Context).
|
|
|
|
|
|
--spec do_deliver(list(term()), pos_integer(), pid(), topic()) -> ok | abort.
|
|
|
-do_deliver([Msg | T], Id, Pid, Topic) ->
|
|
|
- case require_semaphore(?DELIVER_SEMAPHORE, Id) of
|
|
|
- true ->
|
|
|
- Pid ! {deliver, Topic, Msg},
|
|
|
- do_deliver(T, Id, Pid, Topic);
|
|
|
- _ ->
|
|
|
- abort
|
|
|
- end;
|
|
|
-do_deliver([], _, _, _) ->
|
|
|
- ok.
|
|
|
-
|
|
|
--spec require_semaphore(semaphore(), pos_integer()) -> boolean().
|
|
|
-require_semaphore(Semaphore, Id) ->
|
|
|
- Remained = ets:update_counter(?SHARED_CONTEXT_TAB,
|
|
|
- Semaphore,
|
|
|
- {#shared_context.value, -1, -1, -1}),
|
|
|
- wait_semaphore(Remained, Id).
|
|
|
-
|
|
|
--spec wait_semaphore(non_neg_integer(), pos_integer()) -> boolean().
|
|
|
-wait_semaphore(X, Id) when X < 0 ->
|
|
|
- call({?FUNCTION_NAME, Id});
|
|
|
-wait_semaphore(_, _) ->
|
|
|
- true.
|
|
|
-
|
|
|
--spec init_shared_context() -> ok.
|
|
|
-init_shared_context() ->
|
|
|
- ?SHARED_CONTEXT_TAB = ets:new(?SHARED_CONTEXT_TAB,
|
|
|
- [ set, named_table, public
|
|
|
- , {keypos, #shared_context.key}
|
|
|
- , {write_concurrency, true}
|
|
|
- , {read_concurrency, true}]),
|
|
|
- lists:foreach(fun({K, V}) ->
|
|
|
- insert_shared_context(K, V)
|
|
|
- end,
|
|
|
- [{?DELIVER_SEMAPHORE, get_msg_deliver_quota()}]).
|
|
|
-
|
|
|
-
|
|
|
--spec insert_shared_context(shared_context_key(), term()) -> ok.
|
|
|
-insert_shared_context(Key, Term) ->
|
|
|
- ets:insert(?SHARED_CONTEXT_TAB, #shared_context{key = Key, value = Term}),
|
|
|
- ok.
|
|
|
-
|
|
|
--spec get_msg_deliver_quota() -> non_neg_integer().
|
|
|
-get_msg_deliver_quota() ->
|
|
|
- emqx:get_config([retainer, flow_control, msg_deliver_quota]).
|
|
|
-
|
|
|
-spec update_config(state(), hocons:config(), hocons:config()) -> state().
|
|
|
update_config(State, Conf, OldConf) ->
|
|
|
update_config(maps:get(enable, Conf),
|
|
|
@@ -391,24 +278,19 @@ update_config(true, false, State, NewConf, _) ->
|
|
|
enable_retainer(State, NewConf);
|
|
|
|
|
|
update_config(true, true,
|
|
|
- #{clear_timer := ClearTimer,
|
|
|
- release_quota_timer := QuotaTimer} = State, NewConf, OldConf) ->
|
|
|
- #{config := Cfg,
|
|
|
- flow_control := #{quota_release_interval := QuotaInterval},
|
|
|
+ #{clear_timer := ClearTimer} = State, NewConf, OldConf) ->
|
|
|
+ #{backend := BackendCfg,
|
|
|
msg_clear_interval := ClearInterval} = NewConf,
|
|
|
|
|
|
- #{config := OldCfg} = OldConf,
|
|
|
+ #{backend := OldBackendCfg} = OldConf,
|
|
|
|
|
|
- StorageType = maps:get(type, Cfg),
|
|
|
- OldStrorageType = maps:get(type, OldCfg),
|
|
|
+ StorageType = maps:get(type, BackendCfg),
|
|
|
+ OldStrorageType = maps:get(type, OldBackendCfg),
|
|
|
case OldStrorageType of
|
|
|
StorageType ->
|
|
|
State#{clear_timer := check_timer(ClearTimer,
|
|
|
ClearInterval,
|
|
|
- clear_expired),
|
|
|
- release_quota_timer := check_timer(QuotaTimer,
|
|
|
- QuotaInterval,
|
|
|
- release_deliver_quota)};
|
|
|
+ clear_expired)};
|
|
|
_ ->
|
|
|
State2 = disable_retainer(State),
|
|
|
enable_retainer(State2, NewConf)
|
|
|
@@ -417,29 +299,23 @@ update_config(true, true,
|
|
|
-spec enable_retainer(state(), hocon:config()) -> state().
|
|
|
enable_retainer(#{context_id := ContextId} = State,
|
|
|
#{msg_clear_interval := ClearInterval,
|
|
|
- flow_control := #{quota_release_interval := ReleaseInterval},
|
|
|
- config := Config}) ->
|
|
|
+ backend := BackendCfg}) ->
|
|
|
NewContextId = ContextId + 1,
|
|
|
- Context = create_resource(new_context(NewContextId), Config),
|
|
|
+ Context = create_resource(new_context(NewContextId), BackendCfg),
|
|
|
load(Context),
|
|
|
State#{enable := true,
|
|
|
context_id := NewContextId,
|
|
|
context := Context,
|
|
|
- clear_timer := add_timer(ClearInterval, clear_expired),
|
|
|
- release_quota_timer := add_timer(ReleaseInterval, release_deliver_quota)}.
|
|
|
+ clear_timer := add_timer(ClearInterval, clear_expired)}.
|
|
|
|
|
|
-spec disable_retainer(state()) -> state().
|
|
|
-disable_retainer(#{clear_timer := TRef1,
|
|
|
- release_quota_timer := TRef2,
|
|
|
- context := Context,
|
|
|
- wait_quotas := Waits} = State) ->
|
|
|
+disable_retainer(#{clear_timer := ClearTimer,
|
|
|
+ context := Context} = State) ->
|
|
|
unload(),
|
|
|
- ok = lists:foreach(fun(E) -> gen_server:reply(E, false) end, Waits),
|
|
|
ok = close_resource(Context),
|
|
|
State#{enable := false,
|
|
|
- clear_timer := stop_timer(TRef1),
|
|
|
- release_quota_timer := stop_timer(TRef2),
|
|
|
- wait_quotas := []}.
|
|
|
+ clear_timer := stop_timer(ClearTimer)
|
|
|
+ }.
|
|
|
|
|
|
-spec stop_timer(undefined | reference()) -> undefined.
|
|
|
stop_timer(undefined) ->
|
|
|
@@ -466,7 +342,7 @@ check_timer(Timer, _, _) ->
|
|
|
|
|
|
-spec get_backend_module() -> backend().
|
|
|
get_backend_module() ->
|
|
|
- ModName = case emqx:get_config([retainer, config]) of
|
|
|
+ ModName = case emqx:get_config([retainer, backend]) of
|
|
|
#{type := built_in_database} -> mnesia;
|
|
|
#{type := Backend} -> Backend
|
|
|
end,
|