upgrade session
This commit is contained in:
parent
985fbde26f
commit
a0f90b3ac6
|
@ -57,7 +57,7 @@
|
||||||
|
|
||||||
-record(mqtt_message, {
|
-record(mqtt_message, {
|
||||||
topic :: binary(), %% topic published to
|
topic :: binary(), %% topic published to
|
||||||
from :: mqtt_clientid() | atom(), %% from clientid
|
from :: binary() | atom(), %% from clientid
|
||||||
qos = ?QOS_0 :: mqtt_qos(),
|
qos = ?QOS_0 :: mqtt_qos(),
|
||||||
retain = false :: boolean(),
|
retain = false :: boolean(),
|
||||||
dup = false :: boolean(),
|
dup = false :: boolean(),
|
||||||
|
|
|
@ -162,7 +162,7 @@
|
||||||
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}, variable = Var}).
|
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}, variable = Var}).
|
||||||
|
|
||||||
-define(CONNACK_PACKET(ReturnCode),
|
-define(CONNACK_PACKET(ReturnCode),
|
||||||
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK},
|
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK},
|
||||||
variable = #mqtt_packet_connack{return_code = ReturnCode}}).
|
variable = #mqtt_packet_connack{return_code = ReturnCode}}).
|
||||||
|
|
||||||
-define(PUBLISH_PACKET(Qos, Topic, PacketId, Payload),
|
-define(PUBLISH_PACKET(Qos, Topic, PacketId, Payload),
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
-include("emqtt_packet.hrl").
|
-include("emqtt_packet.hrl").
|
||||||
|
|
||||||
-export([from_packet/1, to_packet/1]).
|
-export([from_packet/1, from_packet/2, to_packet/1]).
|
||||||
|
|
||||||
-export([set_flag/1, set_flag/2, unset_flag/1, unset_flag/2]).
|
-export([set_flag/1, set_flag/2, unset_flag/1, unset_flag/2]).
|
||||||
|
|
||||||
|
@ -70,6 +70,9 @@ from_packet(#mqtt_packet_connect{will_retain = Retain,
|
||||||
dup = false,
|
dup = false,
|
||||||
payload = Msg}.
|
payload = Msg}.
|
||||||
|
|
||||||
|
from_packet(ClientId, Packet) ->
|
||||||
|
Msg = from_packet(Packet), Msg#mqtt_message{from = ClientId}.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Message to packet
|
%% @doc Message to packet
|
||||||
%% @end
|
%% @end
|
||||||
|
|
|
@ -106,11 +106,11 @@ handle_call(_Request, _From, State) ->
|
||||||
handle_cast(_Msg, State) ->
|
handle_cast(_Msg, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({dispatch, {_From, Msg}}, State = #state{node = Node, status = down}) ->
|
handle_info({dispatch, Msg}, State = #state{node = Node, status = down}) ->
|
||||||
lager:warning("Bridge Dropped Msg for ~p Down:~n~p", [Node, Msg]),
|
lager:warning("Bridge Dropped Msg for ~p Down:~n~p", [Node, Msg]),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_info({dispatch, {_From, Msg}}, State = #state{node = Node, status = up}) ->
|
handle_info({dispatch, Msg}, State = #state{node = Node, status = up}) ->
|
||||||
rpc:cast(Node, emqttd_pubsub, publish, [transform(Msg, State)]),
|
rpc:cast(Node, emqttd_pubsub, publish, [transform(Msg, State)]),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
|
|
|
@ -106,16 +106,8 @@ handle_info({stop, duplicate_id, _NewPid}, State=#state{proto_state = ProtoState
|
||||||
[emqttd_protocol:clientid(ProtoState), ConnName]),
|
[emqttd_protocol:clientid(ProtoState), ConnName]),
|
||||||
stop({shutdown, duplicate_id}, State);
|
stop({shutdown, duplicate_id}, State);
|
||||||
|
|
||||||
%%TODO: ok??
|
handle_info({deliver, Message}, #state{proto_state = ProtoState} = State) ->
|
||||||
handle_info({dispatch, {From, Messages}}, #state{proto_state = ProtoState} = State) when is_list(Messages) ->
|
{ok, ProtoState1} = emqttd_protocol:send(Message, ProtoState),
|
||||||
ProtoState1 =
|
|
||||||
lists:foldl(fun(Message, PState) ->
|
|
||||||
{ok, PState1} = emqttd_protocol:send({From, Message}, PState), PState1
|
|
||||||
end, ProtoState, Messages),
|
|
||||||
{noreply, State#state{proto_state = ProtoState1}};
|
|
||||||
|
|
||||||
handle_info({dispatch, {From, Message}}, #state{proto_state = ProtoState} = State) ->
|
|
||||||
{ok, ProtoState1} = emqttd_protocol:send({From, Message}, ProtoState),
|
|
||||||
{noreply, State#state{proto_state = ProtoState1}};
|
{noreply, State#state{proto_state = ProtoState1}};
|
||||||
|
|
||||||
handle_info({redeliver, {?PUBREL, PacketId}}, #state{proto_state = ProtoState} = State) ->
|
handle_info({redeliver, {?PUBREL, PacketId}}, #state{proto_state = ProtoState} = State) ->
|
||||||
|
|
|
@ -25,42 +25,47 @@
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqttd_mqwin).
|
-module(emqttd_inflight).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
-export([new/2, len/1, in/2, ack/2]).
|
-include_lib("emqtt/include/emqtt.hrl").
|
||||||
|
|
||||||
-define(WIN_SIZE, 100).
|
-export([new/2, is_full/1, len/1, in/2, ack/2]).
|
||||||
|
|
||||||
-record(mqwin, {name,
|
-define(MAX_SIZE, 100).
|
||||||
w = [], %% window list
|
|
||||||
len = 0, %% current window len
|
|
||||||
size = ?WIN_SIZE}).
|
|
||||||
|
|
||||||
-type mqwin() :: #mqwin{}.
|
-record(inflight, {name, q = [], len = 0, size = ?MAX_SIZE}).
|
||||||
|
|
||||||
-export_type([mqwin/0]).
|
-type inflight() :: #inflight{}.
|
||||||
|
|
||||||
new(Name, Opts) ->
|
-export_type([inflight/0]).
|
||||||
WinSize = emqttd_opts:g(inflight_window, Opts, ?WIN_SIZE),
|
|
||||||
#mqwin{name = Name, size = WinSize}.
|
|
||||||
|
|
||||||
len(#mqwin{len = Len}) ->
|
new(Name, Max) ->
|
||||||
|
#inflight{name = Name, size = Max}.
|
||||||
|
|
||||||
|
is_full(#inflight{size = 0}) ->
|
||||||
|
false;
|
||||||
|
is_full(#inflight{len = Len, size = Size}) when Len < Size ->
|
||||||
|
false;
|
||||||
|
is_full(_Inflight) ->
|
||||||
|
true.
|
||||||
|
|
||||||
|
len(#inflight{len = Len}) ->
|
||||||
Len.
|
Len.
|
||||||
|
|
||||||
in(_Msg, #mqwin{len = Len, size = Size})
|
in(_Msg, #inflight{len = Len, size = Size})
|
||||||
when Len =:= Size -> {error, full};
|
when Len =:= Size -> {error, full};
|
||||||
|
|
||||||
in(Msg, Win = #mqwin{w = W, len = Len}) ->
|
in(Msg = #mqtt_message{msgid = MsgId}, Inflight = #inflight{q = Q, len = Len}) ->
|
||||||
{ok, Win#mqwin{w = [Msg|W], len = Len +1}}.
|
{ok, Inflight#inflight{q = [{MsgId, Msg}|Q], len = Len +1}}.
|
||||||
|
|
||||||
ack(MsgId, QWin = #mqwin{w = W, len = Len}) ->
|
ack(MsgId, Inflight = #inflight{q = Q, len = Len}) ->
|
||||||
case lists:keyfind(MsgId, 2, W) of
|
case lists:keyfind(MsgId, 1, Q) of
|
||||||
false ->
|
false ->
|
||||||
lager:error("qwin(~s) cannot find msgid: ~p", [MsgId]), QWin;
|
lager:error("Inflight(~s) cannot find msgid: ~p", [MsgId]),
|
||||||
|
Inflight;
|
||||||
_Msg ->
|
_Msg ->
|
||||||
QWin#mqwin{w = lists:keydelete(MsgId, 2, W), len = Len - 1}
|
Inflight#inflight{q = lists:keydelete(MsgId, 1, Q), len = Len - 1}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
|
|
||||||
-export([new/2, name/1,
|
-export([new/2, name/1,
|
||||||
is_empty/1, is_full/1,
|
is_empty/1, is_full/1,
|
||||||
len/1, in/2, out/2]).
|
len/1, in/2, out/1]).
|
||||||
|
|
||||||
-define(LOW_WM, 0.2).
|
-define(LOW_WM, 0.2).
|
||||||
|
|
||||||
|
@ -112,22 +112,30 @@ len(#mqueue{len = Len}) -> Len.
|
||||||
%% @doc Queue one message.
|
%% @doc Queue one message.
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec in(mqtt_message(), mqueue()) -> mqueue().
|
|
||||||
|
-spec in({new | old, mqtt_message()}, mqueue()) -> mqueue().
|
||||||
|
|
||||||
%% drop qos0
|
%% drop qos0
|
||||||
in(#mqtt_message{qos = ?QOS_0}, MQ = #mqueue{qos0 = false}) ->
|
in({_, #mqtt_message{qos = ?QOS_0}}, MQ = #mqueue{qos0 = false}) ->
|
||||||
MQ;
|
MQ;
|
||||||
|
|
||||||
%% simply drop the oldest one if queue is full, improve later
|
%% simply drop the oldest one if queue is full, improve later
|
||||||
in(Msg, MQ = #mqueue{name = Name, len = Len, max_len = MaxLen})
|
in({new, Msg}, MQ = #mqueue{name = Name, q = Q, len = Len, max_len = MaxLen})
|
||||||
when Len =:= MaxLen ->
|
when Len =:= MaxLen ->
|
||||||
{{value, OldMsg}, Q2} = queue:out(Q),
|
{{value, OldMsg}, Q2} = queue:out(Q),
|
||||||
lager:error("queue(~s) drop message: ~p", [Name, OldMsg]),
|
lager:error("queue(~s) drop message: ~p", [Name, OldMsg]),
|
||||||
MQ#mqueue{q = queue:in(Msg, Q2)};
|
MQ#mqueue{q = queue:in(Msg, Q2)};
|
||||||
|
|
||||||
in(Msg, MQ = #mqueue{q = Q, len = Len}) ->
|
in({old, Msg}, MQ = #mqueue{name = Name, len = Len, max_len = MaxLen})
|
||||||
|
when Len =:= MaxLen ->
|
||||||
|
lager:error("queue(~s) drop message: ~p", [Name, Msg]), MQ;
|
||||||
|
|
||||||
|
in({new, Msg}, MQ = #mqueue{q = Q, len = Len}) ->
|
||||||
maybe_set_alarm(MQ#mqueue{q = queue:in(Msg, Q), len = Len + 1});
|
maybe_set_alarm(MQ#mqueue{q = queue:in(Msg, Q), len = Len + 1});
|
||||||
|
|
||||||
|
in({old, Msg}, MQ = #mqueue{q = Q, len = Len}) ->
|
||||||
|
MQ#mqueue{q = queue:in_r(Msg, Q), len = Len + 1}.
|
||||||
|
|
||||||
out(MQ = #mqueue{len = 0}) ->
|
out(MQ = #mqueue{len = 0}) ->
|
||||||
{empty, MQ};
|
{empty, MQ};
|
||||||
|
|
||||||
|
@ -143,7 +151,7 @@ maybe_set_alarm(MQ = #mqueue{name = Name, len = Len, high_wm = HighWM, alarm = f
|
||||||
maybe_set_alarm(MQ) ->
|
maybe_set_alarm(MQ) ->
|
||||||
MQ.
|
MQ.
|
||||||
|
|
||||||
maybe_clear_alarm(MQ = #mqueue{name = Name, len = Len, low_watermark = LowWM, alarm = true})
|
maybe_clear_alarm(MQ = #mqueue{name = Name, len = Len, low_wm = LowWM, alarm = true})
|
||||||
when Len =< LowWM ->
|
when Len =< LowWM ->
|
||||||
emqttd_alarm:clear_alarm({queue_high_watermark, Name}),
|
emqttd_alarm:clear_alarm({queue_high_watermark, Name}),
|
||||||
MQ#mqueue{alarm = false};
|
MQ#mqueue{alarm = false};
|
||||||
|
|
|
@ -123,7 +123,7 @@ redeliver(Topic, CPid) when is_binary(Topic) andalso is_pid(CPid) ->
|
||||||
dispatch(_CPid, []) ->
|
dispatch(_CPid, []) ->
|
||||||
ignore;
|
ignore;
|
||||||
dispatch(CPid, Msgs) when is_list(Msgs) ->
|
dispatch(CPid, Msgs) when is_list(Msgs) ->
|
||||||
CPid ! {dispatch, {self(), [Msg || Msg <- Msgs]}};
|
CPid ! {dispatch, [Msg || Msg <- Msgs]};
|
||||||
dispatch(CPid, Msg) when is_record(Msg, mqtt_message) ->
|
dispatch(CPid, Msg) when is_record(Msg, mqtt_message) ->
|
||||||
CPid ! {dispatch, {self(), Msg}}.
|
CPid ! {dispatch, Msg}.
|
||||||
|
|
||||||
|
|
|
@ -51,8 +51,7 @@
|
||||||
username,
|
username,
|
||||||
clientid,
|
clientid,
|
||||||
clean_sess,
|
clean_sess,
|
||||||
sessmod,
|
session,
|
||||||
session, %% session state or session pid
|
|
||||||
will_msg,
|
will_msg,
|
||||||
max_clientid_len = ?MAX_CLIENTID_LEN,
|
max_clientid_len = ?MAX_CLIENTID_LEN,
|
||||||
client_pid
|
client_pid
|
||||||
|
@ -152,13 +151,13 @@ handle(Packet = ?CONNECT_PACKET(Var), State0 = #proto_state{peername = Peername}
|
||||||
emqttd_cm:register(client(State2)),
|
emqttd_cm:register(client(State2)),
|
||||||
|
|
||||||
%%Starting session
|
%%Starting session
|
||||||
{ok, SessMod, Session} = emqttd_sm:start_session(CleanSess, clientid(State2)),
|
{ok, Session} = emqttd_sm:start_session(CleanSess, clientid(State2)),
|
||||||
|
|
||||||
%% Start keepalive
|
%% Start keepalive
|
||||||
start_keepalive(KeepAlive),
|
start_keepalive(KeepAlive),
|
||||||
|
|
||||||
%% ACCEPT
|
%% ACCEPT
|
||||||
{?CONNACK_ACCEPT, State2#proto_state{sessmod = SessMod, session = Session, will_msg = willmsg(Var)}};
|
{?CONNACK_ACCEPT, State2#proto_state{session = Session, will_msg = willmsg(Var)}};
|
||||||
{error, Reason}->
|
{error, Reason}->
|
||||||
lager:error("~s@~s: username '~s', login failed - ~s",
|
lager:error("~s@~s: username '~s', login failed - ~s",
|
||||||
[ClientId, emqttd_net:format(Peername), Username, Reason]),
|
[ClientId, emqttd_net:format(Peername), Username, Reason]),
|
||||||
|
@ -177,7 +176,7 @@ handle(Packet = ?PUBLISH_PACKET(?QOS_0, Topic, _PacketId, _Payload),
|
||||||
State = #proto_state{clientid = ClientId, session = Session}) ->
|
State = #proto_state{clientid = ClientId, session = Session}) ->
|
||||||
case check_acl(publish, Topic, State) of
|
case check_acl(publish, Topic, State) of
|
||||||
allow ->
|
allow ->
|
||||||
do_publish(Session, ClientId, ?QOS_0, Packet);
|
do_publish(Session, ClientId, Packet);
|
||||||
deny ->
|
deny ->
|
||||||
lager:error("ACL Deny: ~s cannot publish to ~s", [ClientId, Topic])
|
lager:error("ACL Deny: ~s cannot publish to ~s", [ClientId, Topic])
|
||||||
end,
|
end,
|
||||||
|
@ -187,7 +186,7 @@ handle(Packet = ?PUBLISH_PACKET(?QOS_1, Topic, PacketId, _Payload),
|
||||||
State = #proto_state{clientid = ClientId, session = Session}) ->
|
State = #proto_state{clientid = ClientId, session = Session}) ->
|
||||||
case check_acl(publish, Topic, State) of
|
case check_acl(publish, Topic, State) of
|
||||||
allow ->
|
allow ->
|
||||||
do_publish(Session, ClientId, ?QOS_1, Packet),
|
do_publish(Session, ClientId, Packet),
|
||||||
send(?PUBACK_PACKET(?PUBACK, PacketId), State);
|
send(?PUBACK_PACKET(?PUBACK, PacketId), State);
|
||||||
deny ->
|
deny ->
|
||||||
lager:error("ACL Deny: ~s cannot publish to ~s", [ClientId, Topic]),
|
lager:error("ACL Deny: ~s cannot publish to ~s", [ClientId, Topic]),
|
||||||
|
@ -198,26 +197,28 @@ handle(Packet = ?PUBLISH_PACKET(?QOS_2, Topic, PacketId, _Payload),
|
||||||
State = #proto_state{clientid = ClientId, session = Session}) ->
|
State = #proto_state{clientid = ClientId, session = Session}) ->
|
||||||
case check_acl(publish, Topic, State) of
|
case check_acl(publish, Topic, State) of
|
||||||
allow ->
|
allow ->
|
||||||
NewSession = do_publish(Session, ClientId, ?QOS_2, Packet),
|
do_publish(Session, ClientId, Packet),
|
||||||
send(?PUBACK_PACKET(?PUBREC, PacketId), State#proto_state{session = NewSession});
|
send(?PUBACK_PACKET(?PUBREC, PacketId), State);
|
||||||
deny ->
|
deny ->
|
||||||
lager:error("ACL Deny: ~s cannot publish to ~s", [ClientId, Topic]),
|
lager:error("ACL Deny: ~s cannot publish to ~s", [ClientId, Topic]),
|
||||||
{ok, State}
|
{ok, State}
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle(?PUBACK_PACKET(Type, PacketId), State = #proto_state{session = Session})
|
handle(?PUBACK_PACKET(?PUBACK, PacketId), State = #proto_state{session = Session}) ->
|
||||||
when Type >= ?PUBACK andalso Type =< ?PUBCOMP ->
|
emqttd_session:puback(Session, PacketId),
|
||||||
NewSession = emqttd_session:puback(Session, {Type, PacketId}),
|
{ok, State};
|
||||||
NewState = State#proto_state{session = NewSession},
|
|
||||||
if
|
handle(?PUBACK_PACKET(?PUBREC, PacketId), State = #proto_state{session = Session}) ->
|
||||||
Type =:= ?PUBREC ->
|
emqttd_session:pubrec(Session, PacketId),
|
||||||
send(?PUBREL_PACKET(PacketId), NewState);
|
send(?PUBREL_PACKET(PacketId), State);
|
||||||
Type =:= ?PUBREL ->
|
|
||||||
send(?PUBACK_PACKET(?PUBCOMP, PacketId), NewState);
|
handle(?PUBACK_PACKET(?PUBREL, PacketId), State = #proto_state{session = Session}) ->
|
||||||
true ->
|
emqttd_session:pubrel(Session, PacketId),
|
||||||
ok
|
send(?PUBACK_PACKET(?PUBCOMP, PacketId), State);
|
||||||
end,
|
|
||||||
{ok, NewState};
|
handle(?PUBACK_PACKET(?PUBCOMP, PacketId), State = #proto_state{session = Session}) ->
|
||||||
|
emqttd_session:pubcomp(Session, PacketId),
|
||||||
|
{ok, State};
|
||||||
|
|
||||||
%% protect from empty topic list
|
%% protect from empty topic list
|
||||||
handle(?SUBSCRIBE_PACKET(PacketId, []), State) ->
|
handle(?SUBSCRIBE_PACKET(PacketId, []), State) ->
|
||||||
|
@ -233,13 +234,13 @@ handle(?SUBSCRIBE_PACKET(PacketId, TopicTable), State = #proto_state{clientid =
|
||||||
false ->
|
false ->
|
||||||
TopicTable1 = emqttd_broker:foldl_hooks(client_subscribe, [], TopicTable),
|
TopicTable1 = emqttd_broker:foldl_hooks(client_subscribe, [], TopicTable),
|
||||||
%%TODO: GrantedQos should be renamed.
|
%%TODO: GrantedQos should be renamed.
|
||||||
{ok, NewSession, GrantedQos} = emqttd_session:subscribe(Session, TopicTable1),
|
{ok, GrantedQos} = emqttd_session:subscribe(Session, TopicTable1),
|
||||||
send(?SUBACK_PACKET(PacketId, GrantedQos), State#proto_state{session = NewSession})
|
send(?SUBACK_PACKET(PacketId, GrantedQos), State)
|
||||||
end;
|
end;
|
||||||
|
|
||||||
handle({subscribe, Topic, Qos}, State = #proto_state{session = Session}) ->
|
handle({subscribe, TopicTable}, State = #proto_state{session = Session}) ->
|
||||||
{ok, NewSession, _GrantedQos} = emqttd_session:subscribe(Session, [{Topic, Qos}]),
|
{ok, _GrantedQos} = emqttd_session:subscribe(Session, TopicTable),
|
||||||
{ok, State#proto_state{session = NewSession}};
|
{ok, State};
|
||||||
|
|
||||||
%% protect from empty topic list
|
%% protect from empty topic list
|
||||||
handle(?UNSUBSCRIBE_PACKET(PacketId, []), State) ->
|
handle(?UNSUBSCRIBE_PACKET(PacketId, []), State) ->
|
||||||
|
@ -247,34 +248,24 @@ handle(?UNSUBSCRIBE_PACKET(PacketId, []), State) ->
|
||||||
|
|
||||||
handle(?UNSUBSCRIBE_PACKET(PacketId, Topics), State = #proto_state{session = Session}) ->
|
handle(?UNSUBSCRIBE_PACKET(PacketId, Topics), State = #proto_state{session = Session}) ->
|
||||||
Topics1 = emqttd_broker:foldl_hooks(client_unsubscribe, [], Topics),
|
Topics1 = emqttd_broker:foldl_hooks(client_unsubscribe, [], Topics),
|
||||||
{ok, NewSession} = emqttd_session:unsubscribe(Session, Topics1),
|
ok = emqttd_session:unsubscribe(Session, Topics1),
|
||||||
send(?UNSUBACK_PACKET(PacketId), State#proto_state{session = NewSession});
|
send(?UNSUBACK_PACKET(PacketId), State);
|
||||||
|
|
||||||
handle(?PACKET(?PINGREQ), State) ->
|
handle(?PACKET(?PINGREQ), State) ->
|
||||||
send(?PACKET(?PINGRESP), State);
|
send(?PACKET(?PINGRESP), State);
|
||||||
|
|
||||||
handle(?PACKET(?DISCONNECT), State) ->
|
handle(?PACKET(?DISCONNECT), State) ->
|
||||||
%%TODO: how to handle session?
|
|
||||||
% clean willmsg
|
% clean willmsg
|
||||||
{stop, normal, State#proto_state{will_msg = undefined}}.
|
{stop, normal, State#proto_state{will_msg = undefined}}.
|
||||||
|
|
||||||
do_publish(Session, ClientId, Qos, Packet) ->
|
do_publish(Session, ClientId, Packet) ->
|
||||||
Message = emqttd_broker:foldl_hooks(client_publish, [], emqtt_message:from_packet(Packet)),
|
Msg = emqtt_message:from_packet(ClientId, Packet),
|
||||||
emqttd_session:publish(Session, ClientId, {Qos, Message}).
|
Msg1 = emqttd_broker:foldl_hooks(client_publish, [], Msg),
|
||||||
|
emqttd_session:publish(Session, Msg1).
|
||||||
|
|
||||||
-spec send({pid() | tuple(), mqtt_message()} | mqtt_packet(), proto_state()) -> {ok, proto_state()}.
|
-spec send(mqtt_message() | mqtt_packet(), proto_state()) -> {ok, proto_state()}.
|
||||||
%% qos0 message
|
send(Msg, State) when is_record(Msg, mqtt_message) ->
|
||||||
send({_From, Message = #mqtt_message{qos = ?QOS_0}}, State) ->
|
send(emqtt_message:to_packet(Msg), State);
|
||||||
send(emqtt_message:to_packet(Message), State);
|
|
||||||
|
|
||||||
%% message from session
|
|
||||||
send({_From = SessPid, Message}, State = #proto_state{session = SessPid}) when is_pid(SessPid) ->
|
|
||||||
send(emqtt_message:to_packet(Message), State);
|
|
||||||
%% message(qos1, qos2) not from session
|
|
||||||
send({_From, Message = #mqtt_message{qos = Qos}}, State = #proto_state{session = Session})
|
|
||||||
when (Qos =:= ?QOS_1) orelse (Qos =:= ?QOS_2) ->
|
|
||||||
{Message1, NewSession} = emqttd_session:await_ack(Session, Message),
|
|
||||||
send(emqtt_message:to_packet(Message1), State#proto_state{session = NewSession});
|
|
||||||
|
|
||||||
send(Packet, State = #proto_state{sendfun = SendFun, peername = Peername}) when is_record(Packet, mqtt_packet) ->
|
send(Packet, State = #proto_state{sendfun = SendFun, peername = Peername}) when is_record(Packet, mqtt_packet) ->
|
||||||
trace(send, Packet, State),
|
trace(send, Packet, State),
|
||||||
|
@ -331,8 +322,8 @@ clientid(ClientId, _State) -> ClientId.
|
||||||
send_willmsg(_ClientId, undefined) ->
|
send_willmsg(_ClientId, undefined) ->
|
||||||
ignore;
|
ignore;
|
||||||
%%TODO:should call session...
|
%%TODO:should call session...
|
||||||
send_willmsg(ClientId, WillMsg) ->
|
send_willmsg(ClientId, WillMsg) ->
|
||||||
emqttd_pubsub:publish(ClientId, WillMsg).
|
emqttd_pubsub:publish(WillMsg#mqtt_message{from = ClientId}).
|
||||||
|
|
||||||
start_keepalive(0) -> ignore;
|
start_keepalive(0) -> ignore;
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,7 @@ publish(From, <<"$Q/", _/binary>> = Queue, #mqtt_message{qos = Qos} = Msg) ->
|
||||||
Qos > SubQos -> Msg#mqtt_message{qos = SubQos};
|
Qos > SubQos -> Msg#mqtt_message{qos = SubQos};
|
||||||
true -> Msg
|
true -> Msg
|
||||||
end,
|
end,
|
||||||
SubPid ! {dispatch, {self(), Msg1}}
|
SubPid ! {dispatch, Msg1}
|
||||||
end, mnesia:dirty_read(queue, Queue));
|
end, mnesia:dirty_read(queue, Queue));
|
||||||
|
|
||||||
publish(_From, Topic, Msg) when is_binary(Topic) ->
|
publish(_From, Topic, Msg) when is_binary(Topic) ->
|
||||||
|
@ -202,7 +202,7 @@ dispatch(Topic, #mqtt_message{qos = Qos} = Msg ) when is_binary(Topic) ->
|
||||||
Qos > SubQos -> Msg#mqtt_message{qos = SubQos};
|
Qos > SubQos -> Msg#mqtt_message{qos = SubQos};
|
||||||
true -> Msg
|
true -> Msg
|
||||||
end,
|
end,
|
||||||
SubPid ! {dispatch, {self(), Msg1}}
|
SubPid ! {dispatch, Msg1}
|
||||||
end, Subscribers),
|
end, Subscribers),
|
||||||
length(Subscribers).
|
length(Subscribers).
|
||||||
|
|
||||||
|
|
|
@ -53,31 +53,26 @@
|
||||||
|
|
||||||
-include_lib("emqtt/include/emqtt_packet.hrl").
|
-include_lib("emqtt/include/emqtt_packet.hrl").
|
||||||
|
|
||||||
%% Start gen_server
|
%% Session API
|
||||||
-export([start_link/2, resume/3, destroy/2]).
|
-export([start_link/3, resume/3, destroy/2]).
|
||||||
|
|
||||||
%% Init Session State
|
|
||||||
-export([new/1]).
|
|
||||||
|
|
||||||
%% PubSub APIs
|
%% PubSub APIs
|
||||||
-export([publish/3,
|
-export([publish/2,
|
||||||
puback/2,
|
puback/2, pubrec/2, pubrel/2, pubcomp/2,
|
||||||
subscribe/2,
|
subscribe/2, unsubscribe/2]).
|
||||||
unsubscribe/2,
|
|
||||||
await/2,
|
|
||||||
dispatch/2]).
|
|
||||||
|
|
||||||
%% gen_server Function Exports
|
%% gen_server Function Exports
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
-record(session, {
|
-record(session, {
|
||||||
%% ClientId: Identifier of Session
|
|
||||||
clientid :: binary(),
|
|
||||||
|
|
||||||
%% Clean Session Flag
|
%% Clean Session Flag
|
||||||
clean_sess = true,
|
clean_sess = true,
|
||||||
|
|
||||||
|
%% ClientId: Identifier of Session
|
||||||
|
clientid :: binary(),
|
||||||
|
|
||||||
%% Client Pid linked with session
|
%% Client Pid linked with session
|
||||||
client_pid :: pid(),
|
client_pid :: pid(),
|
||||||
|
|
||||||
|
@ -91,7 +86,7 @@
|
||||||
%% QoS 1 and QoS 2 messages which have been sent to the Client,
|
%% QoS 1 and QoS 2 messages which have been sent to the Client,
|
||||||
%% but have not been completely acknowledged.
|
%% but have not been completely acknowledged.
|
||||||
%% Client <- Broker
|
%% Client <- Broker
|
||||||
inflight_window :: emqttd_mqwin:mqwin(),
|
inflight_queue :: emqttd_inflight:inflight(),
|
||||||
|
|
||||||
%% All qos1, qos2 messages published to when client is disconnected.
|
%% All qos1, qos2 messages published to when client is disconnected.
|
||||||
%% QoS 1 and QoS 2 messages pending transmission to the Client.
|
%% QoS 1 and QoS 2 messages pending transmission to the Client.
|
||||||
|
@ -129,167 +124,121 @@
|
||||||
|
|
||||||
timestamp}).
|
timestamp}).
|
||||||
|
|
||||||
-type session() :: #session{}.
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Session API
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Start a session process.
|
%% @doc Start a session.
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
start_link(ClientId, ClientPid) ->
|
start_link(CleanSess, ClientId, ClientPid) ->
|
||||||
gen_server:start_link(?MODULE, [ClientId, ClientPid], []).
|
gen_server:start_link(?MODULE, [CleanSess, ClientId, ClientPid], []).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Resume a session.
|
%% @doc Resume a session.
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
resume(Session, _ClientId, _ClientPid) when is_record(Session, session) ->
|
resume(Session, ClientId, ClientPid) when is_pid(Session) ->
|
||||||
Session;
|
gen_server:cast(Session, {resume, ClientId, ClientPid}).
|
||||||
resume(SessPid, ClientId, ClientPid) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {resume, ClientId, ClientPid}), SessPid.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Destroy a session.
|
%% @doc Destroy a session.
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec destroy(SessPid :: pid(), ClientId :: binary()) -> ok.
|
-spec destroy(Session:: pid(), ClientId :: binary()) -> ok.
|
||||||
destroy(SessPid, ClientId) when is_pid(SessPid) ->
|
destroy(Session, ClientId) when is_pid(Session) ->
|
||||||
gen_server:cast(SessPid, {destroy, ClientId}), SessPid.
|
gen_server:call(Session, {destroy, ClientId}).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Init Session State.
|
%% @doc Publish message
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec new(binary()) -> session().
|
-spec publish(Session :: pid(), {mqtt_qos(), mqtt_message()}) -> ok.
|
||||||
new(ClientId) ->
|
publish(Session, Msg = #mqtt_message{qos = ?QOS_0}) when is_pid(Session) ->
|
||||||
|
%% publish qos0 directly
|
||||||
|
emqttd_pubsub:publish(Msg);
|
||||||
|
|
||||||
|
publish(Session, Msg = #mqtt_message{qos = ?QOS_1}) when is_pid(Session) ->
|
||||||
|
%% publish qos1 directly, and client will puback
|
||||||
|
emqttd_pubsub:publish(Msg);
|
||||||
|
|
||||||
|
publish(Session, Msg = #mqtt_message{qos = ?QOS_2}) when is_pid(Session) ->
|
||||||
|
%% publish qos2 by session
|
||||||
|
gen_server:cast(Session, {publish, Msg}).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% @doc PubAck message
|
||||||
|
%% @end
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-spec puback(Session :: pid(), MsgId :: mqtt_packet_id()) -> ok.
|
||||||
|
puback(Session, MsgId) when is_pid(Session) ->
|
||||||
|
gen_server:cast(Session, {puback, MsgId}).
|
||||||
|
|
||||||
|
-spec pubrec(Session :: pid(), MsgId :: mqtt_packet_id()) -> ok.
|
||||||
|
pubrec(Session, MsgId) when is_pid(Session) ->
|
||||||
|
gen_server:cast(Session, {pubrec, MsgId}).
|
||||||
|
|
||||||
|
-spec pubrel(Session :: pid(), MsgId :: mqtt_packet_id()) -> ok.
|
||||||
|
pubrel(Session, MsgId) when is_pid(Session) ->
|
||||||
|
gen_server:cast(Session, {pubrel, MsgId}).
|
||||||
|
|
||||||
|
-spec pubcomp(Session :: pid(), MsgId :: mqtt_packet_id()) -> ok.
|
||||||
|
pubcomp(Session, MsgId) when is_pid(Session) ->
|
||||||
|
gen_server:cast(Session, {pubcomp, MsgId}).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% @doc Subscribe Topics
|
||||||
|
%% @end
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-spec subscribe(Session :: pid(), [{binary(), mqtt_qos()}]) -> {ok, [mqtt_qos()]}.
|
||||||
|
subscribe(Session, Topics) when is_pid(Session) ->
|
||||||
|
gen_server:call(Session, {subscribe, Topics}).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% @doc Unsubscribe Topics
|
||||||
|
%% @end
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-spec unsubscribe(Session :: pid(), [Topic :: binary()]) -> ok.
|
||||||
|
unsubscribe(Session, Topics) when is_pid(Session) ->
|
||||||
|
gen_server:call(Session, {unsubscribe, Topics}).
|
||||||
|
|
||||||
|
%%%=============================================================================
|
||||||
|
%%% gen_server callbacks
|
||||||
|
%%%=============================================================================
|
||||||
|
init([CleanSess, ClientId, ClientPid]) ->
|
||||||
|
if
|
||||||
|
CleanSess =:= false ->
|
||||||
|
process_flag(trap_exit, true),
|
||||||
|
true = link(ClientPid);
|
||||||
|
CleanSess =:= true ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
QEnv = emqttd:env(mqtt, queue),
|
QEnv = emqttd:env(mqtt, queue),
|
||||||
SessEnv = emqttd:env(mqtt, session),
|
SessEnv = emqttd:env(mqtt, session),
|
||||||
#session{
|
PendingQ = emqttd_mqueue:new(ClientId, QEnv),
|
||||||
|
InflightQ = emqttd_inflight:new(ClientId, emqttd_opts:g(max_inflight, SessEnv)),
|
||||||
|
Session = #session{
|
||||||
|
clean_sess = CleanSess,
|
||||||
clientid = ClientId,
|
clientid = ClientId,
|
||||||
clean_sess = true,
|
client_pid = ClientPid,
|
||||||
subscriptions = [],
|
subscriptions = [],
|
||||||
inflight_window = emqttd_mqwin:new(ClientId, QEnv),
|
inflight_queue = InflightQ,
|
||||||
pending_queue = emqttd_mqueue:new(ClientId, QEnv),
|
pending_queue = PendingQ,
|
||||||
awaiting_rel = #{},
|
awaiting_rel = #{},
|
||||||
awaiting_ack = #{},
|
awaiting_ack = #{},
|
||||||
awaiting_comp = #{},
|
awaiting_comp = #{},
|
||||||
unack_retries = emqttd_opts:g(unack_retries, SessEnv),
|
unack_retries = emqttd_opts:g(unack_retries, SessEnv),
|
||||||
unack_timeout = emqttd_opts:g(unack_timeout, SessEnv),
|
unack_timeout = emqttd_opts:g(unack_timeout, SessEnv),
|
||||||
await_rel_timeout = emqttd_opts:g(await_rel_timeout, SessEnv),
|
await_rel_timeout = emqttd_opts:g(await_rel_timeout, SessEnv),
|
||||||
max_awaiting_rel = emqttd_opts:g(max_awaiting_rel, SessEnv),
|
max_awaiting_rel = emqttd_opts:g(max_awaiting_rel, SessEnv),
|
||||||
expired_after = emqttd_opts:g(expired_after, SessEnv) * 3600
|
expired_after = emqttd_opts:g(expired_after, SessEnv) * 3600,
|
||||||
}.
|
timestamp = os:timestamp()
|
||||||
|
},
|
||||||
|
{ok, Session, hibernate}.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
handle_call({subscribe, Topics}, _From, Session = #session{clientid = ClientId, subscriptions = Subscriptions}) ->
|
||||||
%% @doc Publish message
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec publish(session() | pid(), mqtt_clientid(), {mqtt_qos(), mqtt_message()}) -> session() | pid().
|
|
||||||
publish(Session, ClientId, {?QOS_0, Message}) ->
|
|
||||||
%% publish qos0 directly
|
|
||||||
emqttd_pubsub:publish(ClientId, Message), Session;
|
|
||||||
|
|
||||||
publish(Session, ClientId, {?QOS_1, Message}) ->
|
|
||||||
%% publish qos1 directly, and client will puback
|
|
||||||
emqttd_pubsub:publish(ClientId, Message), Session;
|
|
||||||
|
|
||||||
publish(Session = #session{awaiting_rel = AwaitingRel,
|
|
||||||
await_rel_timeout = Timeout,
|
|
||||||
max_awaiting_rel = MaxLen}, ClientId,
|
|
||||||
{?QOS_2, Message = #mqtt_message{msgid = MsgId}}) ->
|
|
||||||
case maps:size(AwaitingRel) >= MaxLen of
|
|
||||||
true -> lager:error([{clientid, ClientId}], "Session ~s "
|
|
||||||
" dropped Qos2 message for too many awaiting_rel: ~p", [ClientId, Message]);
|
|
||||||
false ->
|
|
||||||
%% store in awaiting_rel
|
|
||||||
TRef = erlang:send_after(Timeout * 1000, self(), {timeout, awaiting_rel, MsgId}),
|
|
||||||
Session#session{awaiting_rel = maps:put(MsgId, {Message, TRef}, AwaitingRel)};
|
|
||||||
end;
|
|
||||||
publish(SessPid, ClientId, {?QOS_2, Message}) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {publish, ClientId, {?QOS_2, Message}}), SessPid.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc PubAck message
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-spec puback(session(), {mqtt_packet_type(), mqtt_packet_id()}) -> session().
|
|
||||||
puback(Session = #session{clientid = ClientId, awaiting_ack = Awaiting}, {?PUBACK, PacketId}) ->
|
|
||||||
case maps:is_key(PacketId, Awaiting) of
|
|
||||||
true -> ok;
|
|
||||||
false -> lager:warning("Session ~s: PUBACK PacketId '~p' not found!", [ClientId, PacketId])
|
|
||||||
end,
|
|
||||||
Session#session{awaiting_ack = maps:remove(PacketId, Awaiting)};
|
|
||||||
|
|
||||||
puback(SessPid, {?PUBACK, PacketId}) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {puback, {?PUBACK, PacketId});
|
|
||||||
|
|
||||||
%% PUBREC
|
|
||||||
puback(Session = #session{clientid = ClientId,
|
|
||||||
awaiting_ack = AwaitingAck,
|
|
||||||
awaiting_comp = AwaitingComp}, {?PUBREC, PacketId}) ->
|
|
||||||
case maps:is_key(PacketId, AwaitingAck) of
|
|
||||||
true -> ok;
|
|
||||||
false -> lager:warning("Session ~s: PUBREC PacketId '~p' not found!", [ClientId, PacketId])
|
|
||||||
end,
|
|
||||||
Session#session{awaiting_ack = maps:remove(PacketId, AwaitingAck),
|
|
||||||
awaiting_comp = maps:put(PacketId, true, AwaitingComp)};
|
|
||||||
|
|
||||||
puback(SessPid, {?PUBREC, PacketId}) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {puback, {?PUBREC, PacketId});
|
|
||||||
|
|
||||||
%% PUBREL
|
|
||||||
puback(Session = #session{clientid = ClientId, awaiting_rel = Awaiting}, {?PUBREL, PacketId}) ->
|
|
||||||
case maps:find(PacketId, Awaiting) of
|
|
||||||
{ok, {Msg, TRef}} ->
|
|
||||||
catch erlang:cancel_timer(TRef),
|
|
||||||
emqttd_pubsub:publish(ClientId, Msg);
|
|
||||||
error ->
|
|
||||||
lager:error("Session ~s cannot find PUBREL PacketId '~p'!", [ClientId, PacketId])
|
|
||||||
end,
|
|
||||||
Session#session{awaiting_rel = maps:remove(PacketId, Awaiting)};
|
|
||||||
|
|
||||||
puback(SessPid, {?PUBREL, PacketId}) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {puback, {?PUBREL, PacketId});
|
|
||||||
|
|
||||||
%% PUBCOMP
|
|
||||||
puback(Session = #session{clientid = ClientId,
|
|
||||||
awaiting_comp = AwaitingComp}, {?PUBCOMP, PacketId}) ->
|
|
||||||
case maps:is_key(PacketId, AwaitingComp) of
|
|
||||||
true -> ok;
|
|
||||||
false -> lager:warning("Session ~s: PUBREC PacketId '~p' not exist", [ClientId, PacketId])
|
|
||||||
end,
|
|
||||||
Session#session{awaiting_comp = maps:remove(PacketId, AwaitingComp)};
|
|
||||||
|
|
||||||
puback(SessPid, {?PUBCOMP, PacketId}) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {puback, {?PUBCOMP, PacketId});
|
|
||||||
|
|
||||||
wait_ack
|
|
||||||
|
|
||||||
timeout(awaiting_rel, MsgId, Session = #session{clientid = ClientId, awaiting_rel = Awaiting}) ->
|
|
||||||
case maps:find(MsgId, Awaiting) of
|
|
||||||
{ok, {Msg, _TRef}} ->
|
|
||||||
lager:error([{client, ClientId}], "Session ~s Awaiting Rel Timout!~nDrop Message:~p", [ClientId, Msg]),
|
|
||||||
Session#session{awaiting_rel = maps:remove(MsgId, Awaiting)};
|
|
||||||
error ->
|
|
||||||
lager:error([{client, ClientId}], "Session ~s Cannot find Awaiting Rel: MsgId=~p", [ClientId, MsgId]),
|
|
||||||
Session
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Subscribe Topics
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec subscribe(session() | pid(), [{binary(), mqtt_qos()}]) -> {ok, session() | pid(), [mqtt_qos()]}.
|
|
||||||
subscribe(Session = #session{clientid = ClientId, subscriptions = Subscriptions}, Topics) ->
|
|
||||||
|
|
||||||
%% subscribe first and don't care if the subscriptions have been existed
|
%% subscribe first and don't care if the subscriptions have been existed
|
||||||
{ok, GrantedQos} = emqttd_pubsub:subscribe(Topics),
|
{ok, GrantedQos} = emqttd_pubsub:subscribe(Topics),
|
||||||
|
|
||||||
lager:info([{client, ClientId}], "Client ~s subscribe ~p. Granted QoS: ~p",
|
lager:info([{client, ClientId}], "Session ~s subscribe ~p. Granted QoS: ~p",
|
||||||
[ClientId, Topics, GrantedQos]),
|
[ClientId, Topics, GrantedQos]),
|
||||||
|
|
||||||
Subscriptions1 =
|
Subscriptions1 =
|
||||||
|
@ -310,19 +259,9 @@ subscribe(Session = #session{clientid = ClientId, subscriptions = Subscriptions}
|
||||||
[{Topic, Qos} | Acc]
|
[{Topic, Qos} | Acc]
|
||||||
end
|
end
|
||||||
end, Subscriptions, Topics),
|
end, Subscriptions, Topics),
|
||||||
|
{reply, {ok, GrantedQos}, Session#session{subscriptions = Subscriptions1}};
|
||||||
|
|
||||||
{ok, Session#session{subscriptions = Subscriptions1}, GrantedQos};
|
handle_call({unsubscribe, Topics}, _From, Session = #session{clientid = ClientId, subscriptions = Subscriptions}) ->
|
||||||
|
|
||||||
subscribe(SessPid, Topics) when is_pid(SessPid) ->
|
|
||||||
{ok, GrantedQos} = gen_server:call(SessPid, {subscribe, Topics}),
|
|
||||||
{ok, SessPid, GrantedQos}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Unsubscribe Topics
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec unsubscribe(session() | pid(), [binary()]) -> {ok, session() | pid()}.
|
|
||||||
unsubscribe(Session = #session{clientid = ClientId, subscriptions = Subscriptions}, Topics) ->
|
|
||||||
|
|
||||||
%%unsubscribe from topic tree
|
%%unsubscribe from topic tree
|
||||||
ok = emqttd_pubsub:unsubscribe(Topics),
|
ok = emqttd_pubsub:unsubscribe(Topics),
|
||||||
|
@ -338,63 +277,11 @@ unsubscribe(Session = #session{clientid = ClientId, subscriptions = Subscription
|
||||||
end
|
end
|
||||||
end, Subscriptions, Topics),
|
end, Subscriptions, Topics),
|
||||||
|
|
||||||
{ok, Session#session{subscriptions = Subscriptions1}};
|
{reply, ok, Session#session{subscriptions = Subscriptions1}};
|
||||||
|
|
||||||
unsubscribe(SessPid, Topics) when is_pid(SessPid) ->
|
handle_call({destroy, ClientId}, _From, Session = #session{clientid = ClientId}) ->
|
||||||
gen_server:call(SessPid, {unsubscribe, Topics}),
|
lager:warning("Session ~s destroyed", [ClientId]),
|
||||||
{ok, SessPid}.
|
{stop, {shutdown, destroy}, ok, Session};
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Destroy Session
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
% message(qos1) is awaiting ack
|
|
||||||
await_ack(Msg = #mqtt_message{qos = ?QOS_1}, Session = #session{message_id = MsgId,
|
|
||||||
inflight_queue = InflightQ,
|
|
||||||
awaiting_ack = Awaiting,
|
|
||||||
unack_retry_after = Time,
|
|
||||||
max_unack_retries = Retries}) ->
|
|
||||||
%% assign msgid before send
|
|
||||||
Msg1 = Msg#mqtt_message{msgid = MsgId},
|
|
||||||
TRef = erlang:send_after(Time * 1000, self(), {retry, MsgId}),
|
|
||||||
Awaiting1 = maps:put(MsgId, {TRef, Retries, Time}, Awaiting),
|
|
||||||
{Msg1, next_msgid(Session#session{inflight_queue = [{MsgId, Msg1} | InflightQ],
|
|
||||||
awaiting_ack = Awaiting1})}.
|
|
||||||
|
|
||||||
% message(qos2) is awaiting ack
|
|
||||||
await_ack(Message = #mqtt_message{qos = Qos}, Session = #session{message_id = MsgId, awaiting_ack = Awaiting},)
|
|
||||||
when (Qos =:= ?QOS_1) orelse (Qos =:= ?QOS_2) ->
|
|
||||||
%%assign msgid before send
|
|
||||||
Message1 = Message#mqtt_message{msgid = MsgId, dup = false},
|
|
||||||
Message2 =
|
|
||||||
if
|
|
||||||
Qos =:= ?QOS_2 -> Message1#mqtt_message{dup = false};
|
|
||||||
true -> Message1
|
|
||||||
end,
|
|
||||||
Awaiting1 = maps:put(MsgId, Message2, Awaiting),
|
|
||||||
{Message1, next_msgid(Session#session{awaiting_ack = Awaiting1})}.
|
|
||||||
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% gen_server callbacks
|
|
||||||
%%%=============================================================================
|
|
||||||
init([ClientId, ClientPid]) ->
|
|
||||||
process_flag(trap_exit, true),
|
|
||||||
true = link(ClientPid),
|
|
||||||
Session = emqttd_session:new(ClientId),
|
|
||||||
{ok, Session#session{clean_sess = false,
|
|
||||||
client_pid = ClientPid,
|
|
||||||
timestamp = os:timestamp()}, hibernate}.
|
|
||||||
|
|
||||||
|
|
||||||
handle_call({subscribe, Topics}, _From, Session) ->
|
|
||||||
{ok, NewSession, GrantedQos} = subscribe(Session, Topics),
|
|
||||||
{reply, {ok, GrantedQos}, NewSession};
|
|
||||||
|
|
||||||
handle_call({unsubscribe, Topics}, _From, Session) ->
|
|
||||||
{ok, NewSession} = unsubscribe(Session, Topics),
|
|
||||||
{reply, ok, NewSession};
|
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
handle_call(Req, _From, State) ->
|
||||||
lager:error("Unexpected Request: ~p", [Req]),
|
lager:error("Unexpected Request: ~p", [Req]),
|
||||||
|
@ -403,10 +290,10 @@ handle_call(Req, _From, State) ->
|
||||||
handle_cast({resume, ClientId, ClientPid}, State = #session{
|
handle_cast({resume, ClientId, ClientPid}, State = #session{
|
||||||
clientid = ClientId,
|
clientid = ClientId,
|
||||||
client_pid = OldClientPid,
|
client_pid = OldClientPid,
|
||||||
msg_queue = Queue,
|
pending_queue = Queue,
|
||||||
awaiting_ack = AwaitingAck,
|
awaiting_ack = AwaitingAck,
|
||||||
awaiting_comp = AwaitingComp,
|
awaiting_comp = AwaitingComp,
|
||||||
expire_timer = ETimer}) ->
|
expired_timer = ETimer}) ->
|
||||||
|
|
||||||
lager:info([{client, ClientId}], "Session ~s resumed by ~p",[ClientId, ClientPid]),
|
lager:info([{client, ClientId}], "Session ~s resumed by ~p",[ClientId, ClientPid]),
|
||||||
|
|
||||||
|
@ -426,8 +313,8 @@ handle_cast({resume, ClientId, ClientPid}, State = #session{
|
||||||
emqttd_util:cancel_timer(ETimer),
|
emqttd_util:cancel_timer(ETimer),
|
||||||
|
|
||||||
%% redelivery PUBREL
|
%% redelivery PUBREL
|
||||||
lists:foreach(fun(PacketId) ->
|
lists:foreach(fun(MsgId) ->
|
||||||
ClientPid ! {redeliver, {?PUBREL, PacketId}}
|
ClientPid ! {redeliver, {?PUBREL, MsgId}}
|
||||||
end, maps:keys(AwaitingComp)),
|
end, maps:keys(AwaitingComp)),
|
||||||
|
|
||||||
%% redelivery messages that awaiting PUBACK or PUBREC
|
%% redelivery messages that awaiting PUBACK or PUBREC
|
||||||
|
@ -442,45 +329,114 @@ handle_cast({resume, ClientId, ClientPid}, State = #session{
|
||||||
end, emqttd_queue:all(Queue)),
|
end, emqttd_queue:all(Queue)),
|
||||||
|
|
||||||
{noreply, State#session{client_pid = ClientPid,
|
{noreply, State#session{client_pid = ClientPid,
|
||||||
msg_queue = emqttd_queue:clear(Queue),
|
%%TODO:
|
||||||
expire_timer = undefined}, hibernate};
|
pending_queue = emqttd_queue:clear(Queue),
|
||||||
|
expired_timer = undefined}, hibernate};
|
||||||
|
|
||||||
handle_cast({publish, ClientId, {?QOS_2, Message}}, Session) ->
|
handle_cast({publish, Message = #mqtt_message{qos = ?QOS_2}}, Session) ->
|
||||||
{noreply, publish(Session, ClientId, {?QOS_2, Message})};
|
{noreply, publish_qos2(Message, Session)};
|
||||||
|
|
||||||
handle_cast({puback, {PubAck, PacketId}, Session) ->
|
|
||||||
{noreply, puback(Session, {PubAck, PacketId})};
|
|
||||||
|
|
||||||
handle_cast({destroy, ClientId}, Session = #session{clientid = ClientId}) ->
|
handle_cast({puback, MsgId}, Session = #session{clientid = ClientId, inflight_queue = Q, awaiting_ack = Awaiting}) ->
|
||||||
lager:warning("Session ~s destroyed", [ClientId]),
|
case maps:find(MsgId, Awaiting) of
|
||||||
{stop, normal, Session};
|
{ok, {_, TRef}} ->
|
||||||
|
catch erlang:cancel_timer(TRef),
|
||||||
|
{noreply, dispatch(Session#session{inflight_queue = emqttd_inflight:ack(MsgId, Q),
|
||||||
|
awaiting_ack = maps:remove(MsgId, Awaiting)})};
|
||||||
|
error ->
|
||||||
|
lager:error("Session ~s cannot find PUBACK '~p'!", [ClientId, MsgId]),
|
||||||
|
{noreply, Session}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% PUBREC
|
||||||
|
handle_cast({pubrec, MsgId}, Session = #session{clientid = ClientId,
|
||||||
|
awaiting_ack = AwaitingAck,
|
||||||
|
awaiting_comp = AwaitingComp,
|
||||||
|
await_rel_timeout = Timeout}) ->
|
||||||
|
case maps:find(MsgId, AwaitingAck) of
|
||||||
|
{ok, {_, TRef}} ->
|
||||||
|
catch erlang:cancel_timer(TRef),
|
||||||
|
TRef1 = timer(Timeout, {timeout, awaiting_comp, MsgId}),
|
||||||
|
{noreply, dispatch(Session#session{awaiting_ack = maps:remove(MsgId, AwaitingAck),
|
||||||
|
awaiting_comp = maps:put(MsgId, TRef1, AwaitingComp)})};
|
||||||
|
error ->
|
||||||
|
lager:error("Session ~s cannot find PUBREC '~p'!", [ClientId, MsgId]),
|
||||||
|
{noreply, Session}
|
||||||
|
end;
|
||||||
|
|
||||||
|
handle_cast({pubrel, MsgId}, Session = #session{clientid = ClientId, awaiting_rel = Awaiting}) ->
|
||||||
|
case maps:find(MsgId, Awaiting) of
|
||||||
|
{ok, {Msg, TRef}} ->
|
||||||
|
catch erlang:cancel_timer(TRef),
|
||||||
|
emqttd_pubsub:publish(Msg),
|
||||||
|
{noreply, Session#session{awaiting_rel = maps:remove(MsgId, Awaiting)}};
|
||||||
|
error ->
|
||||||
|
lager:error("Session ~s cannot find PUBREL'~p'!", [ClientId, MsgId]),
|
||||||
|
{noreply, Session}
|
||||||
|
end;
|
||||||
|
|
||||||
|
%% PUBCOMP
|
||||||
|
handle_cast({pubcomp, MsgId}, Session = #session{clientid = ClientId, awaiting_comp = AwaitingComp}) ->
|
||||||
|
case maps:is_key(MsgId, AwaitingComp) of
|
||||||
|
true ->
|
||||||
|
{noreply, Session#session{awaiting_comp = maps:remove(MsgId, AwaitingComp)}};
|
||||||
|
false ->
|
||||||
|
lager:error("Session ~s cannot find PUBREC MsgId '~p'", [ClientId, MsgId]),
|
||||||
|
{noreply, Session}
|
||||||
|
end;
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
handle_cast(Msg, State) ->
|
||||||
lager:critical("Unexpected Msg: ~p, State: ~p", [Msg, State]),
|
lager:critical("Unexpected Msg: ~p, State: ~p", [Msg, State]),
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({dispatch, {_From, Messages}}, Session) when is_list(Messages) ->
|
handle_info({dispatch, MsgList}, Session) when is_list(MsgList) ->
|
||||||
F = fun(Message, S) -> dispatch(Message, S) end,
|
NewSession = lists:foldl(fun(Msg, S) ->
|
||||||
{noreply, lists:foldl(F, Session, Messages)};
|
dispatch({new, Msg}, S)
|
||||||
|
end, Session, MsgList),
|
||||||
|
{noreply, NewSession};
|
||||||
|
|
||||||
handle_info({dispatch, {_From, Message}}, State) ->
|
handle_info({dispatch, {old, Msg}}, Session) when is_record(Msg, mqtt_message) ->
|
||||||
{noreply, dispatch(Message, State)};
|
{noreply, dispatch({old, Msg}, Session)};
|
||||||
|
|
||||||
handle_info({'EXIT', ClientPid, Reason}, Session = #session{clientid = ClientId,
|
handle_info({dispatch, Msg}, Session) when is_record(Msg, mqtt_message) ->
|
||||||
client_pid = ClientPid}) ->
|
{noreply, dispatch({new, Msg}, Session)};
|
||||||
lager:info("Session: client ~s@~p exited for ~p", [ClientId, ClientPid, Reason]),
|
|
||||||
{noreply, start_expire_timer(Session#session{client_pid = undefined})};
|
handle_info({'EXIT', ClientPid, Reason}, Session = #session{clean_sess = false,
|
||||||
|
clientid = ClientId,
|
||||||
|
client_pid = ClientPid,
|
||||||
|
expired_after = Expires}) ->
|
||||||
|
%%TODO: Clean puback, pubrel, pubcomp timers
|
||||||
|
lager:info("Session ~s: client ~p exited for ~p", [ClientId, ClientPid, Reason]),
|
||||||
|
TRef = timer(Expires * 1000, session_expired),
|
||||||
|
{noreply, Session#session{expired_timer = TRef}};
|
||||||
|
|
||||||
handle_info({'EXIT', ClientPid0, _Reason}, State = #session{client_pid = ClientPid}) ->
|
handle_info({'EXIT', ClientPid0, _Reason}, State = #session{client_pid = ClientPid}) ->
|
||||||
lager:error("Unexpected Client EXIT: pid=~p, pid(state): ~p", [ClientPid0, ClientPid]),
|
lager:critical("Unexpected Client EXIT: pid=~p, pid(state): ~p", [ClientPid0, ClientPid]),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_info(session_expired, State = #session{clientid = ClientId}) ->
|
handle_info(session_expired, State = #session{clientid = ClientId}) ->
|
||||||
lager:warning("Session ~s expired!", [ClientId]),
|
lager:error("Session ~s expired, shutdown now!", [ClientId]),
|
||||||
{stop, {shutdown, expired}, State};
|
{stop, {shutdown, expired}, State};
|
||||||
|
|
||||||
handle_info({timeout, awaiting_rel, MsgId}, Session) ->
|
handle_info({timeout, awaiting_rel, MsgId}, Session = #session{clientid = ClientId, awaiting_rel = Awaiting}) ->
|
||||||
{noreply, timeout(awaiting_rel, MsgId, Session)};
|
case maps:find(MsgId, Awaiting) of
|
||||||
|
{ok, {Msg, _TRef}} ->
|
||||||
|
lager:error([{client, ClientId}], "Session ~s Awaiting Rel Timout!~nDrop Message:~p", [ClientId, Msg]),
|
||||||
|
{noreply, Session#session{awaiting_rel = maps:remove(MsgId, Awaiting)}};
|
||||||
|
error ->
|
||||||
|
lager:error([{client, ClientId}], "Session ~s Cannot find Awaiting Rel: MsgId=~p", [ClientId, MsgId]),
|
||||||
|
{noreply, Session}
|
||||||
|
end;
|
||||||
|
|
||||||
|
handle_info({timeout, awaiting_comp, MsgId}, Session = #session{clientid = ClientId, awaiting_comp = Awaiting}) ->
|
||||||
|
case maps:find(MsgId, Awaiting) of
|
||||||
|
{ok, _TRef} ->
|
||||||
|
lager:error([{client, ClientId}], "Session ~s Awaiting PUBCOMP Timout: MsgId=~p!", [ClientId, MsgId]),
|
||||||
|
{noreply, Session#session{awaiting_comp = maps:remove(MsgId, Awaiting)}};
|
||||||
|
error ->
|
||||||
|
lager:error([{client, ClientId}], "Session ~s Cannot find Awaiting PUBCOMP: MsgId=~p", [ClientId, MsgId]),
|
||||||
|
{noreply, Session}
|
||||||
|
end;
|
||||||
|
|
||||||
handle_info(Info, Session) ->
|
handle_info(Info, Session) ->
|
||||||
lager:critical("Unexpected Info: ~p, Session: ~p", [Info, Session]),
|
lager:critical("Unexpected Info: ~p, Session: ~p", [Info, Session]),
|
||||||
|
@ -492,53 +448,106 @@ terminate(_Reason, _Session) ->
|
||||||
code_change(_OldVsn, Session, _Extra) ->
|
code_change(_OldVsn, Session, _Extra) ->
|
||||||
{ok, Session}.
|
{ok, Session}.
|
||||||
|
|
||||||
|
%%%=============================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%=============================================================================
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% @private
|
||||||
|
%% @doc Plubish Qos2 message from client -> broker, and then wait for pubrel.
|
||||||
|
%% @end
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
publish_qos2(Message = #mqtt_message{qos = ?QOS_2,msgid = MsgId}, Session = #session{clientid = ClientId,
|
||||||
|
awaiting_rel = AwaitingRel,
|
||||||
|
await_rel_timeout = Timeout}) ->
|
||||||
|
|
||||||
|
case check_awaiting_rel(Session) of
|
||||||
|
true ->
|
||||||
|
TRef = timer(Timeout, {timeout, awaiting_rel, MsgId}),
|
||||||
|
Session#session{awaiting_rel = maps:put(MsgId, {Message, TRef}, AwaitingRel)};
|
||||||
|
false ->
|
||||||
|
lager:error([{clientid, ClientId}], "Session ~s "
|
||||||
|
" dropped Qos2 message for too many awaiting_rel: ~p", [ClientId, Message]),
|
||||||
|
Session
|
||||||
|
end.
|
||||||
|
|
||||||
|
check_awaiting_rel(#session{max_awaiting_rel = 0}) ->
|
||||||
|
true;
|
||||||
|
check_awaiting_rel(#session{awaiting_rel = AwaitingRel,
|
||||||
|
max_awaiting_rel = MaxLen}) ->
|
||||||
|
maps:size(AwaitingRel) < MaxLen.
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% Dispatch message from broker -> client.
|
%%% Dispatch message from broker -> client.
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
||||||
|
dispatch(Session = #session{client_pid = undefined}) ->
|
||||||
|
%% do nothing
|
||||||
|
Session;
|
||||||
|
|
||||||
|
dispatch(Session = #session{pending_queue = PendingQ}) ->
|
||||||
|
case emqttd_mqueue:out(PendingQ) of
|
||||||
|
{empty, _Q} ->
|
||||||
|
Session;
|
||||||
|
{{value, Msg}, Q1} ->
|
||||||
|
self() ! {dispatch, {old, Msg}},
|
||||||
|
Session#session{pending_queue = Q1}
|
||||||
|
end.
|
||||||
|
|
||||||
%% queued the message if client is offline
|
%% queued the message if client is offline
|
||||||
dispatch(Msg, Session = #session{client_pid = undefined}) ->
|
dispatch({Type, Msg}, Session = #session{client_pid = undefined,
|
||||||
queue(Msg, Session);
|
pending_queue= PendingQ}) ->
|
||||||
|
Session#session{pending_queue = emqttd_mqueue:in({Type, Msg}, PendingQ)};
|
||||||
|
|
||||||
%% dispatch qos0 directly to client process
|
%% dispatch qos0 directly to client process
|
||||||
dispatch(Msg = #mqtt_message{qos = ?QOS_0}, Session = #session{client_pid = ClientPid}) ->
|
dispatch({_Type, Msg} = #mqtt_message{qos = ?QOS_0}, Session = #session{client_pid = ClientPid}) ->
|
||||||
ClientPid ! {dispatch, {self(), Msg}}, Session;
|
ClientPid ! {deliver, Msg}, Session;
|
||||||
|
|
||||||
%% dispatch qos1/2 messages and wait for puback
|
%% dispatch qos1/2 message and wait for puback
|
||||||
dispatch(Msg = #mqtt_message{qos = Qos}, Session = #session{clientid = ClientId,
|
dispatch({Type, Msg = #mqtt_message{qos = Qos}}, Session = #session{clientid = ClientId,
|
||||||
message_id = MsgId,
|
client_pid = ClientPid,
|
||||||
pending_queue = Q,
|
message_id = MsgId,
|
||||||
inflight_window = Win})
|
pending_queue = PendingQ,
|
||||||
when (Qos =:= ?QOS_1) orelse (Qos =:= ?QOS_2) ->
|
inflight_queue= InflightQ})
|
||||||
|
when Qos =:= ?QOS_1 orelse Qos =:= ?QOS_2 ->
|
||||||
case emqttd_mqwin:is_full(InflightWin) of
|
%% assign id first
|
||||||
true ->
|
Msg1 = Msg#mqtt_message{msgid = MsgId},
|
||||||
lager:error("Session ~s inflight window is full!", [ClientId]),
|
Msg2 =
|
||||||
Session#session{pending_queue = emqttd_mqueue:in(Msg, Q)};
|
if
|
||||||
false ->
|
Qos =:= ?QOS_1 -> Msg1;
|
||||||
Msg1 = Msg#mqtt_message{msgid = MsgId},
|
Qos =:= ?QOS_2 -> Msg1#mqtt_message{dup = false}
|
||||||
Msg2 =
|
end,
|
||||||
if
|
case emqttd_inflight:in(Msg1, InflightQ) of
|
||||||
Qos =:= ?QOS_2 -> Msg1#mqtt_message{dup = false};
|
{error, full} ->
|
||||||
true -> Msg1
|
lager:error("Session ~s inflight queue is full!", [ClientId]),
|
||||||
end,
|
Session#session{pending_queue = emqttd_mqueue:in({Type, Msg}, PendingQ)};
|
||||||
ClientPid ! {dispatch, {self(), Msg2}},
|
{ok, InflightQ1} ->
|
||||||
NewWin = emqttd_mqwin:in(Msg2, Win),
|
ClientPid ! {deliver, Msg1},
|
||||||
await_ack(Msg2, next_msgid(Session#session{inflight_window = NewWin}))
|
await_ack(Msg1, next_msgid(Session#session{inflight_queue = InflightQ1}))
|
||||||
end.
|
end.
|
||||||
|
|
||||||
queue(Msg, Session = #session{pending_queue= Queue}) ->
|
deliver(Msg, Session) ->
|
||||||
Session#session{pending_queue = emqttd_mqueue:in(Msg, Queue)}.
|
ok.
|
||||||
|
|
||||||
next_msgid(State = #session{message_id = 16#ffff}) ->
|
await(Msg, Session) ->
|
||||||
State#session{message_id = 1};
|
ok.
|
||||||
|
|
||||||
next_msgid(State = #session{message_id = MsgId}) ->
|
% message(qos1/2) is awaiting ack
|
||||||
State#session{message_id = MsgId + 1}.
|
await_ack(Msg = #mqtt_message{msgid = MsgId}, Session = #session{awaiting_ack = Awaiting,
|
||||||
|
unack_retries = Retries,
|
||||||
|
unack_timeout = Timeout}) ->
|
||||||
|
|
||||||
|
TRef = timer(Timeout * 1000, {retry, MsgId}),
|
||||||
|
Awaiting1 = maps:put(MsgId, {{Retries, Timeout}, TRef}, Awaiting),
|
||||||
|
Session#session{awaiting_ack = Awaiting1}.
|
||||||
|
|
||||||
start_expire_timer(Session = #session{expired_after = Expires,
|
timer(Timeout, TimeoutMsg) ->
|
||||||
expired_timer = OldTimer}) ->
|
erlang:send_after(Timeout * 1000, self(), TimeoutMsg).
|
||||||
emqttd_util:cancel_timer(OldTimer),
|
|
||||||
Timer = erlang:send_after(Expires * 1000, self(), session_expired),
|
next_msgid(Session = #session{message_id = 16#ffff}) ->
|
||||||
Session#session{expired_timer = Timer}.
|
Session#session{message_id = 1};
|
||||||
|
|
||||||
|
next_msgid(Session = #session{message_id = MsgId}) ->
|
||||||
|
Session#session{message_id = MsgId + 1}.
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
-behavior(supervisor).
|
-behavior(supervisor).
|
||||||
|
|
||||||
-export([start_link/0, start_session/2]).
|
-export([start_link/0, start_session/3]).
|
||||||
|
|
||||||
-export([init/1]).
|
-export([init/1]).
|
||||||
|
|
||||||
|
@ -46,16 +46,17 @@ start_link() ->
|
||||||
%% @doc Start a session
|
%% @doc Start a session
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec start_session(binary(), pid()) -> {ok, pid()}.
|
-spec start_session(boolean(), binary(), pid()) -> {ok, pid()}.
|
||||||
start_session(ClientId, ClientPid) ->
|
start_session(CleanSess, ClientId, ClientPid) ->
|
||||||
supervisor:start_child(?MODULE, [ClientId, ClientPid]).
|
supervisor:start_child(?MODULE, [CleanSess, ClientId, ClientPid]).
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% Supervisor callbacks
|
%%% Supervisor callbacks
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
{ok, {{simple_one_for_one, 10, 10},
|
{ok, {{simple_one_for_one, 0, 1},
|
||||||
[{session, {emqttd_session_proc, start_link, []},
|
[{session, {emqttd_session, start_link, []},
|
||||||
transient, 10000, worker, [emqttd_session_proc]}]}}.
|
transient, 10000, worker, [emqttd_session]}]}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -90,20 +90,10 @@ table() -> ?SESSION_TAB.
|
||||||
%% @end
|
%% @end
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
-spec start_session(CleanSess :: boolean(), binary()) -> {ok, module(), record() | pid()}.
|
-spec start_session(CleanSess :: boolean(), binary()) -> {ok, pid()} | {error, any()}.
|
||||||
start_session(true, ClientId) ->
|
start_session(CleanSess, ClientId) ->
|
||||||
%% destroy old session if existed
|
SM = gproc_pool:pick_worker(?SM_POOL, ClientId),
|
||||||
ok = destroy_session(ClientId),
|
call(SM, {start_session, {CleanSess, ClientId, self()}}).
|
||||||
{ok, emqttd_session, emqttd_session:new(ClientId)};
|
|
||||||
|
|
||||||
start_session(false, ClientId) ->
|
|
||||||
SmPid = gproc_pool:pick_worker(?SM_POOL, ClientId),
|
|
||||||
case call(SmPid, {start_session, ClientId, self()}) of
|
|
||||||
{ok, SessPid} ->
|
|
||||||
{ok, emqttd_session_proc, SessPid};
|
|
||||||
{error, Error} ->
|
|
||||||
{error, Error}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Lookup Session Pid
|
%% @doc Lookup Session Pid
|
||||||
|
@ -122,10 +112,10 @@ lookup_session(ClientId) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec destroy_session(binary()) -> ok.
|
-spec destroy_session(binary()) -> ok.
|
||||||
destroy_session(ClientId) ->
|
destroy_session(ClientId) ->
|
||||||
SmPid = gproc_pool:pick_worker(?SM_POOL, ClientId),
|
SM = gproc_pool:pick_worker(?SM_POOL, ClientId),
|
||||||
call(SmPid, {destroy_session, ClientId}).
|
call(SM, {destroy_session, ClientId}).
|
||||||
|
|
||||||
call(SmPid, Req) -> gen_server:call(SmPid, Req).
|
call(SM, Req) -> gen_server:call(SM, Req).
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% gen_server callbacks
|
%%% gen_server callbacks
|
||||||
|
@ -135,23 +125,27 @@ init([Id, StatsFun]) ->
|
||||||
gproc_pool:connect_worker(?SM_POOL, {?MODULE, Id}),
|
gproc_pool:connect_worker(?SM_POOL, {?MODULE, Id}),
|
||||||
{ok, #state{id = Id, statsfun = StatsFun}}.
|
{ok, #state{id = Id, statsfun = StatsFun}}.
|
||||||
|
|
||||||
handle_call({start_session, ClientId, ClientPid}, _From, State) ->
|
handle_call({start_session, {false, ClientId, ClientPid}}, _From, State) ->
|
||||||
Reply =
|
Reply =
|
||||||
case ets:lookup(?SESSION_TAB, ClientId) of
|
case ets:lookup(?SESSION_TAB, ClientId) of
|
||||||
[{_, SessPid, _MRef}] ->
|
[{_, SessPid, _MRef}] ->
|
||||||
emqttd_session_proc:resume(SessPid, ClientId, ClientPid),
|
emqttd_session:resume(SessPid, ClientId, ClientPid),
|
||||||
{ok, SessPid};
|
{ok, SessPid};
|
||||||
[] ->
|
[] ->
|
||||||
case emqttd_session_sup:start_session(ClientId, ClientPid) of
|
new_session(false, ClientId, ClientPid)
|
||||||
{ok, SessPid} ->
|
|
||||||
ets:insert(?SESSION_TAB, {ClientId, SessPid, erlang:monitor(process, SessPid)}),
|
|
||||||
{ok, SessPid};
|
|
||||||
{error, Error} ->
|
|
||||||
{error, Error}
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
{reply, Reply, setstats(State)};
|
{reply, Reply, setstats(State)};
|
||||||
|
|
||||||
|
handle_call({start_session, {true, ClientId, ClientPid}}, _From, State) ->
|
||||||
|
case ets:lookup(?SESSION_TAB, ClientId) of
|
||||||
|
[{_, SessPid, MRef}] ->
|
||||||
|
erlang:demonitor(MRef, [flush]),
|
||||||
|
emqttd_session:destroy_session(SessPid, ClientId);
|
||||||
|
[] ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
{reply, new_session(true, ClientId, ClientPid), setstats(State)};
|
||||||
|
|
||||||
handle_call({destroy_session, ClientId}, _From, State) ->
|
handle_call({destroy_session, ClientId}, _From, State) ->
|
||||||
case ets:lookup(?SESSION_TAB, ClientId) of
|
case ets:lookup(?SESSION_TAB, ClientId) of
|
||||||
[{_, SessPid, MRef}] ->
|
[{_, SessPid, MRef}] ->
|
||||||
|
@ -186,6 +180,16 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
||||||
|
new_session(CleanSess, ClientId, ClientPid) ->
|
||||||
|
case emqttd_session_sup:start_session(CleanSess, ClientId, ClientPid) of
|
||||||
|
{ok, SessPid} ->
|
||||||
|
ets:insert(?SESSION_TAB, {ClientId, SessPid, erlang:monitor(process, SessPid)}),
|
||||||
|
{ok, SessPid};
|
||||||
|
{error, Error} ->
|
||||||
|
{error, Error}
|
||||||
|
end.
|
||||||
|
|
||||||
setstats(State = #state{statsfun = StatsFun}) ->
|
setstats(State = #state{statsfun = StatsFun}) ->
|
||||||
StatsFun(ets:info(?SESSION_TAB, size)), State.
|
StatsFun(ets:info(?SESSION_TAB, size)), State.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -130,15 +130,8 @@ handle_cast({received, Packet}, State = #state{proto_state = ProtoState}) ->
|
||||||
handle_cast(_Msg, State) ->
|
handle_cast(_Msg, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({dispatch, {From, Messages}}, #state{proto_state = ProtoState} = State) when is_list(Messages) ->
|
handle_info({deliver, Message}, #state{proto_state = ProtoState} = State) ->
|
||||||
ProtoState1 =
|
{ok, ProtoState1} = emqttd_protocol:send(Message, ProtoState),
|
||||||
lists:foldl(fun(Message, PState) ->
|
|
||||||
{ok, PState1} = emqttd_protocol:send({From, Message}, PState), PState1
|
|
||||||
end, ProtoState, Messages),
|
|
||||||
{noreply, State#state{proto_state = ProtoState1}};
|
|
||||||
|
|
||||||
handle_info({dispatch, {From, Message}}, #state{proto_state = ProtoState} = State) ->
|
|
||||||
{ok, ProtoState1} = emqttd_protocol:send({From, Message}, ProtoState),
|
|
||||||
{noreply, State#state{proto_state = ProtoState1}};
|
{noreply, State#state{proto_state = ProtoState1}};
|
||||||
|
|
||||||
handle_info({redeliver, {?PUBREL, PacketId}}, #state{proto_state = ProtoState} = State) ->
|
handle_info({redeliver, {?PUBREL, PacketId}}, #state{proto_state = ProtoState} = State) ->
|
||||||
|
|
|
@ -90,6 +90,10 @@
|
||||||
%% Expired after 2 days
|
%% Expired after 2 days
|
||||||
{expired_after, 48},
|
{expired_after, 48},
|
||||||
|
|
||||||
|
%% Max number of QoS 1 and 2 messages that can be “in flight” at one time.
|
||||||
|
%% 0 means no limit
|
||||||
|
{max_inflight, 100},
|
||||||
|
|
||||||
%% Max retries for unack Qos1/2 messages
|
%% Max retries for unack Qos1/2 messages
|
||||||
{unack_retries, 3},
|
{unack_retries, 3},
|
||||||
|
|
||||||
|
@ -99,16 +103,14 @@
|
||||||
%% Awaiting PUBREL Timeout
|
%% Awaiting PUBREL Timeout
|
||||||
{await_rel_timeout, 8},
|
{await_rel_timeout, 8},
|
||||||
|
|
||||||
%% Max Packets that Awaiting PUBREL
|
%% Max Packets that Awaiting PUBREL, 0 means no limit
|
||||||
{max_awaiting_rel, 100}
|
{max_awaiting_rel, 0}
|
||||||
|
|
||||||
]},
|
]},
|
||||||
{queue, [
|
{queue, [
|
||||||
%% Max queue length
|
%% Max queue length
|
||||||
{max_length, 1000},
|
{max_length, 1000},
|
||||||
|
|
||||||
%% Max number of QoS 1 and 2 messages that can be “in flight” at one time.
|
|
||||||
{inflight_window, 100},
|
|
||||||
|
|
||||||
%% Low watermark of queued messsages
|
%% Low watermark of queued messsages
|
||||||
{low_watermark, 0.2},
|
{low_watermark, 0.2},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue