Skip to content

Commit fc86296

Browse files
Merge pull request #4374 from esl/duplicated_mam_id
Fix doubled messaged in MAM bug
2 parents 9c1cdf8 + 729c237 commit fc86296

File tree

4 files changed

+171
-7
lines changed

4 files changed

+171
-7
lines changed

Diff for: big_tests/tests/mam_SUITE.erl

+151-2
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,12 @@ is_skipped(_, _) ->
239239
basic_groups() ->
240240
[
241241
{mam_all, [parallel],
242-
[{mam04, [parallel], mam_cases() ++ [retrieve_form_fields] ++ text_search_cases()},
242+
[{mam04, [parallel], mam_cases() ++ [retrieve_form_fields] ++ text_search_cases()
243+
++ [{stream_management, [], stream_management_cases()}]},
243244
{mam06, [parallel], mam_cases() ++ [retrieve_form_fields_extra_features]
244245
++ stanzaid_cases() ++ retract_cases()
245-
++ metadata_cases() ++ fetch_specific_msgs_cases()},
246+
++ metadata_cases() ++ fetch_specific_msgs_cases()
247+
++ [{stream_management, [], stream_management_cases()}]},
246248
{nostore, [parallel], nostore_cases()},
247249
{archived, [parallel], archived_cases()},
248250
{configurable_archiveid, [], configurable_archiveid_cases()},
@@ -513,6 +515,11 @@ impl_specific() ->
513515
pm_sql_query_failed,
514516
async_pools_batch_flush].
515517

518+
stream_management_cases() ->
519+
[reconnect_ack,
520+
reconnect_no_ack,
521+
reconnect_no_ack_different_resource].
522+
516523
suite() ->
517524
require_rpc_nodes([mim]) ++ escalus:suite().
518525

@@ -582,6 +589,13 @@ init_per_group(with_rsm04, Config) ->
582589
[{props, mam04_props()}, {with_rsm, true}|Config];
583590
init_per_group(nostore, Config) ->
584591
Config;
592+
init_per_group(stream_management, Config) ->
593+
Config1 = dynamic_modules:save_modules(host_type(), Config),
594+
DefaultSMConfig = config_parser_helper:default_mod_config(mod_stream_management),
595+
MnesiaOrCets = ct_helper:get_internal_database(),
596+
SMConfig = DefaultSMConfig#{backend => MnesiaOrCets},
597+
dynamic_modules:ensure_modules(host_type(), [{mod_stream_management, SMConfig}]),
598+
Config1;
585599
init_per_group(archived, Config) ->
586600
Config;
587601
init_per_group(muc04, Config) ->
@@ -683,6 +697,8 @@ end_per_group(G, Config) when G == drop_msg;
683697
G == muc_drop_msg ->
684698
teardown_meck(),
685699
Config;
700+
end_per_group(stream_management, Config) ->
701+
dynamic_modules:restore_modules(Config);
686702
end_per_group(muc_configurable_archiveid, Config) ->
687703
dynamic_modules:restore_modules(Config),
688704
Config;
@@ -3854,6 +3870,139 @@ check_user_exist(Config) ->
38543870
%% cleanup
38553871
ok = rpc(mim(), ejabberd_auth, remove_user, [JID]).
38563872

3873+
reconnect_no_ack(Config) ->
3874+
%% Connect Bob and Alice
3875+
Bob = sm_helper:connect_fresh(Config, bob, presence),
3876+
Alice = sm_helper:connect_fresh(Config, alice, sr_presence, manual),
3877+
AliceJid = escalus_client:full_jid(Alice),
3878+
BobJid = escalus_client:full_jid(Bob),
3879+
sm_helper:ack_initial_presence(Alice),
3880+
3881+
% 1. Bob sends a msg to Alice
3882+
Body = <<"OH, HAI! Msg 1">>,
3883+
escalus:send(Bob, escalus_stanza:chat_to(Alice, Body)),
3884+
mam_helper:wait_for_archive_size(Alice, 1),
3885+
3886+
% 2. Alice receives, and does not acknowledge
3887+
% She may get the ack request before the message for some reason
3888+
Resp = [_, _] = escalus_client:wait_for_stanzas(Alice, 2),
3889+
escalus:assert_many([fun(Msg) -> escalus_pred:is_chat_message_from_to(BobJid, AliceJid, Body, Msg) end,
3890+
fun(SMRequest) -> escalus_pred:is_sm_ack_request(SMRequest) end],
3891+
Resp),
3892+
3893+
% 3. Alice disconnects abruptly
3894+
C2SPid = mongoose_helper:get_session_pid(Alice),
3895+
escalus_connection:kill(Alice),
3896+
sm_helper:wait_until_resume_session(C2SPid),
3897+
sm_helper:assert_alive_resources(Alice, 1),
3898+
3899+
% 4. Alice reconnects
3900+
NewAlice = sm_helper:connect_same(Alice, session),
3901+
3902+
% We have to send presence by hand, because the message may be received first
3903+
sm_helper:send_initial_presence(NewAlice),
3904+
% Current behaviour - unacked stanza is rerouted when a quick reconnection occurs
3905+
% there is no delay element, or any indication of retransmission
3906+
NewResp = [_, _] = escalus_client:wait_for_stanzas(NewAlice, 2),
3907+
escalus:assert_many([fun(Msg) -> escalus_pred:is_chat_message_from_to(BobJid, AliceJid, Body, Msg) end,
3908+
fun(Presence) -> escalus_pred:is_presence(Presence) end],
3909+
NewResp),
3910+
3911+
AliceUsername = escalus_client:username(NewAlice),
3912+
AliceServer = escalus_client:server(NewAlice),
3913+
3914+
% There is only one message in MAM, even though it was resent
3915+
?assertEqual(1, mam_helper:archive_size(AliceServer, AliceUsername)),
3916+
3917+
escalus_connection:stop(Bob),
3918+
escalus_connection:stop(Alice).
3919+
3920+
reconnect_ack(Config) ->
3921+
% Connect Bob and Alice
3922+
Bob = sm_helper:connect_fresh(Config, bob, presence),
3923+
Alice = sm_helper:connect_fresh(Config, alice, sr_presence, manual),
3924+
AliceJid = escalus_client:full_jid(Alice),
3925+
BobJid = escalus_client:full_jid(Bob),
3926+
sm_helper:ack_initial_presence(Alice),
3927+
3928+
% 1. Bob sends a msg to Alice
3929+
Body = <<"OH, HAI! Msg 1">>,
3930+
escalus:send(Bob, escalus_stanza:chat_to(Alice, Body)),
3931+
mam_helper:wait_for_archive_size(Alice, 1),
3932+
3933+
% 2. Alice receives, and acknowledges
3934+
Resp = [_, _] = escalus_client:wait_for_stanzas(Alice, 2),
3935+
escalus:assert_many([fun(Msg) -> escalus_pred:is_chat_message_from_to(BobJid, AliceJid, Body, Msg) end,
3936+
fun(SMRequest) -> escalus_pred:is_sm_ack_request(SMRequest) end],
3937+
Resp),
3938+
escalus_connection:send(Alice, escalus_stanza:sm_ack(2)),
3939+
3940+
% 3. Alice disconnects abruptly
3941+
C2SPid = mongoose_helper:get_session_pid(Alice),
3942+
escalus_connection:kill(Alice),
3943+
sm_helper:wait_until_resume_session(C2SPid),
3944+
sm_helper:assert_alive_resources(Alice, 1),
3945+
3946+
% 4. Alice reconnects
3947+
NewAlice = sm_helper:connect_same(Alice, presence),
3948+
3949+
% 5. Check no new messages received
3950+
timer:sleep(timer:seconds(1)),
3951+
escalus_assert:has_no_stanzas(NewAlice),
3952+
3953+
% No new messages in MAM as well
3954+
AliceUsername = escalus_client:username(NewAlice),
3955+
AliceServer = escalus_client:server(NewAlice),
3956+
?assertEqual(1, mam_helper:archive_size(AliceServer, AliceUsername)),
3957+
3958+
escalus_connection:stop(Bob),
3959+
escalus_connection:stop(Alice).
3960+
3961+
reconnect_no_ack_different_resource(Config) ->
3962+
%% Connect Bob and Alice
3963+
Bob = sm_helper:connect_fresh(Config, bob, presence),
3964+
Spec = escalus_fresh:create_fresh_user(Config, {alice, 2}),
3965+
Alice = sm_helper:connect_spec(Spec, sr_presence, manual),
3966+
AliceJid = escalus_client:full_jid(Alice),
3967+
BobJid = escalus_client:full_jid(Bob),
3968+
sm_helper:ack_initial_presence(Alice),
3969+
3970+
% 1. Bob sends a msg to Alice
3971+
Body = <<"OH, HAI! Msg 1">>,
3972+
escalus:send(Bob, escalus_stanza:chat_to(Alice, Body)),
3973+
mam_helper:wait_for_archive_size(Alice, 1),
3974+
3975+
% 2. Alice receives, and does not acknowledge
3976+
Resp = [_, _] = escalus_client:wait_for_stanzas(Alice, 2),
3977+
escalus:assert_many([fun(Msg) -> escalus_pred:is_chat_message_from_to(BobJid, AliceJid, Body, Msg) end,
3978+
fun(SMRequest) -> escalus_pred:is_sm_ack_request(SMRequest) end],
3979+
Resp),
3980+
3981+
% 3. Alice disconnects abruptly
3982+
C2SPid = mongoose_helper:get_session_pid(Alice),
3983+
escalus_connection:kill(Alice),
3984+
sm_helper:wait_until_resume_session(C2SPid),
3985+
sm_helper:assert_alive_resources(Alice, 1),
3986+
3987+
% 4. Alice reconnects a different resource
3988+
NewAlice = sm_helper:connect_spec([{resource, <<"mam_sm_test_2nd_resource">>} | Spec], presence, manual),
3989+
3990+
% 2nd resource doesn't get the stanza, only the delayed presence.
3991+
Presence = escalus:wait_for_stanza(NewAlice),
3992+
escalus:assert(is_presence, Presence),
3993+
3994+
% 5. Check no new messages received
3995+
timer:sleep(timer:seconds(1)),
3996+
escalus_assert:has_no_stanzas(NewAlice),
3997+
3998+
% No new messages in MAM as well
3999+
AliceUsername = escalus_client:username(NewAlice),
4000+
AliceServer = escalus_client:server(NewAlice),
4001+
?assertEqual(1, mam_helper:archive_size(AliceServer, AliceUsername)),
4002+
4003+
escalus_connection:stop(Bob),
4004+
escalus_connection:stop(Alice).
4005+
38574006
%% This function supports only one device, one user.
38584007
%% We don't send initial presence to avoid presence broadcasts between resources
38594008
%% of the same user from different stories.

Diff for: src/mam/mod_mam_pm.erl

+18-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
filter_packet/3,
5050
remove_user/3,
5151
determine_amp_strategy/3,
52-
sm_filter_offline_message/3]).
52+
sm_filter_offline_message/3,
53+
filter_unacknowledged_messages/3]).
5354

5455
%% ejabberd handlers
5556
-export([process_mam_iq/5]).
@@ -273,6 +274,11 @@ get_personal_data(Acc, #{jid := ArcJID}, #{host_type := HostType}) ->
273274
Entries = mongoose_hooks:get_mam_pm_gdpr_data(HostType, ArcJID),
274275
{ok, [{mam_pm, Schema, Entries} | Acc]}.
275276

277+
-spec filter_unacknowledged_messages(Buffer :: [mongoose_acc:t()], Params :: map(), Extra :: map()) ->
278+
{ok, [mongoose_acc:t()]}.
279+
filter_unacknowledged_messages(Buffer, _, _) ->
280+
{ok, [acc_with_perm_mam_id(Acc) || Acc <- Buffer]}.
281+
276282
%% ----------------------------------------------------------------------
277283
%% Internal functions
278284

@@ -531,6 +537,15 @@ return_acc_with_mam_id_if_configured(ExtMessId, HostType, Acc) ->
531537
true -> mongoose_acc:set_permanent(mam, mam_id, ExtMessId, Acc)
532538
end.
533539

540+
-spec acc_with_perm_mam_id(Acc :: mongoose_acc:t()) -> Acc :: mongoose_acc:t().
541+
acc_with_perm_mam_id(Acc) ->
542+
case mongoose_acc:get(mam, mam_id, undefined, Acc) of
543+
undefined ->
544+
Acc;
545+
MamID ->
546+
mongoose_acc:set_permanent(mam, mam_id, MamID, Acc)
547+
end.
548+
534549
is_interesting(LocJID, RemJID) ->
535550
HostType = jid_to_host_type(LocJID),
536551
ArcID = archive_id_int(HostType, LocJID),
@@ -732,7 +747,8 @@ hooks(HostType) ->
732747
{anonymous_purge, HostType, fun ?MODULE:remove_user/3, #{}, 50},
733748
{amp_determine_strategy, HostType, fun ?MODULE:determine_amp_strategy/3, #{}, 20},
734749
{sm_filter_offline_message, HostType, fun ?MODULE:sm_filter_offline_message/3, #{}, 50},
735-
{get_personal_data, HostType, fun ?MODULE:get_personal_data/3, #{}, 50}
750+
{get_personal_data, HostType, fun ?MODULE:get_personal_data/3, #{}, 50},
751+
{filter_unacknowledged_messages, HostType, fun ?MODULE:filter_unacknowledged_messages/3, #{}, 50}
736752
].
737753

738754
add_iq_handlers(HostType, Opts) ->

Diff for: src/mongoose_acc.erl

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
% Strip with or without stanza replacement
5757
-export([strip/1, strip/2]).
5858

59-
-ignore_xref([delete/2, ref/1]).
59+
-ignore_xref([delete/2, ref/1, strip/1]).
6060

6161
%% Note about 'undefined' to_jid and from_jid: these are the special cases when JID may be
6262
%% truly unknown: before a client is authorized.

Diff for: src/stream_management/mod_stream_management.erl

+1-2
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,7 @@ maybe_notify_unacknowledged_msg(Acc, Jid) ->
352352

353353
-spec notify_unacknowledged_msg(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t().
354354
notify_unacknowledged_msg(Acc, Jid) ->
355-
NewAcc = mongoose_hooks:unacknowledged_message(Acc, Jid),
356-
mongoose_acc:strip(NewAcc).
355+
mongoose_hooks:unacknowledged_message(Acc, Jid).
357356

358357
-spec reroute_unacked_messages(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
359358
mongoose_c2s_hooks:result().

0 commit comments

Comments
 (0)