This commit is contained in:
Feng Lee 2015-03-22 21:13:40 +08:00
parent 3b84e5c982
commit a72fccf28d
1 changed files with 55 additions and 73 deletions

View File

@ -48,8 +48,8 @@
-export([topics/0,
create/1,
subscribe/2,
unsubscribe/2,
subscribe/1,
unsubscribe/1,
publish/1,
publish/2,
%local node
@ -108,8 +108,8 @@ topics() ->
%% @end
%%------------------------------------------------------------------------------
-spec create(binary()) -> {atomic, Reason :: any()} | {aborted, Reason :: any()}.
create(Topic) ->
gen_server:call(?SERVER, {create, Topic}).
create(Topic) when is_binary(Topic) ->
mnesia:transaction(fun trie_add/1, [Topic]).
%%------------------------------------------------------------------------------
%% @doc
@ -117,16 +117,33 @@ create(Topic) ->
%%
%% @end
%%------------------------------------------------------------------------------
-spec subscribe({binary(), mqtt_qos()} | list(), pid()) -> {ok, list(mqtt_qos())}.
subscribe({Topic, Qos}, SubPid) when is_binary(Topic) and is_pid(SubPid) ->
subscribe([{Topic, Qos}], SubPid);
-spec subscribe({binary(), mqtt_qos()} | list()) -> {ok, list(mqtt_qos())}.
subscribe({Topic, Qos}) when is_binary(Topic) ->
case subscribe([{Topic, Qos}]) of
{ok, [GrantedQos]} -> {ok, GrantedQos};
{error, Error} -> {error, Error}
end;
subscribe(Topics = [{_Topic, _Qos}|_]) ->
subscribe(Topics, self(), []).
%% TODO:
%% call will not work when there are 2000K+ clients, 100+ sub requests/sec...
%% will optimize in 0.9.x...
%%
subscribe(Topics, SubPid) when is_list(Topics) and is_pid(SubPid) ->
gen_server:call(?SERVER, {subscribe, Topics, SubPid}, infinity).
subscribe([], _SubPid, Acc) ->
{ok, lists:reverse(Acc)};
subscribe([{Topic, Qos}|Topics], SubPid, Acc) ->
Subscriber = #topic_subscriber{topic=Topic, qos = Qos, subpid=SubPid},
F = fun() ->
case trie_add(Topic) of
ok ->
mnesia:write(Subscriber);
{atomic, already_exist} ->
mnesia:write(Subscriber);
{aborted, Reason} ->
{aborted, Reason}
end
end,
case mnesia:transaction(F) of
ok -> subscribe(Topics, SubPid, [Qos|Acc]);
{aborted, Reason} -> {error, {aborted, Reason}}
end.
%%------------------------------------------------------------------------------
%% @doc
@ -134,12 +151,16 @@ subscribe(Topics, SubPid) when is_list(Topics) and is_pid(SubPid) ->
%%
%% @end
%%------------------------------------------------------------------------------
-spec unsubscribe(binary() | list(binary()), pid()) -> ok.
unsubscribe(Topic, SubPid) when is_binary(Topic) and is_pid(SubPid) ->
unsubscribe([Topic], SubPid);
-spec unsubscribe(binary() | list(binary())) -> ok.
unsubscribe(Topic) when is_binary(Topic) ->
%% call mnesia directly
unsubscribe([Topic]);
unsubscribe(Topics = [Topics|_]) when is_list(Topics) and is_binary(Topic) ->
unsubscribe(Topics, self()).
unsubscribe(Topics, SubPid) ->
unsubscribe(Topics, SubPid) when is_list(Topics) and is_pid(SubPid) ->
gen_server:cast(?SERVER, {unsubscribe, Topics, SubPid}).
%%------------------------------------------------------------------------------
%% @doc
@ -199,42 +220,38 @@ match(Topic) when is_binary(Topic) ->
%% ------------------------------------------------------------------
init([]) ->
mnesia:create_table(topic_trie, [
{ram_copies, [node()]},
{attributes, record_info(fields, topic_trie)}]),
%% trie and topic tables, will be copied by all nodes.
mnesia:create_table(topic_trie_node, [
{ram_copies, [node()]},
{attributes, record_info(fields, topic_trie_node)}]),
mnesia:add_table_copy(topic_trie_node, node(), ram_copies),
mnesia:create_table(topic_trie, [
{ram_copies, [node()]},
{attributes, record_info(fields, topic_trie)}]),
mnesia:add_table_copy(topic_trie, node(), ram_copies),
mnesia:create_table(topic, [
{type, bag},
{record_name, topic},
{ram_copies, [node()]},
{attributes, record_info(fields, topic)}]),
mnesia:add_table_copy(topic_trie, node(), ram_copies),
mnesia:add_table_copy(topic_trie_node, node(), ram_copies),
mnesia:add_table_copy(topic, node(), ram_copies),
ets:new(topic_subscriber, [bag, named_table, {keypos, 2}]),
%% local table, not shared with other table
mnesia:create_table(topic_subscriber, [
{type, bag},
{record_name, topic_subscriber},
{ram_copies, [node()]},
{attributes, record_info(fields, topic_subscriber)},
{index, [subpid]},
{local_content, true}]),
{ok, #state{}}.
handle_call(getstats, _From, State = #state{max_subs = Max}) ->
Stats = [{'topics/count', mnesia:table_info(topic, size)},
{'subscribers/count', ets:info(topic_subscriber, size)},
{'subscribers/count', mnesia:info(topic_subscriber, size)},
{'subscribers/max', Max}],
{reply, Stats, State};
handle_call({create, Topic}, _From, State) ->
Result = mnesia:transaction(fun trie_add/1, [Topic]),
{reply, Result, setstats(State)};
handle_call({subscribe, Topics, SubPid}, _From, State) ->
Result = [subscribe_topic({Topic, Qos}, SubPid) || {Topic, Qos} <- Topics],
Reply =
case [Err || Err = {error, _} <- Result] of
[] -> {ok, [Qos || {ok, Qos} <- Result]};
Errors -> hd(Errors)
end,
{reply, Reply, setstats(State)};
handle_call(Req, _From, State) ->
{stop, {badreq, Req}, State}.
@ -273,41 +290,6 @@ code_change(_OldVsn, State, _Extra) ->
%%%=============================================================================
%%% Internal functions
%%%=============================================================================
subscribe_topic({Topic, Qos}, SubPid) ->
case mnesia:transaction(fun trie_add/1, [Topic]) of
{atomic, _} ->
case get({subscriber, SubPid}) of
undefined ->
%%TODO: refactor later...
MonRef = erlang:monitor(process, SubPid),
put({subcriber, SubPid}, MonRef),
put({submon, MonRef}, SubPid);
_ ->
already_monitored
end,
%% remove duplicated subscribers
try_remove_subscriber({Topic, Qos}, SubPid),
ets:insert(topic_subscriber, #topic_subscriber{topic=Topic, qos = Qos, subpid=SubPid}),
%TODO: GrantedQos??
{ok, Qos};
{aborted, Reason} ->
{error, Reason}
end.
try_remove_subscriber({Topic, Qos}, SubPid) ->
case ets:lookup(topic_subscriber, Topic) of
[] ->
not_found;
Subs ->
DupSubs = [Sub || Sub = #topic_subscriber{qos = OldQos, subpid = OldPid}
<- Subs, Qos =/= OldQos, OldPid =:= SubPid],
case DupSubs of
[] -> ok;
[DupSub] ->
lager:warning("PubSub: remove duplicated subscriber ~p", [DupSub]),
ets:delete(topic_subscriber, DupSub)
end
end.
try_remove_topic(Name) when is_binary(Name) ->
case ets:member(topic_subscriber, Name) of