1: -module(ejabberd_sm_SUITE).
    2: -include_lib("eunit/include/eunit.hrl").
    3: -include_lib("common_test/include/ct.hrl").
    4: 
    5: -include_lib("jid/include/jid.hrl").
    6: -include_lib("session.hrl").
    7: -compile([export_all, nowarn_export_all]).
    8: 
    9: -define(eq(E, I), ?assertEqual(E, I)).
   10: 
   11: -define(B(C), (proplists:get_value(backend, C))).
   12: -define(MAX_USER_SESSIONS, 2).
   13: 
   14: -import(config_parser_helper, [default_config/1]).
   15: 
   16: all() -> [{group, mnesia}, {group, redis}].
   17: 
   18: init_per_suite(C) ->
   19:     {ok, _} = application:ensure_all_started(jid),
   20:     application:ensure_all_started(exometer_core),
   21:     F = fun() ->
   22:         ejabberd_sm_backend_sup:start_link(),
   23:         receive stop -> ok end
   24:     end,
   25:     Pid = spawn(F),
   26:     [{pid, Pid} | C].
   27: 
   28: end_per_suite(C) ->
   29:     Pid = ?config(pid, C),
   30:     Pid ! stop,
   31:     application:stop(exometer),
   32:     application:stop(exometer_core).
   33: 
   34: groups() ->
   35:     [{mnesia, [], tests()},
   36:      {redis, [], tests()}].
   37: 
   38: tests() ->
   39:     [open_session,
   40:      get_full_session_list,
   41:      get_vh_session_list,
   42:      get_sessions_2,
   43:      get_sessions_3,
   44:      session_is_updated_when_created_twice,
   45:      delete_session,
   46:      clean_up,
   47:      too_much_sessions,
   48:      unique_count,
   49:      unique_count_while_removing_entries,
   50:      session_info_is_stored,
   51:      session_info_is_updated_if_keys_match,
   52:      session_info_is_extended_if_new_keys_present,
   53:      session_info_keys_not_truncated_if_session_opened_with_empty_infolist,
   54:      kv_can_be_stored_for_session,
   55:      kv_can_be_updated_for_session,
   56:      kv_can_be_removed_for_session,
   57:      store_info_sends_message_to_the_session_owner,
   58:      remove_info_sends_message_to_the_session_owner,
   59:      cannot_reproduce_race_condition_in_store_info
   60:     ].
   61: 
   62: init_per_group(mnesia, Config) ->
   63:     ok = mnesia:create_schema([node()]),
   64:     ok = mnesia:start(),
   65:     [{backend, ejabberd_sm_mnesia} | Config];
   66: init_per_group(redis, Config) ->
   67:     init_redis_group(is_redis_running(), Config).
   68: 
   69: init_redis_group(true, Config) ->
   70:     Self = self(),
   71:     proc_lib:spawn(fun() ->
   72:                   register(test_helper, self()),
   73:                   mongoose_wpool:ensure_started(),
   74:                   % This would be started via outgoing_pools in normal case
   75:                   Pool = default_config([outgoing_pools, redis, default]),
   76:                   mongoose_wpool:start_configured_pools([Pool], []),
   77:                   Self ! ready,
   78:                   receive stop -> ok end
   79:           end),
   80:     receive ready -> ok after timer:seconds(30) -> ct:fail(test_helper_not_ready) end,
   81:     [{backend, ejabberd_sm_redis} | Config];
   82: init_redis_group(_, _) ->
   83:     {skip, "redis not running"}.
   84: 
   85: end_per_group(mnesia, Config) ->
   86:     mnesia:stop(),
   87:     mnesia:delete_schema([node()]),
   88:     Config;
   89: end_per_group(_, Config) ->
   90:     whereis(test_helper) ! stop,
   91:     Config.
   92: 
   93: init_per_testcase(too_much_sessions, Config) ->
   94:     set_test_case_meck(?MAX_USER_SESSIONS),
   95:     setup_sm(Config),
   96:     Config;
   97: init_per_testcase(_, Config) ->
   98:     set_test_case_meck(infinity),
   99:     setup_sm(Config),
  100:     Config.
  101: 
  102: end_per_testcase(_, Config) ->
  103:     clean_sessions(Config),
  104:     terminate_sm(),
  105:     unload_meck(),
  106:     unset_opts(Config).
  107: 
  108: open_session(C) ->
  109:     {Sid, USR} = generate_random_user(<<"localhost">>),
  110:     given_session_opened(Sid, USR),
  111:     verify_session_opened(C, Sid, USR).
  112: 
  113: get_full_session_list(C) ->
  114:     ManyUsers = generate_many_random_users(5, [<<"localhost">>, <<"otherhost">>]),
  115:     ManyUsersLen = length(ManyUsers),
  116:     [given_session_opened(Sid, USR) || {Sid, USR} <- ManyUsers],
  117:     AllSessions = ejabberd_sm:get_full_session_list(),
  118:     AllSessionsLen = length(AllSessions),
  119:     AllSessionsLen = ManyUsersLen,
  120:     [verify_session_opened(C, Sid, USR) || {Sid, USR} <- ManyUsers].
  121: 
  122: get_vh_session_list(C) ->
  123:     ManyUsersLocal = generate_many_random_users(5, [<<"localhost">>]),
  124:     ManyUsersOther = generate_many_random_users(5, [<<"otherhost">>]),
  125:     ManyUsersLocalLen = length(ManyUsersLocal),
  126:     [given_session_opened(Sid, USR) || {Sid, USR} <- ManyUsersLocal ++ ManyUsersOther],
  127:     LocalhostSessions = ejabberd_sm:get_vh_session_list(<<"localhost">>),
  128:     LocalhostSessionsLen = length(LocalhostSessions),
  129:     LocalhostSessionsLen = ManyUsersLocalLen,
  130:     ManyUsersLocalLen = ejabberd_sm:get_vh_session_number(<<"localhost">>),
  131:     [verify_session_opened(C, Sid, USR) || {Sid, USR} <- ManyUsersLocal].
  132: 
  133: get_sessions_2(C) ->
  134:     UsersWithManyResources = generate_many_random_res(5, 3, [<<"localhost">>, <<"otherhost">>]),
  135:     [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources],
  136:     USDict = get_unique_us_dict(UsersWithManyResources),
  137:     [verify_session_opened(C, U, S, dict:fetch({U, S}, USDict)) || {U, S} <- dict:fetch_keys(USDict)],
  138:     [verify_session_opened(C, Sid, USR) || {Sid, USR} <- UsersWithManyResources].
  139: 
  140: 
  141: get_sessions_3(C) ->
  142:     UserRes = generate_many_random_res(1, 3, [<<"localhost">>]),
  143:     AllSessions = length(UserRes),
  144:     {_, {User, Server, _}} = hd(UserRes),
  145:     [given_session_opened(Sid, USR) || {Sid, USR} <- UserRes],
  146:     Sessions_2 = ?B(C):get_sessions(User, Server),
  147:     AllSessions = length(Sessions_2),
  148:     F = fun({Sid, {U, S, R} = USR}) ->
  149:         [#session{sid = Sid} = Session] = ?B(C):get_sessions(U, S, R),
  150:         Session = lists:keyfind(Sid, #session.sid, Sessions_2),
  151:         Session = lists:keyfind(USR, #session.usr, Sessions_2),
  152:         true
  153:     end,
  154:     true = lists:all(F, UserRes).
  155: 
  156: session_is_updated_when_created_twice(C) ->
  157:     {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>),
  158:     given_session_opened(Sid, USR),
  159:     verify_session_opened(C, Sid, USR),
  160: 
  161:     given_session_opened(Sid, USR, 20),
  162:     verify_session_opened(C, Sid, USR),
  163: 
  164:     [#session{usr = USR, sid = Sid, priority = 20}] = ?B(C):get_sessions(),
  165:     [#session{usr = USR, sid = Sid, priority = 20}] = ?B(C):get_sessions(S),
  166:     [#session{priority = 20}] = ?B(C):get_sessions(U, S).
  167: 
  168: session_info_is_stored(C) ->
  169:     {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>),
  170:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  171: 
  172:     [#session{sid = Sid, info = #{key1 := val1}}]
  173:      = ?B(C):get_sessions(U,S).
  174: 
  175: session_info_is_updated_if_keys_match(C) ->
  176:     {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>),
  177:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  178: 
  179:     when_session_opened(Sid, USR, 1, [{key1, val2}]),
  180: 
  181:     [#session{sid = Sid, info = #{key1 := val2}}]
  182:      = ?B(C):get_sessions(U,S).
  183: 
  184: session_info_is_extended_if_new_keys_present(C) ->
  185:     {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>),
  186:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  187: 
  188:     when_session_opened(Sid, USR, 1, [{key1, val1}, {key2, val2}]),
  189: 
  190:     [#session{sid = Sid, info = #{key1 := val1, key2 := val2}}]
  191:      = ?B(C):get_sessions(U,S).
  192: 
  193: session_info_keys_not_truncated_if_session_opened_with_empty_infolist(C) ->
  194:     {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>),
  195:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  196: 
  197:     when_session_opened(Sid, USR, 1, []),
  198: 
  199:     [#session{sid = Sid, info = #{key1 := val1}}]
  200:      = ?B(C):get_sessions(U,S).
  201: 
  202: 
  203: kv_can_be_stored_for_session(C) ->
  204:     {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>),
  205:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  206: 
  207:     when_session_info_stored(U, S, R, {key2, newval}),
  208: 
  209:     ?assertMatch([#session{sid = Sid, info = #{key1 := val1, key2 := newval}}],
  210:                  ?B(C):get_sessions(U,S)).
  211: 
  212: kv_can_be_updated_for_session(C) ->
  213:     {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>),
  214:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  215: 
  216:     when_session_info_stored(U, S, R, {key2, newval}),
  217:     when_session_info_stored(U, S, R, {key2, override}),
  218: 
  219:     ?assertMatch([#session{sid = Sid, info = #{key1 := val1, key2 := override}}],
  220:                  ?B(C):get_sessions(U, S)).
  221: 
  222: kv_can_be_removed_for_session(C) ->
  223:     {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>),
  224:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  225: 
  226:     when_session_info_stored(U, S, R, {key2, newval}),
  227: 
  228:     [#session{sid = Sid, info = #{key1 := val1, key2 := newval}}]
  229:      = ?B(C):get_sessions(U, S),
  230: 
  231:     when_session_info_removed(U, S, R, key2),
  232: 
  233:     [#session{sid = Sid, info = #{key1 := val1}}]
  234:      = ?B(C):get_sessions(U, S),
  235: 
  236:     when_session_info_removed(U, S, R, key1),
  237: 
  238:     [#session{sid = Sid, info = #{}}]
  239:      = ?B(C):get_sessions(U, S).
  240: 
  241: cannot_reproduce_race_condition_in_store_info(C) ->
  242:     ok = try_to_reproduce_race_condition(C).
  243: 
  244: store_info_sends_message_to_the_session_owner(C) ->
  245:     SID = {erlang:system_time(microsecond), self()},
  246:     U = <<"alice2">>,
  247:     S = <<"localhost">>,
  248:     R = <<"res1">>,
  249:     Session = #session{sid = SID, usr = {U, S, R}, us = {U, S}, priority = 1, info = #{}},
  250:     %% Create session in one process
  251:     ?B(C):create_session(U, S, R, Session),
  252:     %% but call store_info from another process
  253:     JID = jid:make_noprep(U, S, R),
  254:     spawn_link(fun() -> ejabberd_sm:store_info(JID, cc, undefined) end),
  255:     %% The original process receives a message
  256:     receive {store_session_info,
  257:              #jid{luser = User, lserver = Server, lresource = Resource},
  258:              K, V, _FromPid} ->
  259:         ?eq(U, User),
  260:         ?eq(S, Server),
  261:         ?eq(R, Resource),
  262:         ?eq({cc, undefined}, {K, V}),
  263:         ok
  264:         after 5000 ->
  265:             ct:fail("store_info_sends_message_to_the_session_owner=timeout")
  266:     end.
  267: 
  268: remove_info_sends_message_to_the_session_owner(C) ->
  269:     SID = {erlang:timestamp(), self()},
  270:     U = <<"alice2">>,
  271:     S = <<"localhost">>,
  272:     R = <<"res1">>,
  273:     Session = #session{sid = SID, usr = {U, S, R}, us = {U, S}, priority = 1, info = #{}},
  274:     %% Create session in one process
  275:     ?B(C):create_session(U, S, R, Session),
  276:     %% but call remove_info from another process
  277:     JID = jid:make_noprep(U, S, R),
  278:     spawn_link(fun() -> ejabberd_sm:remove_info(JID, cc) end),
  279:     %% The original process receives a message
  280:     receive {remove_session_info,
  281:              #jid{luser = User, lserver = Server, lresource = Resource},
  282:              Key, _FromPid} ->
  283:         ?eq(U, User),
  284:         ?eq(S, Server),
  285:         ?eq(R, Resource),
  286:         ?eq(cc, Key),
  287:         ok
  288:         after 5000 ->
  289:             ct:fail("remove_info_sends_message_to_the_session_owner=timeout")
  290:     end.
  291: 
  292: delete_session(C) ->
  293:     {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>),
  294:     given_session_opened(Sid, USR),
  295:     verify_session_opened(C, Sid, USR),
  296: 
  297:     ?B(C):delete_session(Sid, U, S, R),
  298: 
  299:     [] = ?B(C):get_sessions(),
  300:     [] = ?B(C):get_sessions(S),
  301:     [] = ?B(C):get_sessions(U, S),
  302:     [] = ?B(C):get_sessions(U, S, R).
  303: 
  304: 
  305: 
  306: clean_up(C) ->
  307:     UsersWithManyResources = generate_many_random_res(5, 3, [<<"localhost">>, <<"otherhost">>]),
  308:     [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources],
  309:     ?B(C):cleanup(node()),
  310:     %% give sm backend some time to clean all sessions
  311:     ensure_empty(C, 10, ?B(C):get_sessions()).
  312: 
  313: ensure_empty(_C, 0, Sessions) ->
  314:     [] = Sessions;
  315: ensure_empty(C, N, Sessions) ->
  316:     case Sessions of
  317:         [] ->
  318:             ok;
  319:         _ ->
  320:             timer:sleep(50),
  321:             ensure_empty(C, N-1, ?B(C):get_sessions())
  322:     end.
  323: 
  324: too_much_sessions(_C) ->
  325:     %% Max sessions set to ?MAX_USER_SESSIONS in init_per_testcase
  326:     UserSessions = [generate_random_user(<<"a">>, <<"localhost">>) || _ <- lists:seq(1, ?MAX_USER_SESSIONS)],
  327:     {AddSid, AddUSR} = generate_random_user(<<"a">>, <<"localhost">>),
  328: 
  329:     [given_session_opened(Sid, USR) || {Sid, USR} <- UserSessions],
  330: 
  331:     given_session_opened(AddSid, AddUSR),
  332: 
  333:     receive
  334:         replaced ->
  335:             ok
  336:     after 10 ->
  337:         ct:fail("replaced message not sent")
  338:     end.
  339: 
  340: 
  341: 
  342: unique_count(_C) ->
  343:     UsersWithManyResources = generate_many_random_res(5, 3, [<<"localhost">>, <<"otherhost">>]),
  344:     [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources],
  345:     USDict = get_unique_us_dict(UsersWithManyResources),
  346:     UniqueCount = ejabberd_sm:get_unique_sessions_number(),
  347:     UniqueCount = dict:size(USDict).
  348: 
  349: 
  350: unique_count_while_removing_entries(C) ->
  351:     unique_count(C),
  352:     UniqueCount = ejabberd_sm:get_unique_sessions_number(),
  353:     %% Register more sessions and mock the crash
  354:     UsersWithManyResources = generate_many_random_res(10, 3, [<<"localhost">>, <<"otherhost">>]),
  355:     [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources],
  356:     set_test_case_meck_unique_count_crash(?B(C)),
  357:     USDict = get_unique_us_dict(UsersWithManyResources),
  358:     %% Check if unique count equals prev cached value
  359:     UniqueCount = ejabberd_sm:get_unique_sessions_number(),
  360:     meck:unload(?B(C)),
  361:     true = UniqueCount /= dict:size(USDict) + UniqueCount.
  362: 
  363: unload_meck() ->
  364:     meck:unload(acl),
  365:     meck:unload(gen_hook),
  366:     meck:unload(ejabberd_commands),
  367:     meck:unload(mongoose_domain_api).
  368: 
  369: set_test_case_meck(MaxUserSessions) ->
  370:     meck:new(acl, []),
  371:     meck:expect(acl, match_rule, fun(_, _, _, _) -> MaxUserSessions end),
  372:     meck:new(gen_hook, []),
  373:     meck:expect(gen_hook, run_fold, fun(_, _, Acc, _) -> {ok, Acc} end),
  374:     meck:new(mongoose_domain_api, []),
  375:     meck:expect(mongoose_domain_api, get_domain_host_type, fun(H) -> {ok, H} end).
  376: 
  377: set_test_case_meck_unique_count_crash(Backend) ->
  378:     F = get_fun_for_unique_count(Backend),
  379:     meck:new(Backend, []),
  380:     meck:expect(Backend, unique_count, F).
  381: 
  382: get_fun_for_unique_count(ejabberd_sm_mnesia) ->
  383:     fun() ->
  384:         mnesia:abort({badarg,[session,{{1442,941593,580189},list_to_pid("<0.23291.6>")}]})
  385:     end;
  386: get_fun_for_unique_count(ejabberd_sm_redis) ->
  387:     fun() ->
  388:         %% The code below is on purpose, it's to crash with badarg reason
  389:         length({error, timeout})
  390:     end.
  391: 
  392: make_sid() ->
  393:     {erlang:timestamp(), self()}.
  394: 
  395: given_session_opened(Sid, USR) ->
  396:     given_session_opened(Sid, USR, 1).
  397: 
  398: given_session_opened(Sid, {U, S, R}, Priority) ->
  399:     given_session_opened(Sid, {U, S, R}, Priority, []).
  400: 
  401: given_session_opened(Sid, {U, S, R}, Priority, Info) ->
  402:     JID = jid:make_noprep(U, S, R),
  403:     ejabberd_sm:open_session(S, Sid, JID, Priority, maps:from_list(Info)).
  404: 
  405: when_session_opened(Sid, {U, S, R}, Priority, Info) ->
  406:     given_session_opened(Sid, {U, S, R}, Priority, Info).
  407: 
  408: when_session_info_stored(U, S, R, {K, V}) ->
  409:     JID = jid:make_noprep(U, S, R),
  410:     ejabberd_sm:store_info(JID, K, V).
  411: 
  412: when_session_info_removed(U, S, R, Key) ->
  413:     JID = jid:make_noprep(U, S, R),
  414:     ejabberd_sm:remove_info(JID, Key).
  415: 
  416: verify_session_opened(C, Sid, USR) ->
  417:     do_verify_session_opened(?B(C), Sid, USR).
  418: 
  419: do_verify_session_opened(ejabberd_sm_mnesia, Sid, {U, S, R} = USR) ->
  420:     general_session_check(ejabberd_sm_mnesia, Sid, USR, U, S, R);
  421: do_verify_session_opened(ejabberd_sm_redis, Sid, {U, S, R} = USR) ->
  422:     UHash = iolist_to_binary(hash(U, S, R, Sid)),
  423:     Hashes = mongoose_redis:cmd(["SMEMBERS", n(node())]),
  424:     true = lists:member(UHash, Hashes),
  425:     SessionsUSEncoded = mongoose_redis:cmd(["SMEMBERS", hash(U, S)]),
  426:     SessionsUS = [binary_to_term(Entry) || Entry <- SessionsUSEncoded],
  427:     true = lists:keymember(Sid, 2, SessionsUS),
  428:     [SessionUSREncoded] = mongoose_redis:cmd(["SMEMBERS", hash(U, S, R)]),
  429:     SessionUSR = binary_to_term(SessionUSREncoded),
  430:     #session{sid = Sid} = SessionUSR,
  431:     general_session_check(ejabberd_sm_redis, Sid, USR, U, S, R).
  432: 
  433: verify_session_opened(C, U, S, Resources) ->
  434:     Sessions = ?B(C):get_sessions(U, S),
  435:     F = fun({Sid, USR}) ->
  436:         #session{} = Session = lists:keyfind(Sid, #session.sid, Sessions),
  437:         Session == lists:keyfind(USR, #session.usr, Sessions)
  438:     end,
  439:     true = lists:all(F, Resources).
  440: 
  441: general_session_check(M, Sid, USR, U, S, R) ->
  442:     [#session{sid = Sid, usr = USR, us = {U, S}}] = M:get_sessions(U, S, R).
  443: 
  444: clean_sessions(C) ->
  445:     case ?B(C) of
  446:         ejabberd_sm_mnesia ->
  447:             mnesia:clear_table(session);
  448:         ejabberd_sm_redis ->
  449:             mongoose_redis:cmd(["FLUSHALL"])
  450:     end.
  451: 
  452: generate_random_user(S) ->
  453:     U = base16:encode(crypto:strong_rand_bytes(5)),
  454:     generate_random_user(U, S).
  455: 
  456: generate_random_user(U, S) ->
  457:     R = base16:encode(crypto:strong_rand_bytes(5)),
  458:     generate_user(U, S, R).
  459: 
  460: generate_user(U, S, R) ->
  461:     Sid = make_sid(),
  462:     {Sid, {U, S, R}}.
  463: 
  464: generate_many_random_users(PerServerCount, Servers) ->
  465:     Users = [generate_random_users(PerServerCount, Server) || Server <- Servers],
  466:     lists:flatten(Users).
  467: 
  468: generate_random_users(Count, Server) ->
  469:     [generate_random_user(Server) || _ <- lists:seq(1, Count)].
  470: 
  471: generate_many_random_res(UsersPerServer, ResourcesPerUser, Servers) ->
  472:     Usernames = [base16:encode(crypto:strong_rand_bytes(5)) || _ <- lists:seq(1, UsersPerServer)],
  473:     [generate_random_user(U, S) || U <- Usernames, S <- Servers, _ <- lists:seq(1, ResourcesPerUser)].
  474: 
  475: get_unique_us_dict(USRs) ->
  476:     F = fun({_, {U, S, _}} = I, SetAcc) ->
  477:         dict:append({U, S}, I, SetAcc)
  478:     end,
  479:     lists:foldl(F, dict:new(), USRs).
  480: 
  481: %% Taken from ejabberd_sm_redis
  482: 
  483: -spec hash(binary()) -> iolist().
  484: hash(Val1) ->
  485:     ["s3:*:", Val1, ":*"].
  486: 
  487: 
  488: -spec hash(binary(), binary()) -> iolist().
  489: hash(Val1, Val2) ->
  490:     ["s2:", Val1, ":", Val2].
  491: 
  492: 
  493: -spec hash(binary(), binary(), binary()) -> iolist().
  494: hash(Val1, Val2, Val3) ->
  495:     ["s3:", Val1, ":", Val2, ":", Val3].
  496: 
  497: 
  498: -spec hash(binary(), binary(), binary(), binary()) -> iolist().
  499: hash(Val1, Val2, Val3, Val4) ->
  500:     ["s4:", Val1, ":", Val2, ":", Val3, ":", term_to_binary(Val4)].
  501: 
  502: 
  503: -spec n(atom()) -> iolist().
  504: n(Node) ->
  505:     ["n:", atom_to_list(Node)].
  506: 
  507: 
  508: is_redis_running() ->
  509:     case eredis:start_link() of
  510:         {ok, Client} ->
  511:             Result = eredis:q(Client, [<<"PING">>], 5000),
  512:             eredis:stop(Client),
  513:             case Result of
  514:                 {ok,<<"PONG">>} ->
  515:                     true;
  516:                 _ ->
  517:                     false
  518:             end;
  519:         _ ->
  520:             false
  521:     end.
  522: 
  523: try_to_reproduce_race_condition(Config) ->
  524:     SID = {erlang:timestamp(), self()},
  525:     U = <<"alice">>,
  526:     S = <<"localhost">>,
  527:     R = <<"res1">>,
  528:     Session = #session{sid = SID, usr = {U, S, R}, us = {U, S}, priority = 1, info = #{}},
  529:     ?B(Config):create_session(U, S, R, Session),
  530:     Parent = self(),
  531:     %% Add some instrumentation to simulate race conditions
  532:     %% The goal is to delete the session after other process reads it
  533:     %% but before it updates it. In other words, delete a record
  534:     %% between get_sessions and create_session in ejabberd_sm:store_info
  535:     %% Step1 prepare concurrent processes
  536:     DeleterPid = spawn_link(fun() ->
  537:                                     receive start -> ok end,
  538:                                     ?B(Config):delete_session(SID, U, S, R),
  539:                                     Parent ! p1_done
  540:                             end),
  541:     SetterPid = spawn_link(fun() ->
  542:                                    receive start -> ok end,
  543:                                    when_session_info_stored(U, S, R, {cc, undefined}),
  544:                                    Parent ! p2_done
  545:                            end),
  546:     %% Step2 setup mocking for some ejabbers_sm_mnesia functions
  547:     meck:new(?B(Config), []),
  548:     %% When the first get_sessions (run from ejabberd_sm:store_info)
  549:     %% is executed, the start msg is sent to Deleter process
  550:     %% Thanks to that, setter will get not empty list of sessions
  551:     PassThrough3 = fun(A, B, C) ->
  552:                            DeleterPid ! start,
  553:                            meck:passthrough([A, B, C]) end,
  554:     meck:expect(?B(Config), get_sessions, PassThrough3),
  555:     %% Wait some time before setting the sessions
  556:     %% so we are sure delete operation finishes
  557:     meck:expect(?B(Config), create_session,
  558:                 fun(U1, S1, R1, Session1) ->
  559:                         timer:sleep(100),
  560:                         meck:passthrough([U1, S1, R1, Session1])
  561:                 end),
  562:     PassThrough4 = fun(A, B, C, D) -> meck:passthrough([A, B, C, D]) end,
  563:     meck:expect(?B(Config), delete_session, PassThrough4),
  564:     %% Start the play from setter process
  565:     SetterPid ! start,
  566:     %% Wait for both process to finish
  567:     receive p1_done -> ok end,
  568:     receive p2_done -> ok end,
  569:     meck:unload(?B(Config)),
  570:     %% Session should not exist
  571:     case ?B(Config):get_sessions(U, S, R) of
  572:         [] ->
  573:             ok;
  574:         Other ->
  575:             error_logger:error_msg("issue=reproduced, sid=~p, other=~1000p",
  576:                                    [SID, Other]),
  577:             {error, reproduced}
  578:     end.
  579: 
  580: setup_sm(Config) ->
  581:     set_opts(Config),
  582:     set_meck(),
  583:     ejabberd_sm:start_link(),
  584:     case ?config(backend, Config) of
  585:         ejabberd_sm_redis ->
  586:             mongoose_redis:cmd(["FLUSHALL"]);
  587:         ejabberd_sm_mnesia ->
  588:             ok
  589:     end.
  590: 
  591: terminate_sm() ->
  592:     gen_server:stop(ejabberd_sm).
  593: 
  594: set_opts(Config) ->
  595:     [mongoose_config:set_opt(Key, Value) || {Key, Value} <- opts(Config)].
  596: 
  597: unset_opts(Config) ->
  598:     [mongoose_config:unset_opt(Key) || {Key, _Value} <- opts(Config)].
  599: 
  600: opts(Config) ->
  601:     [{hosts, [<<"localhost">>]},
  602:      {host_types, []},
  603:      {all_metrics_are_global, false},
  604:      {sm_backend, sm_backend(?config(backend, Config))}].
  605: 
  606: sm_backend(ejabberd_sm_redis) -> redis;
  607: sm_backend(ejabberd_sm_mnesia) -> mnesia.
  608: 
  609: set_meck() ->
  610:     meck:expect(gen_hook, add_handler, fun(_, _, _, _, _) -> ok end),
  611:     meck:new(ejabberd_commands, []),
  612:     meck:expect(ejabberd_commands, register_commands, fun(_) -> ok end),
  613:     meck:expect(ejabberd_commands, unregister_commands, fun(_) -> ok end),
  614:     ok.