|
|
@@ -22,11 +22,14 @@
|
|
|
|
|
|
-include("emqttd_internal.hrl").
|
|
|
|
|
|
-%% API Exports
|
|
|
--export([start_link/3, subscribe/2, unsubscribe/2, publish/2,
|
|
|
- async_subscribe/2, async_unsubscribe/2]).
|
|
|
+%% Start API.
|
|
|
+-export([start_link/3]).
|
|
|
|
|
|
--export([subscribers/1, dispatch/2]).
|
|
|
+%% PubSub API.
|
|
|
+-export([subscribe/3, async_subscribe/3, publish/2, unsubscribe/3,
|
|
|
+ async_unsubscribe/3, subscribers/1]).
|
|
|
+
|
|
|
+-export([dispatch/2]).
|
|
|
|
|
|
%% gen_server.
|
|
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
|
|
@@ -36,24 +39,38 @@
|
|
|
|
|
|
-define(PUBSUB, ?MODULE).
|
|
|
|
|
|
--spec(start_link(atom(), pos_integer(), [tuple()]) -> {ok, pid()} | ignore | {error, any()}).
|
|
|
+-define(is_local(Options), lists:member(local, Options)).
|
|
|
+
|
|
|
+%%--------------------------------------------------------------------
|
|
|
+%% Start PubSub
|
|
|
+%%--------------------------------------------------------------------
|
|
|
+
|
|
|
+%% @doc Start one Pubsub
|
|
|
+-spec(start_link(atom(), pos_integer(), list()) -> {ok, pid()} | ignore | {error, any()}).
|
|
|
start_link(Pool, Id, Env) ->
|
|
|
gen_server2:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id, Env], []).
|
|
|
|
|
|
--spec(subscribe(binary(), emqttd:subscriber()) -> ok).
|
|
|
-subscribe(Topic, Subscriber) ->
|
|
|
- call(pick(Topic), {subscribe, Topic, Subscriber}).
|
|
|
+%%--------------------------------------------------------------------
|
|
|
+%% PubSub API
|
|
|
+%%--------------------------------------------------------------------
|
|
|
+
|
|
|
+%% @doc Subscribe a Topic
|
|
|
+-spec(subscribe(binary(), emqttd:subscriber(), [emqttd:suboption()]) -> ok).
|
|
|
+subscribe(Topic, Subscriber, Options) ->
|
|
|
+ call(pick(Topic), {subscribe, Topic, Subscriber, Options}).
|
|
|
|
|
|
--spec(async_subscribe(binary(), emqttd:subscriber()) -> ok).
|
|
|
-async_subscribe(Topic, Subscriber) ->
|
|
|
- cast(pick(Topic), {subscribe, Topic, Subscriber}).
|
|
|
+-spec(async_subscribe(binary(), emqttd:subscriber(), [emqttd:suboption()]) -> ok).
|
|
|
+async_subscribe(Topic, Subscriber, Options) ->
|
|
|
+ cast(pick(Topic), {subscribe, Topic, Subscriber, Options}).
|
|
|
|
|
|
+%% @doc Publish MQTT Message to Topic
|
|
|
-spec(publish(binary(), any()) -> {ok, mqtt_delivery()} | ignore).
|
|
|
publish(Topic, Msg) ->
|
|
|
- route(emqttd_router:match(Topic), delivery(Msg)).
|
|
|
+ route(lists:append(emqttd_router:match(Topic),
|
|
|
+ emqttd_router:match_local(Topic)), delivery(Msg)).
|
|
|
|
|
|
-route([], _Delivery) ->
|
|
|
- ignore;
|
|
|
+route([], #mqtt_delivery{message = #mqtt_message{topic = Topic}}) ->
|
|
|
+ dropped(Topic), ignore;
|
|
|
|
|
|
%% Dispatch on the local node
|
|
|
route([#mqtt_route{topic = To, node = Node}],
|
|
|
@@ -83,7 +100,7 @@ dispatch(Topic, Delivery = #mqtt_delivery{message = Msg, flows = Flows}) ->
|
|
|
dropped(Topic), {ok, Delivery};
|
|
|
[Sub] -> %% optimize?
|
|
|
dispatch(Sub, Topic, Msg),
|
|
|
- {ok, Delivery#mqtt_delivery{flows = [{dispatch, Topic, 1} | Flows]}};
|
|
|
+ {ok, Delivery#mqtt_delivery{flows = [{dispatch, Topic, 1}|Flows]}};
|
|
|
Subscribers ->
|
|
|
Flows1 = [{dispatch, Topic, length(Subscribers)} | Flows],
|
|
|
lists:foreach(fun(Sub) -> dispatch(Sub, Topic, Msg) end, Subscribers),
|
|
|
@@ -93,10 +110,27 @@ dispatch(Topic, Delivery = #mqtt_delivery{message = Msg, flows = Flows}) ->
|
|
|
dispatch(Pid, Topic, Msg) when is_pid(Pid) ->
|
|
|
Pid ! {dispatch, Topic, Msg};
|
|
|
dispatch(SubId, Topic, Msg) when is_binary(SubId) ->
|
|
|
- emqttd_sm:dispatch(SubId, Topic, Msg).
|
|
|
+ emqttd_sm:dispatch(SubId, Topic, Msg);
|
|
|
+dispatch({_Share, [Sub]}, Topic, Msg) ->
|
|
|
+ dispatch(Sub, Topic, Msg);
|
|
|
+dispatch({_Share, []}, _Topic, _Msg) ->
|
|
|
+ ok;
|
|
|
+dispatch({_Share, Subs}, Topic, Msg) ->
|
|
|
+ dispatch(lists:nth(rand:uniform(length(Subs)), Subs), Topic, Msg).
|
|
|
|
|
|
subscribers(Topic) ->
|
|
|
- try ets:lookup_element(mqtt_subscriber, Topic, 2) catch error:badarg -> [] end.
|
|
|
+ group_by_share(try ets:lookup_element(mqtt_subscriber, Topic, 2) catch error:badarg -> [] end).
|
|
|
+
|
|
|
+group_by_share([]) -> [];
|
|
|
+
|
|
|
+group_by_share(Subscribers) ->
|
|
|
+ {Subs1, Shares1} =
|
|
|
+ lists:foldl(fun({Share, Sub}, {Subs, Shares}) ->
|
|
|
+ {Subs, dict:append(Share, Sub, Shares)};
|
|
|
+ (Sub, {Subs, Shares}) ->
|
|
|
+ {[Sub|Subs], Shares}
|
|
|
+ end, {[], dict:new()}, Subscribers),
|
|
|
+ lists:append(Subs1, dict:to_list(Shares1)).
|
|
|
|
|
|
%% @private
|
|
|
%% @doc Ingore $SYS Messages.
|
|
|
@@ -105,45 +139,50 @@ dropped(<<"$SYS/", _/binary>>) ->
|
|
|
dropped(_Topic) ->
|
|
|
emqttd_metrics:inc('messages/dropped').
|
|
|
|
|
|
--spec(unsubscribe(binary(), emqttd:subscriber()) -> ok).
|
|
|
-unsubscribe(Topic, Subscriber) ->
|
|
|
- call(pick(Topic), {unsubscribe, Topic, Subscriber}).
|
|
|
+%% @doc Unsubscribe
|
|
|
+-spec(unsubscribe(binary(), emqttd:subscriber(), [emqttd:suboption()]) -> ok).
|
|
|
+unsubscribe(Topic, Subscriber, Options) ->
|
|
|
+ call(pick(Topic), {unsubscribe, Topic, Subscriber, Options}).
|
|
|
|
|
|
--spec(async_unsubscribe(binary(), emqttd:subscriber()) -> ok).
|
|
|
-async_unsubscribe(Topic, Subscriber) ->
|
|
|
- cast(pick(Topic), {unsubscribe, Topic, Subscriber}).
|
|
|
+-spec(async_unsubscribe(binary(), emqttd:subscriber(), [emqttd:suboption()]) -> ok).
|
|
|
+async_unsubscribe(Topic, Subscriber, Options) ->
|
|
|
+ cast(pick(Topic), {unsubscribe, Topic, Subscriber, Options}).
|
|
|
|
|
|
-call(Server, Req) ->
|
|
|
- gen_server2:call(Server, Req, infinity).
|
|
|
+call(PubSub, Req) when is_pid(PubSub) ->
|
|
|
+ gen_server2:call(PubSub, Req, infinity).
|
|
|
|
|
|
-cast(Server, Msg) ->
|
|
|
- gen_server2:cast(Server, Msg).
|
|
|
+cast(PubSub, Msg) when is_pid(PubSub) ->
|
|
|
+ gen_server2:cast(PubSub, Msg).
|
|
|
|
|
|
-pick(Topic) ->
|
|
|
- gproc_pool:pick_worker(pubsub, Topic).
|
|
|
+pick(Subscriber) ->
|
|
|
+ gproc_pool:pick_worker(pubsub, Subscriber).
|
|
|
+
|
|
|
+%%--------------------------------------------------------------------
|
|
|
+%% gen_server Callbacks
|
|
|
+%%--------------------------------------------------------------------
|
|
|
|
|
|
init([Pool, Id, Env]) ->
|
|
|
?GPROC_POOL(join, Pool, Id),
|
|
|
{ok, #state{pool = Pool, id = Id, env = Env}}.
|
|
|
|
|
|
-handle_call({subscribe, Topic, Subscriber}, _From, State) ->
|
|
|
- add_subscriber_(Topic, Subscriber),
|
|
|
+handle_call({subscribe, Topic, Subscriber, Options}, _From, State) ->
|
|
|
+ add_subscriber(Topic, Subscriber, Options),
|
|
|
{reply, ok, setstats(State)};
|
|
|
|
|
|
-handle_call({unsubscribe, Topic, Subscriber}, _From, State) ->
|
|
|
- del_subscriber_(Topic, Subscriber),
|
|
|
- {reply, ok, setstats(State)};
|
|
|
+handle_call({unsubscribe, Topic, Subscriber, Options}, _From, State) ->
|
|
|
+ del_subscriber(Topic, Subscriber, Options),
|
|
|
+ {reply, ok, setstats(State), hibernate};
|
|
|
|
|
|
handle_call(Req, _From, State) ->
|
|
|
?UNEXPECTED_REQ(Req, State).
|
|
|
|
|
|
-handle_cast({subscribe, Topic, Subscriber}, State) ->
|
|
|
- add_subscriber_(Topic, Subscriber),
|
|
|
+handle_cast({subscribe, Topic, Subscriber, Options}, State) ->
|
|
|
+ add_subscriber(Topic, Subscriber, Options),
|
|
|
{noreply, setstats(State)};
|
|
|
|
|
|
-handle_cast({unsubscribe, Topic, Subscriber}, State) ->
|
|
|
- del_subscriber_(Topic, Subscriber),
|
|
|
- {noreply, setstats(State)};
|
|
|
+handle_cast({unsubscribe, Topic, Subscriber, Options}, State) ->
|
|
|
+ del_subscriber(Topic, Subscriber, Options),
|
|
|
+ {noreply, setstats(State), hibernate};
|
|
|
|
|
|
handle_cast(Msg, State) ->
|
|
|
?UNEXPECTED_MSG(Msg, State).
|
|
|
@@ -161,17 +200,42 @@ code_change(_OldVsn, State, _Extra) ->
|
|
|
%% Internel Functions
|
|
|
%%--------------------------------------------------------------------
|
|
|
|
|
|
-add_subscriber_(Topic, Subscriber) ->
|
|
|
- (not ets:member(mqtt_subscriber, Topic))
|
|
|
- andalso emqttd_router:add_route(Topic),
|
|
|
- ets:insert(mqtt_subscriber, {Topic, Subscriber}).
|
|
|
+add_subscriber(Topic, Subscriber, Options) ->
|
|
|
+ Share = proplists:get_value(share, Options),
|
|
|
+ case ?is_local(Options) of
|
|
|
+ false -> add_subscriber_(Share, Topic, Subscriber);
|
|
|
+ true -> add_local_subscriber_(Share, Topic, Subscriber)
|
|
|
+ end.
|
|
|
+
|
|
|
+add_subscriber_(Share, Topic, Subscriber) ->
|
|
|
+ (not ets:member(mqtt_subscriber, Topic)) andalso emqttd_router:add_route(Topic),
|
|
|
+ ets:insert(mqtt_subscriber, {Topic, shared(Share, Subscriber)}).
|
|
|
+
|
|
|
+add_local_subscriber_(Share, Topic, Subscriber) ->
|
|
|
+ (not ets:member(mqtt_subscriber, {local, Topic})) andalso emqttd_router:add_local_route(Topic),
|
|
|
+ ets:insert(mqtt_subscriber, {{local, Topic}, shared(Share, Subscriber)}).
|
|
|
+
|
|
|
+del_subscriber(Topic, Subscriber, Options) ->
|
|
|
+ Share = proplists:get_value(share, Options),
|
|
|
+ case ?is_local(Options) of
|
|
|
+ false -> del_subscriber_(Share, Topic, Subscriber);
|
|
|
+ true -> del_local_subscriber_(Share, Topic, Subscriber)
|
|
|
+ end.
|
|
|
+
|
|
|
+del_subscriber_(Share, Topic, Subscriber) ->
|
|
|
+ ets:delete_object(mqtt_subscriber, {Topic, shared(Share, Subscriber)}),
|
|
|
+ (not ets:member(mqtt_subscriber, Topic)) andalso emqttd_router:del_route(Topic).
|
|
|
+
|
|
|
+del_local_subscriber_(Share, Topic, Subscriber) ->
|
|
|
+ ets:delete_object(mqtt_subscriber, {{local, Topic}, shared(Share, Subscriber)}),
|
|
|
+ (not ets:member(subscriber, {local, Topic})) andalso emqttd_router:del_local_route(Topic).
|
|
|
|
|
|
-del_subscriber_(Topic, Subscriber) ->
|
|
|
- ets:delete_object(mqtt_subscriber, {Topic, Subscriber}),
|
|
|
- (not ets:member(mqtt_subscriber, Topic))
|
|
|
- andalso emqttd_router:del_route(Topic).
|
|
|
+shared(undefined, Subscriber) ->
|
|
|
+ Subscriber;
|
|
|
+shared(Share, Subscriber) ->
|
|
|
+ {Share, Subscriber}.
|
|
|
|
|
|
setstats(State) ->
|
|
|
- emqttd_stats:setstats('subscribers/count', 'subscribers/max',
|
|
|
- ets:info(mqtt_subscriber, size)), State.
|
|
|
+ emqttd_stats:setstats('subscribers/count', 'subscribers/max', ets:info(mqtt_subscriber, size)),
|
|
|
+ State.
|
|
|
|