From 57f3af45a3473ee3c4032c17d51b16e4cf3299e8 Mon Sep 17 00:00:00 2001 From: angel-popov Date: Thu, 20 Feb 2020 09:50:04 +0200 Subject: [PATCH 01/10] Changed mqtt timeout for waiting for REL packet QoS2 (#273) * Changed await timeout so concierge account could load the profile * Refrormatting --- sys.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sys.config b/sys.config index f915adf54..e798c6c84 100644 --- a/sys.config +++ b/sys.config @@ -112,7 +112,7 @@ {max_inflight,32}, {retry_interval,20000}, {max_awaiting_rel,100000}, - {await_rel_timeout,20000}, + {await_rel_timeout,60000}, {enable_stats,false}, {expiry_interval,7200000}]}, {queue, -- GitLab From 20163f24981b7ce61822257ecdca6321a6d5747b Mon Sep 17 00:00:00 2001 From: Thomas Arts Date: Fri, 6 Mar 2020 14:53:05 +0100 Subject: [PATCH 02/10] Use NYNJA-MC emqttd and emqttc These fix a bug in memory spikes. --- rebar.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index 71df83a2c..dfc1848f4 100644 --- a/rebar.config +++ b/rebar.config @@ -6,9 +6,9 @@ {active, ".*", {git, "git://github.com/synrc/active",{tag,"master"}}}, {esockd, ".*", {git, "https://github.com/voxoz/esockd",{ref, "a80634b961c315ffe5f020d73236473b53ae5dc9"}}}, {bpe, ".*", {git, "git://github.com/synrc/bpe", {tag,"4.4"}}}, - {emqttd, ".*", {git, "git://github.com/synrc/emqttd",{tag,"master"}}}, + {emqttd, ".*", {git, "git://github.com/NYNJA-MC/emqttd",{tag,"master"}}}, {n2o, ".*", {git, "git://github.com/synrc/n2o", {tag,"6.4"}}}, - {emqttc, ".*", {git, "git://github.com/voxoz/emqttc",{tag,"master"}}}, + {emqttc, ".*", {git, "git://github.com/NYNJA-MC/emqttc",{tag,"master"}}}, {rest, ".*", {git, "git://github.com/synrc/rest",{tag,"5.10"}}}, {gen_smtp, ".*", {git, "git://github.com/voxoz/gen_smtp",{tag,"master"}}}, {emq_dashboard, ".*", {git, "https://github.com/synrc/emq_dashboard",{tag,"master"}}}, -- GitLab From e74912b50ab2369a55720c428e558bfe8e6dfa8d Mon Sep 17 00:00:00 2001 From: Thomas Arts Date: Thu, 5 Mar 2020 15:12:01 +0100 Subject: [PATCH 03/10] Don't loop in case of an error --- apps/roster/src/roster.erl | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/roster/src/roster.erl b/apps/roster/src/roster.erl index e3b47bed8..094ff5327 100644 --- a/apps/roster/src/roster.erl +++ b/apps/roster/src/roster.erl @@ -571,7 +571,8 @@ unload() -> on_client_disconnected(_Reason, #mqtt_client{client_id = <<"emqttd_", _/bytes>> = ClientId}, _Env) -> % TODO: mqttc:publish(C, lists:concat(["ses/",Phone])) on_disconnect(ClientId); -on_client_disconnected(_Reason, #mqtt_client{client_id = <<"reg_", _/bytes>> = ClientId}, _) -> final_disconnect(ClientId); +on_client_disconnected(_Reason, #mqtt_client{client_id = <<"reg_", _/bytes>> = ClientId}, _) -> + final_disconnect(ClientId); on_client_disconnected(_Reason, _Client = #mqtt_client{}, _Env) -> ok. on_disconnect(<<"emqttd_", _/binary>> = ClientId) -> info(roster_auth, "~p:CLIENT DISCONNECT",[ClientId]), @@ -2282,5 +2283,13 @@ cache_ses(PhoneId, Term) -> send_cache(#'Auth'{client_id = ClientId, settings = Settings} = Auth, C) -> [send_action(C, ClientId, V) || #'Feature'{key = <<"cache_", _/binary>>, value = V} <- Settings], case [F||#'Feature'{group = Group} = F <- Settings, Group /= <<"CACHE_GROUP">>] of - Settings -> ok; S -> kvs:put(Auth#'Auth'{settings = S}) end; -send_cache(ClientId, C) -> send_cache(element(2, kvs:get('Auth', ClientId)), C). + Settings -> + ok; + S -> + kvs:put(Auth#'Auth'{settings = S}) + end; +send_cache(ClientId, C) -> + case kvs:get('Auth', ClientId) of + {ok, Auth} -> send_cache(Auth, C); + Error -> Error + end. -- GitLab From b1393f3f4c4cb7e78e559b9027d6df6c769b6607 Mon Sep 17 00:00:00 2001 From: Thomas Arts Date: Thu, 5 Mar 2020 15:17:43 +0100 Subject: [PATCH 04/10] Code refactoring --- apps/roster/src/protocol/roster_presence.erl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/roster/src/protocol/roster_presence.erl b/apps/roster/src/protocol/roster_presence.erl index b184d0bb1..68ca6c173 100644 --- a/apps/roster/src/protocol/roster_presence.erl +++ b/apps/roster/src/protocol/roster_presence.erl @@ -22,7 +22,8 @@ on_connect(Phone, ClientId, C, Ver) -> case send_presence(online, Phone, C, ClientId) of {error, profile_not_found} -> roster:info(?MODULE, "~p:~p:Connect:ProfileNotFound", [Phone, ClientId]), roster:delete_sessions(Phone); - #'Profile'{} = P when Ver==?VERSION -> roster:send_cache(ClientId, C); + #'Profile'{} = P when Ver==?VERSION -> + roster:send_cache(ClientId, C); #'Profile'{} = P-> roster:send_profile(P#'Profile'{settings = amazon_settings(P), status = init}, [], 0, ClientId, C), roster:send_cache(ClientId, C) @@ -56,11 +57,15 @@ send_presence(Status, Phone, C, ClientId) when Status == online; Status == offli PhoneId = roster:phone_id(Phone, AccId), [roster:Send(C, PhoneId, #'Contact'{phone_id = PhoneId, presence = Status, update = Now, status = internal}) || Send <- [send_muc, send_ac]]; - #error{} -> skip + {error, _} -> skip end || AccId <- Rosters], P2; - _ -> roster:info(?MODULE, "~p:~p:~p",[Phone, ClientId,P]), P end; - _ -> {error, profile_not_found} end. + _ -> + roster:info(?MODULE, "~p:~p:~p",[Phone, ClientId,P]), + P + end; + _ -> {error, profile_not_found} + end. send_presence(Status, PhoneId, C) -> -- GitLab From 3498775105b568e39f3d52e140a739e7bd510f85 Mon Sep 17 00:00:00 2001 From: sennui Date: Fri, 6 Mar 2020 15:27:24 +0100 Subject: [PATCH 05/10] Change hardcoded path for link_roster.txt to local dir and ignore if missing --- apps/roster/src/roster_db.erl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/roster/src/roster_db.erl b/apps/roster/src/roster_db.erl index 76d26189c..6c4e33557 100644 --- a/apps/roster/src/roster_db.erl +++ b/apps/roster/src/roster_db.erl @@ -1010,8 +1010,14 @@ add_linkage([{PhoneMiss, PhoneHas} | Rest], Acc1, Acc2) -> end. read_link(FileName) -> - {ok, Text} = file:read_file("/home/nynja/depot/server/" ++ FileName), - read_link(string:split(Text, ";", all), []). + case file:read_file(FileName) of + {ok, Text} -> + read_link(string:split(Text, ";", all), []); + {error, _} = Reason -> + roster:info(?MODULE, "read_link file not read ~p", [Reason]), + [] + end. + read_link([], Acc) -> Acc; read_link([Link | Rest], Acc) -> -- GitLab From 14d94ef6990e4d539b31197e8e88b57292af55a5 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 6 Mar 2020 16:11:14 +0100 Subject: [PATCH 06/10] Add millisecond precision to roster:info logs --- apps/roster/src/roster.erl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/roster/src/roster.erl b/apps/roster/src/roster.erl index 094ff5327..ccde38cb8 100644 --- a/apps/roster/src/roster.erl +++ b/apps/roster/src/roster.erl @@ -2056,8 +2056,9 @@ daystime_to_ms({D, Time}) -> (D * 86400 + calendar:time_to_seconds(Time)) * 1000 timestamp_to_datetime(TimeStamp) -> UnixEpochGS = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), GregorianSeconds = (TimeStamp div 1000) + UnixEpochGS, + Ms = TimeStamp rem 1000, {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:gregorian_seconds_to_datetime(GregorianSeconds), - lists:flatten(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w", [Year, Month, Day, Hour, Minute, Second])). + lists:flatten(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0w.~3..0w", [Year, Month, Day, Hour, Minute, Second, Ms])). mk2(I) when I < 10 -> [$0 | integer_to_list(I)]; mk2(I) -> integer_to_list(I). -- GitLab From 4fdd007a11489a0328a5ceb0a6401221cb97acc3 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Fri, 6 Mar 2020 20:26:44 +0100 Subject: [PATCH 07/10] Don't evaluate length(...) unnecessarily --- apps/roster/src/roster.erl | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/apps/roster/src/roster.erl b/apps/roster/src/roster.erl index ccde38cb8..9e986471b 100644 --- a/apps/roster/src/roster.erl +++ b/apps/roster/src/roster.erl @@ -1274,29 +1274,19 @@ objlist(Index, Id, LastSync, N, Fun) when Index == #'Roster'.userlist; Index == #'Roster'.roomlist; Index == #'Roster'.favorite -> case kvs:get('Roster', Id) of - {ok, R} -> + {ok, R} -> objlist(Index, R, LastSync, N, Fun); _ -> {error, roster_not_found} end. -build_object(Object, - [Chunk | _] = Acc, - Roster, - LastSync, - N, - Fun) when length(Chunk) >= N -> +build_object(Object, [Chunk | _] = Acc, Roster, LastSync, N, Fun) + when N /= [] andalso length(Chunk) >= N -> Fun(Chunk), build_object(Object, [[] | Acc], Roster, LastSync, N, Fun); -build_object(Object, - Acc, - #'Roster'{ - id = Id, - phone = Phone - } = Roster, - LastSync, - _N, - _Fun) -> +build_object(Object, Acc, + #'Roster'{ id = Id, phone = Phone } = Roster, + LastSync, _N, _Fun) -> PhoneId = phone_id(Phone, Id), Feed = feed(Object, PhoneId), LastUpd = last_upd(Object), -- GitLab From 8bfc1d6e350fcaa53cace38add2a3d716d708060 Mon Sep 17 00:00:00 2001 From: Hans Svensson Date: Mon, 9 Mar 2020 20:42:52 +0100 Subject: [PATCH 08/10] Improve fetching contacts for get_profile We have already done half of the lookup(s), being a bit clever we can avoid repeating them. --- apps/roster/src/roster.erl | 48 ++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/apps/roster/src/roster.erl b/apps/roster/src/roster.erl index 9e986471b..e6239977a 100644 --- a/apps/roster/src/roster.erl +++ b/apps/roster/src/roster.erl @@ -1095,14 +1095,20 @@ sort_readers(Room) -> readmsgs(#'Contact'{phone_id = R}=C, LocR) -> C#'Contact'{reader = p2p_readmsgs(LocR, R)}; readmsgs(M, _) -> M. -p2p_readmsgs(R, R) -> [contact_readmsg(R,R), contact_readmsg(R,R)]; +p2p_readmsgs(LocR, C = #'Contact'{ phone_id = PhoneId }) -> + [contact_readmsg(LocR, PhoneId), contact_readmsg(C)]; p2p_readmsgs(LocR, R) -> [contact_readmsg(LocR, R), contact_readmsg(R, LocR)]. + +contact_readmsg(#'Contact'{ reader = ReaderId }) when ReaderId /= [] -> + reader_cache(ReaderId); +contact_readmsg(_) -> + 0. + contact_readmsg(#'Roster'{id = LocalId, phone = Phone}, #'Roster'{} = R) -> contact_readmsg(roster:phone_id(LocalId, Phone), R); contact_readmsg(LocalPhoneId, #'Roster'{userlist = Contacts}) -> - case lists:keyfind(LocalPhoneId, #'Contact'.phone_id, Contacts) of - #'Contact'{reader = ReaderId} when ReaderId /= []-> reader_cache(ReaderId); _ -> 0 end; + contact_readmsg(lists:keyfind(LocalPhoneId, #'Contact'.phone_id, Contacts)); contact_readmsg(LocalPhoneId, PhoneId) -> case kvs:get('Roster', roster_id(PhoneId)) of {ok, #'Roster'{} = R}-> contact_readmsg(LocalPhoneId, R); @@ -1315,48 +1321,40 @@ set_explicit_removed([], RoomId, RoomName) -> set_explicit_removed(Room, _, _) -> Room. - userlist(R) -> hd(split_objlist(#'Roster'.userlist, R)). user(Roster, C) -> user(Roster, C, 0). user(RosterId, UserId, LastSync) -> user(RosterId, UserId, [], LastSync). -user(#'Roster'{ - id = Id, - phone = Phone - }, - #'Contact'{ - phone_id = PhoneId, - reader = Reader - } = C, - W, - _LastSync) +user(#'Roster'{ id = Id, phone = Phone } = Roster, + #'Contact'{ phone_id = PhoneId, reader = Reader } = C, + W, _LastSync) when is_record(W, writer); is_record(W, p2p); W == [] -> - Writer = case W of + LocalPhoneId = phone_id(Phone, Id), + Writer = case W of [] -> - kvs_stream:load_writer(feed(C, phone_id(Phone, Id))); + kvs_stream:load_writer(feed(C, LocalPhoneId)); _ -> - W + W end, - Local = phone_id(Phone, Id), - {Unread, LastMsg, _} = unread_msg(Writer, Reader, Local), + {Unread, LastMsg, _} = unread_msg(Writer, Reader, LocalPhoneId), {Presence, Update} = is_online2(PhoneId), - Msgs = case catch p2p_readmsgs(Local, PhoneId) of + Msgs = case catch p2p_readmsgs(Roster, C) of {'EXIT', {Err, _}} -> n2o:error(?MODULE,"Catch:~p~n",[n2o:stack_trace(error, Err)]), - throw({error, {user, Local, PhoneId}}); - Ms -> Ms + throw({error, {user, LocalPhoneId, PhoneId}}); + Ms -> Ms end, C#'Contact'{ presence = Presence, - update = Update, + update = Update, services = get_services(phone(PhoneId)), - unread = Unread, + unread = Unread, last_msg = LastMsg, - reader = Msgs + reader = Msgs }; user(#'Roster'{id = RosterId, userlist = Users, phone = Phone}=R, UserId, W, LastSync) -> case lists:keyfind(UserId, #'Contact'.phone_id, Users) of -- GitLab From 54dab210303188c2d3055ff19d966449d91a168a Mon Sep 17 00:00:00 2001 From: sennui Date: Tue, 10 Mar 2020 17:30:28 +0100 Subject: [PATCH 09/10] cleanup, whitespace --- apps/roster/src/protocol/roster_message.erl | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/roster/src/protocol/roster_message.erl b/apps/roster/src/protocol/roster_message.erl index 4c341efca..afdbe65b5 100644 --- a/apps/roster/src/protocol/roster_message.erl +++ b/apps/roster/src/protocol/roster_message.erl @@ -47,7 +47,7 @@ info(#'Message'{status = [], id = [], feed_id = F, from=From0, to = To, <<"sys">> -> From0; <<"emqttd">> -> roster:phone_id(ClientId) end, - Created=roster:now_msec(), + FId = roster:roster_id(From), FeedData = {R, UID} = case Feed = roster:feed_key(F) of @@ -268,9 +268,12 @@ info(#'Message'{id = Id, msg_id = ClMID, feed_id = Feed, from = From0, seenby = _ -> skip end; _ -> skip end, n2o_vnode:send(C, Topic, term_to_binary(Internal)), %% NOTE! send push about deleted msg only if acted user is in the seenby - case Seen of [-1] -> - n2o_async:pid(system, ?MODULE) ! {send_push, From1, To1, Internal, ?MSG_DELETE_ACTION}; _ -> - skip end, + case Seen of + [-1] -> + n2o_async:pid(system, ?MODULE) ! {send_push, From1, To1, Internal, ?MSG_DELETE_ACTION}; + _ -> + skip + end, case {Type, Link} of {[reply], Link} when is_integer(Link) andalso NewSeen == [-1] -> case kvs:get('Message', Link) of @@ -431,7 +434,13 @@ proc({send_push, From, To, #'Message'{type = TypeList} = Msg, Action}, #handler{ %% Check should server proceed push %% TODO add push for message/delete case From of - To -> case lists:member(cursor, TypeList) of true -> notify(From, To, Msg, Action); _ -> roster:info(?MODULE, "ExcessivePush:~p", [From]) end; + To -> + case lists:member(cursor, TypeList) of + true -> + notify(From, To, Msg, Action); + _ -> + roster:info(?MODULE, "ExcessivePush:~p", [From]) + end; _ -> notify(From, To, Msg, Action) end, {reply, [], H}; -- GitLab From ba7348f83de79d50b258a223306b4685b6e6fc1b Mon Sep 17 00:00:00 2001 From: sennui Date: Tue, 10 Mar 2020 17:30:47 +0100 Subject: [PATCH 10/10] handle connection drop and subscription tables --- apps/roster/src/protocol/micro_auth.erl | 7 +++++++ apps/roster/src/protocol/roster_presence.erl | 13 ++++++++++-- apps/roster/src/roster.erl | 21 +++++++++++++------- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/apps/roster/src/protocol/micro_auth.erl b/apps/roster/src/protocol/micro_auth.erl index 44cf751d9..5508c1acb 100644 --- a/apps/roster/src/protocol/micro_auth.erl +++ b/apps/roster/src/protocol/micro_auth.erl @@ -45,6 +45,13 @@ check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, AuthPid = n2o_async:pid(system, ?MODULE), Ver = binary_to_list(BVer), case kvs:get('Auth', ClientId) of + {ok, #'Auth'{user_id = PhoneId, type = logout}} -> + FreshAuth = #'Auth'{user_id = PhoneId, phone = roster:phone(PhoneId), + last_online = roster:now_msec(), type = verified}, + kvs:put(FreshAuth), + roster:info(?MODULE, "~p:Auth:auth(micro)/check:session created, post logout ", [ClientId]), + AuthPid ! roster_auth:control_ver(FreshAuth, Ver), + ok; {ok, #'Auth'{user_id = PhoneId} = Auth} -> AuthPid ! roster_auth:control_ver(Auth#'Auth'{type = []}, Ver), ok; diff --git a/apps/roster/src/protocol/roster_presence.erl b/apps/roster/src/protocol/roster_presence.erl index 68ca6c173..57e26ec43 100644 --- a/apps/roster/src/protocol/roster_presence.erl +++ b/apps/roster/src/protocol/roster_presence.erl @@ -36,11 +36,20 @@ on_disconnect(#'Auth'{type = logout, phone = Phone, client_id = ClientId, user_i roster:info(?MODULE, "~p:~p:DISCONNECT:LOGOUT", [Phone, ClientId]), send_presence(offline, Phone, C, ClientId), roster:unsubscribe_p2p(ClientId, roster:roster_id(PhoneId)), - roster:unsubscribe_room(ClientId), kvs:delete('Auth', ClientId), + roster:unsubscribe_room(ClientId), + kvs:delete('Auth', ClientId), roster:final_disconnect(ClientId); -on_disconnect(#'Auth'{phone = Phone, client_id = ClientId}, C) -> +on_disconnect(#'Auth'{type = disconnect, phone = Phone, client_id = ClientId, user_id = PhoneId}, C) -> roster:info(?MODULE, "~p:~p:DISCONNECT", [Phone, ClientId]), + send_presence(offline, Phone, C, ClientId), + roster:unsubscribe_p2p(ClientId, roster:roster_id(PhoneId)), + roster:unsubscribe_room(ClientId), + %% Warning: Do not delete Auth record, it holds push notification tokens + roster:final_disconnect(ClientId); + +on_disconnect(#'Auth'{phone = Phone, client_id = ClientId, type=Type}, C) -> + roster:info(?MODULE, "~p:~p:DISCONNECT:~p", [Phone, ClientId, Type]), send_presence(offline, Phone, C, ClientId). on_verify(ClientId, PhoneId) -> roster:sub_client(subscribe, ClientId, PhoneId). diff --git a/apps/roster/src/roster.erl b/apps/roster/src/roster.erl index e6239977a..9075db7c3 100644 --- a/apps/roster/src/roster.erl +++ b/apps/roster/src/roster.erl @@ -321,7 +321,8 @@ parts_phone_id(PhoneId) -> [Phone] -> list_rosters(Phone, first_roster); _E -> [<<>>, 0] end. roster_id(<<"emqttd_", _/binary>> = ClientId) -> - {ok, #'Auth'{user_id = PhoneId}} = kvs:get('Auth', ClientId), roster_id(PhoneId); + {ok, #'Auth'{user_id = PhoneId}} = kvs:get('Auth', ClientId), + roster_id(PhoneId); roster_id(PhoneId) -> [_, Id] = parts_phone_id(PhoneId), Id. roster_index(Index,V)-> [Id || #'Roster'{id=Id} <-kvs:index('Roster',Index,V)]. @@ -568,18 +569,24 @@ unload() -> emqttd:unhook('session.terminated', fun ?MODULE:on_session_terminated/4), emqttd:unhook('client.disconnected', fun ?MODULE:on_client_disconnected/3). -on_client_disconnected(_Reason, #mqtt_client{client_id = <<"emqttd_", _/bytes>> = ClientId}, _Env) -> +on_client_disconnected(_Reason, #mqtt_client{client_id = <<"emqttd_", _/bytes>> = ClientId} = Client, _Env) -> + info(roster_auth, "~p:MQTT_CLIENT DISCONNECT ~p",[ClientId, Client]), % TODO: mqttc:publish(C, lists:concat(["ses/",Phone])) on_disconnect(ClientId); -on_client_disconnected(_Reason, #mqtt_client{client_id = <<"reg_", _/bytes>> = ClientId}, _) -> +on_client_disconnected(_Reason, #mqtt_client{client_id = <<"reg_", _/bytes>> = ClientId} = Client, _) -> + info(roster_auth, "~p:MQTT_CLIENT DISCONNECT ~p",[ClientId, Client]), final_disconnect(ClientId); -on_client_disconnected(_Reason, _Client = #mqtt_client{}, _Env) -> ok. +on_client_disconnected(_Reason, #mqtt_client{client_id = ClientId} = Client, _Env) -> + info(roster_auth, "~p:MQTT_CLIENT DISCONNECT ~p",[ClientId, Client]), + ok. on_disconnect(<<"emqttd_", _/binary>> = ClientId) -> info(roster_auth, "~p:CLIENT DISCONNECT",[ClientId]), case kvs:get('Auth', ClientId) of - {ok, Auth} -> n2o_async:pid(system, roster_auth) ! {disconnect, Auth}, - kvs:put(Auth#'Auth'{last_online = now_msec()}); - _ -> skip end, + {ok, Auth} -> + n2o_async:pid(system, roster_auth) ! {disconnect, Auth#'Auth'{type = disconnect}}, + kvs:put(Auth#'Auth'{last_online = now_msec(), type = logout}); + _ -> skip + end, ok. final_disconnect(ClientId) -> % info(roster_auth, "~p:ANY DISCONNECT",[RegClientId]), -- GitLab