1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2010-2013. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: 
   20: %%
   21: %% Stress tests of rwmutex implementation.
   22: %%
   23: %% Author: Rickard Green
   24: %%
   25: -module(mtx_SUITE).
   26: 
   27: %%-define(line_trace,true).
   28: 
   29: -include_lib("common_test/include/ct.hrl").
   30: 
   31: -export([all/0,suite/0,groups/0,
   32: 	 init_per_group/2,end_per_group/2, init_per_suite/1, 
   33: 	 end_per_suite/1, init_per_testcase/2, end_per_testcase/2]).
   34: 
   35: -export([long_rwlock/1,
   36: 	 hammer_ets_rwlock/1,
   37: 	 hammer_rwlock/1,
   38: 	 hammer_rwlock_check/1,
   39: 	 hammer_tryrwlock/1,
   40: 	 hammer_tryrwlock_check/1,
   41: 	 hammer_sched_long_rwlock/1,
   42: 	 hammer_sched_long_rwlock_check/1,
   43: 	 hammer_sched_long_freqread_rwlock/1,
   44: 	 hammer_sched_long_freqread_rwlock_check/1,
   45: 	 hammer_sched_long_tryrwlock/1,
   46: 	 hammer_sched_long_tryrwlock_check/1,
   47: 	 hammer_sched_long_freqread_tryrwlock/1,
   48: 	 hammer_sched_long_freqread_tryrwlock_check/1,
   49: 	 hammer_sched_rwlock/1,
   50: 	 hammer_sched_rwlock_check/1,
   51: 	 hammer_sched_freqread_rwlock/1,
   52: 	 hammer_sched_freqread_rwlock_check/1,
   53: 	 hammer_sched_tryrwlock/1,
   54: 	 hammer_sched_tryrwlock_check/1,
   55: 	 hammer_sched_freqread_tryrwlock/1,
   56: 	 hammer_sched_freqread_tryrwlock_check/1]).
   57: 
   58: init_per_suite(Config) when is_list(Config) ->
   59:     DataDir = ?config(data_dir, Config),
   60:     Lib = filename:join([DataDir, atom_to_list(?MODULE)]),
   61:     case {erlang:load_nif(Lib, none),erlang:system_info(threads)} of
   62: 	{{error,_},false} ->
   63: 	    {skip, "No thread support"};
   64: 	_ ->
   65: 	    Config
   66:     end.
   67: 
   68: end_per_suite(Config) when is_list(Config) ->
   69:     catch erts_debug:set_internal_state(available_internal_state, false),
   70:     Config.
   71: 
   72: init_per_testcase(_Case, Config) ->
   73:     Dog = ?t:timetrap(?t:minutes(15)),
   74:     %% Wait for deallocations to complete since we measure
   75:     %% runtime in test cases.
   76:     wait_deallocations(),
   77:     [{watchdog, Dog}|Config].
   78: 
   79: end_per_testcase(_Func, Config) ->
   80:     Dog = ?config(watchdog, Config),
   81:     ?t:timetrap_cancel(Dog).
   82: 
   83: wait_deallocations() ->
   84:     try
   85: 	erts_debug:set_internal_state(wait, deallocations)
   86:     catch
   87: 	error:undef ->
   88: 	    erts_debug:set_internal_state(available_internal_state, true),
   89: 	    wait_deallocations()
   90:     end.
   91: 
   92: suite() -> [{ct_hooks,[ts_install_cth]}].
   93: 
   94: all() -> 
   95:     [long_rwlock, hammer_rwlock_check, hammer_rwlock,
   96:      hammer_tryrwlock_check, hammer_tryrwlock,
   97:      hammer_ets_rwlock, hammer_sched_long_rwlock_check,
   98:      hammer_sched_long_rwlock,
   99:      hammer_sched_long_freqread_rwlock_check,
  100:      hammer_sched_long_freqread_rwlock,
  101:      hammer_sched_long_tryrwlock_check,
  102:      hammer_sched_long_tryrwlock,
  103:      hammer_sched_long_freqread_tryrwlock_check,
  104:      hammer_sched_long_freqread_tryrwlock,
  105:      hammer_sched_rwlock_check, hammer_sched_rwlock,
  106:      hammer_sched_freqread_rwlock_check,
  107:      hammer_sched_freqread_rwlock,
  108:      hammer_sched_tryrwlock_check, hammer_sched_tryrwlock,
  109:      hammer_sched_freqread_tryrwlock_check,
  110:      hammer_sched_freqread_tryrwlock].
  111: 
  112: groups() -> 
  113:     [].
  114: 
  115: init_per_group(_GroupName, Config) ->
  116: 	Config.
  117: 
  118: end_per_group(_GroupName, Config) ->
  119: 	Config.
  120: 
  121: 
  122: long_rwlock(Config) when is_list(Config) ->
  123:     statistics(runtime),
  124:     LLRes = long_rw_test(),
  125:     {_, RunTime} = statistics(runtime),
  126:     %% A very short run time is expected, since
  127:     %% threads in the test mostly wait
  128:     ?t:format("RunTime=~p~n", [RunTime]),
  129:     ?line true = RunTime < 400,
  130:     ?line RunTimeStr = "Run-time during test was "++integer_to_list(RunTime)++" ms.",
  131:     case LLRes of
  132: 	ok ->
  133: 	    {comment, RunTimeStr};
  134: 	{comment, Comment} ->
  135: 	    {comment, Comment ++ " " ++ RunTimeStr}
  136:     end.
  137: 
  138: hammer_rwlock(Config) when is_list(Config) ->
  139:     hammer_rw_test(false).
  140: 
  141: hammer_rwlock_check(Config) when is_list(Config) ->
  142:     hammer_rw_test(true).
  143: 
  144: hammer_tryrwlock(Config) when is_list(Config) ->
  145:     hammer_tryrw_test(false).
  146: 
  147: hammer_tryrwlock_check(Config) when is_list(Config) ->
  148:     hammer_tryrw_test(true).
  149: 
  150: hammer_sched_rwlock(Config) when is_list(Config) ->
  151:     hammer_sched_rwlock_test(false, false, true, 0, 0).
  152: 
  153: hammer_sched_rwlock_check(Config) when is_list(Config) ->
  154:     hammer_sched_rwlock_test(false, true, true, 0, 0).
  155: 
  156: hammer_sched_freqread_rwlock(Config) when is_list(Config) ->
  157:     hammer_sched_rwlock_test(true, false, true, 0, 0).
  158: 
  159: hammer_sched_freqread_rwlock_check(Config) when is_list(Config) ->
  160:     hammer_sched_rwlock_test(true, true, true, 0, 0).
  161: 
  162: hammer_sched_tryrwlock(Config) when is_list(Config) ->
  163:     hammer_sched_rwlock_test(false, false, false, 0, 100).
  164: 
  165: hammer_sched_tryrwlock_check(Config) when is_list(Config) ->
  166:     hammer_sched_rwlock_test(false, true, false, 0, 100).
  167: 
  168: hammer_sched_freqread_tryrwlock(Config) when is_list(Config) ->
  169:     hammer_sched_rwlock_test(true, false, false, 0, 100).
  170: 
  171: hammer_sched_freqread_tryrwlock_check(Config) when is_list(Config) ->
  172:     hammer_sched_rwlock_test(true, true, false, 0, 100).
  173: 
  174: hammer_sched_long_rwlock(Config) when is_list(Config) ->
  175:     hammer_sched_rwlock_test(false, false, true, 100, 0).
  176: 
  177: hammer_sched_long_rwlock_check(Config) when is_list(Config) ->
  178:     hammer_sched_rwlock_test(false, true, true, 100, 0).
  179: 
  180: hammer_sched_long_freqread_rwlock(Config) when is_list(Config) ->
  181:     hammer_sched_rwlock_test(true, false, true, 100, 0).
  182: 
  183: hammer_sched_long_freqread_rwlock_check(Config) when is_list(Config) ->
  184:     hammer_sched_rwlock_test(true, true, true, 100, 0).
  185: 
  186: hammer_sched_long_tryrwlock(Config) when is_list(Config) ->
  187:     hammer_sched_rwlock_test(false, false, false, 100, 100).
  188: 
  189: hammer_sched_long_tryrwlock_check(Config) when is_list(Config) ->
  190:     hammer_sched_rwlock_test(false, true, false, 100, 100).
  191: 
  192: hammer_sched_long_freqread_tryrwlock(Config) when is_list(Config) ->
  193:     hammer_sched_rwlock_test(true, false, false, 100, 100).
  194: 
  195: hammer_sched_long_freqread_tryrwlock_check(Config) when is_list(Config) ->
  196:     hammer_sched_rwlock_test(true, true, false, 100, 100).
  197: 
  198: hammer_sched_rwlock_test(FreqRead, LockCheck, Blocking, WaitLocked, WaitUnlocked) ->
  199:     case create_rwlock(FreqRead, LockCheck) of
  200: 	enotsup ->
  201: 	    {skipped, "Not supported."};
  202: 	RWLock ->
  203: 	    Onln = erlang:system_info(schedulers_online),
  204: 	    NWPs = case Onln div 3 of
  205: 		       1 -> case Onln < 4 of
  206: 				true -> 1;
  207: 				false -> 2
  208: 			    end;
  209: 		       X -> X
  210: 		   end,
  211: 	    NRPs = Onln - NWPs,
  212: 	    NoLockOps = ((((50000000 div Onln)
  213: 			       div case {Blocking, WaitLocked} of
  214: 				       {false, 0} -> 1;
  215: 				       _ -> 10
  216: 				   end)
  217: 			      div (case WaitLocked == 0 of
  218: 				       true -> 1;
  219: 				       false -> WaitLocked*250
  220: 				   end))
  221: 			     div handicap()),
  222: 	    ?t:format("NoLockOps=~p~n", [NoLockOps]),
  223: 	    Sleep = case Blocking of
  224: 			true -> NoLockOps;
  225: 			false -> NoLockOps div 10
  226: 		    end,
  227: 	    WPs = lists:map(
  228: 		    fun (Sched) ->
  229: 			    spawn_opt(
  230: 			      fun () ->
  231: 				      io:format("Writer on scheduler ~p.~n",
  232: 						[Sched]),
  233: 				      Sched = erlang:system_info(scheduler_id),
  234: 				      receive go -> gone end,
  235: 				      hammer_sched_rwlock_proc(RWLock,
  236: 							       Blocking,
  237: 							       true,
  238: 							       WaitLocked,
  239: 							       WaitUnlocked,
  240: 							       NoLockOps,
  241: 							       Sleep),
  242: 				      Sched = erlang:system_info(scheduler_id)
  243: 			      end,
  244: 			      [link, {scheduler, Sched}])
  245: 		    end,
  246: 		    lists:seq(1, NWPs)),
  247: 	    RPs = lists:map(
  248: 		    fun (Sched) ->
  249: 			    spawn_opt(
  250: 			      fun () ->
  251: 				      io:format("Reader on scheduler ~p.~n",
  252: 						[Sched]),
  253: 				      Sched = erlang:system_info(scheduler_id),
  254: 				      receive go -> gone end,
  255: 				      hammer_sched_rwlock_proc(RWLock,
  256: 							       Blocking,
  257: 							       false,
  258: 							       WaitLocked,
  259: 							       WaitUnlocked,
  260: 							       NoLockOps,
  261: 							       Sleep),
  262: 				      Sched = erlang:system_info(scheduler_id)
  263: 			      end,
  264: 			      [link, {scheduler, Sched}])
  265: 		    end,
  266: 		    lists:seq(NWPs + 1, NWPs + NRPs)),
  267: 	    Procs = WPs ++ RPs,
  268: 	    case {Blocking, WaitLocked} of
  269: 		{_, 0} -> ok;
  270: 		{false, _} -> ok;
  271: 		_ -> statistics(runtime)
  272: 	    end,
  273: 	    lists:foreach(fun (P) -> P ! go end, Procs),
  274: 	    lists:foreach(fun (P) ->
  275: 				  M = erlang:monitor(process, P),
  276: 				  receive
  277: 				      {'DOWN', M, process, P, _} ->
  278: 					  ok
  279: 				  end
  280: 			  end,
  281: 			  Procs),
  282: 	    case {Blocking, WaitLocked} of
  283: 		{_, 0} -> ok;
  284: 		{false, _} -> ok;
  285: 		_ ->
  286: 		    {_, RunTime} = statistics(runtime),
  287: 		    ?t:format("RunTime=~p~n", [RunTime]),
  288: 		    ?line true = RunTime < 700,
  289: 		    {comment,
  290: 		     "Run-time during test was "
  291: 		     ++ integer_to_list(RunTime)
  292: 		     ++ " ms."}
  293: 	    end
  294:     end.
  295: 
  296: hammer_sched_rwlock_proc(_RWLock,
  297: 			 _Blocking,
  298: 			 _WriteOp,
  299: 			 _WaitLocked,
  300: 			 _WaitUnlocked,
  301: 			 0,
  302: 			 _Sleep) ->
  303:     ok;
  304: hammer_sched_rwlock_proc(RWLock,
  305: 			 Blocking,
  306: 			 WriteOp,
  307: 			 WaitLocked,
  308: 			 WaitUnlocked,
  309: 			 Times,
  310: 			 Sleep) when Times rem Sleep == 0 ->
  311:     rwlock_op(RWLock, Blocking, WriteOp, WaitLocked, WaitUnlocked),
  312:     hammer_sched_rwlock_proc(RWLock,
  313: 			     Blocking,
  314: 			     WriteOp,
  315: 			     WaitLocked,
  316: 			     WaitUnlocked,
  317: 			     Times - 1,
  318: 			     Sleep);
  319: hammer_sched_rwlock_proc(RWLock,
  320: 			 Blocking,
  321: 			 WriteOp,
  322: 			 WaitLocked,
  323: 			 WaitUnlocked,
  324: 			 Times,
  325: 			 Sleep) ->
  326:     rwlock_op(RWLock, Blocking, WriteOp, WaitLocked, 0),
  327:     hammer_sched_rwlock_proc(RWLock,
  328: 			     Blocking,
  329: 			     WriteOp,
  330: 			     WaitLocked,
  331: 			     WaitUnlocked,
  332: 			     Times - 1,
  333: 			     Sleep).
  334: 
  335: -define(HAMMER_ETS_RWLOCK_REPEAT_TIMES, 1).
  336: -define(HAMMER_ETS_RWLOCK_TSIZE, 500).
  337: 
  338: hammer_ets_rwlock(Config) when is_list(Config) ->
  339:     {Ops, Procs} = case handicap() of
  340: 		       1 -> {20000, 500};
  341: 		       2 -> {20000, 50};
  342: 		       3 -> {2000, 50};
  343: 		       _ -> {200, 50}
  344: 		   end,
  345:     ?t:format("Procs=~p~nOps=~p~n", [Procs, Ops]),
  346:     lists:foreach(fun (XOpts) ->
  347: 			  ?t:format("Running with extra opts: ~p", [XOpts]),
  348: 			  hammer_ets_rwlock_test(XOpts, true, 2, Ops,
  349: 						 Procs, false)
  350: 		  end,
  351: 		  [[],
  352: 		   [{read_concurrency, true}],
  353: 		   [{write_concurrency, true}],
  354: 		   [{read_concurrency, true},{write_concurrency, true}]]),
  355:     ok.
  356: 
  357: %% Aux funcs
  358: 
  359: long_rw_test() ->
  360:     exit(no_nif_implementation).
  361: 
  362: hammer_rw_test(_Arg) ->
  363:     exit(no_nif_implementation).
  364: 
  365: hammer_tryrw_test(_Arg) ->
  366:     exit(no_nif_implementation).
  367: 
  368: create_rwlock(_FreqRead, _LockCheck) ->
  369:     exit(no_nif_implementation).
  370: 
  371: rwlock_op(_RWLock, _Blocking, _WriteOp, _WaitLocked, _WaitUnlocked) ->
  372:     exit(no_nif_implementation).
  373: 
  374: hammer_ets_rwlock_put_data() ->
  375:     put(?MODULE, {"here are some", data, "to store", make_ref()}).
  376: 
  377: hammer_ets_rwlock_get_data() ->
  378:     get(?MODULE).
  379: 
  380: hammer_ets_rwlock_ops(_T, _UW, _N, _C, _SC, 0) ->
  381:     ok;
  382: hammer_ets_rwlock_ops(T, UW, N, C, SC, Tot) when N >= ?HAMMER_ETS_RWLOCK_TSIZE ->
  383:     hammer_ets_rwlock_ops(T, UW, 0, C, SC, Tot);
  384: hammer_ets_rwlock_ops(T, UW, N, 0, SC, Tot) ->
  385:     case UW of
  386: 	true ->
  387: 	    true = ets:insert(T, {N, Tot, hammer_ets_rwlock_get_data()});
  388: 	false ->
  389: 	    [{N, _, _}] = ets:lookup(T, N)
  390:     end,
  391:     hammer_ets_rwlock_ops(T, UW, N+1, SC, SC, Tot-1);
  392: hammer_ets_rwlock_ops(T, UW, N, C, SC, Tot) ->
  393:     case UW of
  394: 	false ->
  395: 	    true = ets:insert(T, {N, Tot, hammer_ets_rwlock_get_data()});
  396: 	true ->
  397: 	    [{N, _, _}] = ets:lookup(T, N)
  398:     end,
  399:     hammer_ets_rwlock_ops(T, UW, N+1, C-1, SC, Tot-1).
  400: 
  401: hammer_ets_rwlock_init(T, N) when N < ?HAMMER_ETS_RWLOCK_TSIZE ->
  402:     ets:insert(T, {N, N, N}),
  403:     hammer_ets_rwlock_init(T, N+1);
  404: hammer_ets_rwlock_init(_T, _N) ->
  405:     ok.
  406: 
  407: hammer_ets_rwlock_test(XOpts, UW, C, N, NP, SC) ->
  408:     receive after 100 -> ok end,
  409:     {TP, TM} = spawn_monitor(
  410: 		 fun () ->
  411: 			 _L = repeat_list(
  412: 				fun () ->
  413: 					Caller = self(),
  414: 					T = fun () ->
  415: 						    Parent = self(),
  416: 						    hammer_ets_rwlock_put_data(),
  417: 						    T=ets:new(x, [public | XOpts]),
  418: 						    hammer_ets_rwlock_init(T, 0),
  419: 						    Ps0 = repeat_list(
  420: 							    fun () ->
  421: 								    spawn_link(
  422: 								      fun () ->
  423: 									      hammer_ets_rwlock_put_data(),
  424: 									      receive go -> ok end,
  425: 									      hammer_ets_rwlock_ops(T, UW, N, C, C, N),
  426: 									      Parent ! {done, self()},
  427: 									      receive after infinity -> ok end
  428: 								      end)
  429: 							    end,
  430: 							    NP - case SC of
  431: 								     false -> 0;
  432: 								     _ -> 1
  433: 								 end),
  434: 						    Ps = case SC of
  435: 							     false -> Ps0;
  436: 							     _ -> [spawn_link(fun () ->
  437: 										      hammer_ets_rwlock_put_data(),
  438: 										      receive go -> ok end,
  439: 										      hammer_ets_rwlock_ops(T, UW, N, SC, SC, N),
  440: 										      Parent ! {done, self()},
  441: 										      receive after infinity -> ok end
  442: 									      end) | Ps0]
  443: 							 end,
  444: 						    Start = now(),
  445: 						    lists:foreach(fun (P) -> P ! go end, Ps),
  446: 						    lists:foreach(fun (P) -> receive {done, P} -> ok end end, Ps),
  447: 						    Stop = now(),
  448: 						    lists:foreach(fun (P) ->
  449: 									  unlink(P),
  450: 									  exit(P, bang),
  451: 									  M = erlang:monitor(process, P),
  452: 									  receive
  453: 									      {'DOWN', M, process, P, _} -> ok
  454: 									  end
  455: 								  end, Ps),
  456: 						    Res = timer:now_diff(Stop, Start)/1000000,
  457: 						    Caller ! {?MODULE, self(), Res}
  458: 					    end,
  459: 					TP = spawn_link(T),
  460: 					receive
  461: 					    {?MODULE, TP, Res} ->
  462: 						Res
  463: 					end
  464: 				end,
  465: 				?HAMMER_ETS_RWLOCK_REPEAT_TIMES)
  466: 		 end),
  467:     receive
  468: 	{'DOWN', TM, process, TP, _} -> ok
  469:     end.
  470: 
  471: repeat_list(Fun, N) ->
  472:     repeat_list(Fun, N, []).
  473: 
  474: repeat_list(_Fun, 0, Acc) ->
  475:     Acc;
  476: repeat_list(Fun, N, Acc) ->
  477:     repeat_list(Fun, N-1, [Fun()|Acc]).
  478: 
  479: 
  480: handicap() ->
  481:     X0 = case catch (erlang:system_info(logical_processors_available) >=
  482: 			 erlang:system_info(schedulers_online)) of
  483: 	     true -> 1;
  484: 	     _ -> 2
  485: 	 end,
  486:     case erlang:system_info(build_type) of
  487: 	opt ->
  488: 	    X0;
  489: 	ReallySlow when ReallySlow == debug;
  490: 			ReallySlow == valgrind;
  491: 			ReallySlow == purify ->
  492: 	    X0*3;
  493: 	_Slow ->
  494: 	    X0*2
  495:     end.
  496: