1: %%
    2: %% %CopyrightBegin%
    3: %% 
    4: %% Copyright Ericsson AB 2003-2012. 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: -module(hibernate_SUITE).
   21: 
   22: -include_lib("test_server/include/test_server.hrl").
   23: 
   24: -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
   25: 	 init_per_group/2,end_per_group/2,
   26: 	 init_per_testcase/2,end_per_testcase/2,
   27: 	 basic/1,dynamic_call/1,min_heap_size/1,bad_args/1,
   28: 	 messages_in_queue/1,undefined_mfa/1,no_heap/1,wake_up_and_bif_trap/1]).
   29: 
   30: %% Used by test cases.
   31: -export([basic_hibernator/1,dynamic_call_hibernator/2,messages_in_queue_restart/2, no_heap_loop/0,characters_to_list_trap/1]).
   32: 
   33: suite() -> [{ct_hooks,[ts_install_cth]}].
   34: 
   35: all() -> 
   36:     [basic, dynamic_call, min_heap_size, bad_args, messages_in_queue,
   37:      undefined_mfa, no_heap, wake_up_and_bif_trap].
   38: 
   39: groups() -> 
   40:     [].
   41: 
   42: init_per_suite(Config) ->
   43:     Config.
   44: 
   45: end_per_suite(_Config) ->
   46:     ok.
   47: 
   48: init_per_group(_GroupName, Config) ->
   49:     Config.
   50: 
   51: end_per_group(_GroupName, Config) ->
   52:     Config.
   53: 
   54: 
   55: init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
   56:     Dog = ?t:timetrap(?t:minutes(3)),
   57:     [{watchdog,Dog}|Config].
   58: 
   59: end_per_testcase(_Func, Config) ->
   60:     Dog=?config(watchdog, Config),
   61:     ?t:timetrap_cancel(Dog).
   62: 
   63: %%%
   64: %%% Testing the basic functionality of erlang:hibernate/3.
   65: %%%
   66: 
   67: basic(Config) when is_list(Config) ->
   68:     Ref = make_ref(),
   69:     Info = {self(),Ref},
   70:     ExpectedHeapSz = erts_debug:size([Info]),
   71:     ?line Child = spawn_link(fun() -> basic_hibernator(Info) end),
   72:     ?line hibernate_wake_up(100, ExpectedHeapSz, Child),
   73:     ?line Child ! please_quit_now,
   74:     ok.
   75: 
   76: hibernate_wake_up(0, _, _) -> ok;
   77: hibernate_wake_up(N, ExpectedHeapSz, Child) ->
   78:     {heap_size,Before} = process_info(Child, heap_size),
   79:     case N rem 2 of
   80: 	0 ->
   81: 	    Child ! {acquire_old_heap,self()},
   82: 	    receive
   83: 		done -> ok
   84: 	    end;
   85: 	1 -> ok
   86:     end,
   87:     ?line Child ! {hibernate,self()},
   88:     ?line wait_until(fun () ->
   89: 			     {current_function,{erlang,hibernate,3}} ==
   90: 				 process_info(Child, current_function)
   91: 		     end),
   92:     ?line {message_queue_len,0} = process_info(Child, message_queue_len),
   93:     ?line {status,waiting} = process_info(Child, status),
   94:     ?line {heap_size,ExpectedHeapSz} = process_info(Child, heap_size),
   95:     io:format("Before hibernation: ~p  After hibernation: ~p\n",
   96: 	      [Before,ExpectedHeapSz]),
   97:     ?line Child ! {whats_up,self()},
   98:     ?line receive
   99: 	      {all_fine,X,Child,_Ref} ->
  100: 		  if
  101: 		      N =:= 1 -> io:format("~p\n", [X]);
  102: 		      true -> ok
  103: 		  end,
  104: 		  {backtrace,Bin} = process_info(Child, backtrace),
  105: 		  if
  106: 		      size(Bin) > 1000 ->
  107: 			  io:format("~s\n", [binary_to_list(Bin)]),
  108: 			  ?line ?t:fail(stack_is_growing);
  109: 		      true ->
  110: 			  hibernate_wake_up(N-1, ExpectedHeapSz, Child)
  111: 		  end;
  112: 	      Other ->
  113: 		  ?line io:format("~p\n", [Other]),
  114: 		  ?line ?t:fail(unexpected_message)
  115: 	  end.
  116: 
  117: basic_hibernator(Info) ->
  118:     {catchlevel,0} = process_info(self(), catchlevel),
  119:     receive
  120: 	Any ->
  121: 	    basic_hibernator_msg(Any, Info),
  122: 	    basic_hibernator(Info)
  123:     end.
  124: 
  125: basic_hibernator_msg({hibernate,_}, Info) ->
  126:     catch erlang:hibernate(?MODULE, basic_hibernator, [Info]),
  127:     exit(hibernate_returned);
  128: basic_hibernator_msg({acquire_old_heap,Parent}, _) ->
  129:     acquire_old_heap(),
  130:     Parent ! done;
  131: basic_hibernator_msg({whats_up,Parent}, {Parent,Ref}) ->
  132:     {heap_size,HeapSize} = process_info(self(), heap_size),
  133:     io:format("Heap size after waking up: ~p\n", [HeapSize]),
  134:     X = whats_up_calc(5000, 2, math:pi(), 4, 5, 6, 7, 8.5, 9, []),
  135:     Parent ! {all_fine,X,self(),Ref};
  136: basic_hibernator_msg(please_quit_now, _) ->
  137:     exit(normal);
  138: basic_hibernator_msg(Other, _) ->
  139:     exit({unexpected,Other}).
  140: 
  141: acquire_old_heap() ->
  142:     case process_info(self(), [heap_size,total_heap_size]) of
  143: 	[{heap_size,Sz},{total_heap_size,Total}] when Sz < Total ->
  144: 	    ok;
  145: 	_ ->
  146: 	    acquire_old_heap()
  147:     end.
  148: 
  149: %% The point with this calculation is to force memory to be
  150: %% allocated for the argument registers in the process structure.
  151: %% The allocation will be forced if the process is scheduled out
  152: %% while calling a function with more than 6 arguments.
  153: whats_up_calc(0, A2, A3, A4, A5, A6, A7, A8, A9, Acc) ->
  154:     {Acc,A2+A3+A4+A5+A6+A7+A8+A9};
  155: whats_up_calc(A1, A2, A3, A4, A5, A6, A7, A8, A9, Acc) ->
  156:     whats_up_calc(A1-1, A2+1, A3+2, A4+3, A5+4, A6+5, A7+6, A8+7, A9+8, [A1,A2|Acc]).
  157: 
  158: %%%
  159: %%% Testing a call to erlang:hibernate/3 that the compiler and loader do not
  160: %%% translate to an instruction.
  161: %%%
  162: 
  163: dynamic_call(Config) when is_list(Config) ->
  164:     Ref = make_ref(),
  165:     Info = {self(),Ref},
  166:     ExpectedHeapSz = erts_debug:size([Info]),
  167:     ?line Child = spawn_link(fun() -> ?MODULE:dynamic_call_hibernator(Info, hibernate) end),
  168:     ?line hibernate_wake_up(100, ExpectedHeapSz, Child),
  169:     ?line Child ! please_quit_now,
  170:     ok.
  171: 
  172: dynamic_call_hibernator(Info, Function) ->
  173:     {catchlevel,0} = process_info(self(), catchlevel),
  174:     receive
  175: 	Any ->
  176: 	    dynamic_call_hibernator_msg(Any, Function, Info),
  177: 	    dynamic_call_hibernator(Info, Function)
  178:     end.
  179: 
  180: dynamic_call_hibernator_msg({hibernate,_}, Function, Info) ->
  181:     catch apply(erlang, Function, [?MODULE, basic_hibernator, [Info]]),
  182:     exit(hibernate_returned);
  183: dynamic_call_hibernator_msg(Msg, _Function, Info) ->
  184:     basic_hibernator_msg(Msg, Info).
  185: 
  186: %%%
  187: %%% Testing setting the minimum heap size.
  188: %%%
  189: 
  190: min_heap_size(Config) when is_list(Config) ->
  191:     case test_server:is_native(?MODULE) of
  192: 	true -> {skip, "Test case relies on trace which is not available in HiPE"};
  193: 	false -> min_heap_size_1(Config)
  194:     end.
  195: 
  196: min_heap_size_1(Config) when is_list(Config) ->
  197:     ?line erlang:trace(new, true, [call]),
  198:     MFA = {?MODULE,min_hibernator,1},
  199:     ?line 1 = erlang:trace_pattern(MFA, true, [local]),
  200:     Ref = make_ref(),
  201:     Info = {self(),Ref},
  202:     ?line Child = spawn_opt(fun() -> min_hibernator(Info) end,
  203: 			    [{min_heap_size,15000},link]),
  204:     receive
  205: 	{trace,Child,call,{?MODULE,min_hibernator,_}} ->
  206: 	    ?line 1 = erlang:trace_pattern(MFA, false, [local]),
  207: 	    ?line erlang:trace(new, false, [call])
  208:     end,
  209:     {heap_size,HeapSz} = process_info(Child, heap_size),
  210:     io:format("Heap size: ~p\n", [HeapSz]),
  211:     ?line if
  212: 	      HeapSz < 20 -> ok
  213: 	  end,
  214:     ?line Child ! wake_up,
  215:     receive
  216: 	{heap_size,AfterSize} ->
  217: 	    io:format("Heap size after wakeup: ~p\n", [AfterSize]),
  218: 	    ?line
  219: 		if
  220: 		    AfterSize >= 15000 -> ok
  221: 		end;
  222: 	Other ->
  223: 	    io:format("Unexpected: ~p\n", [Other]),
  224: 	    ?line ?t:fail()
  225:     end.
  226: 
  227: min_hibernator({Parent,_Ref}) ->
  228:     erlang:hibernate(erlang, apply, [fun min_hibernator_recv/1, [Parent]]).
  229: 
  230: min_hibernator_recv(Parent) ->
  231:     receive
  232: 	wake_up ->
  233: 	    Parent ! process_info(self(), heap_size)
  234:     end.
  235: 
  236: %%%
  237: %%% Testing feeding erlang:hibernate/3 with bad arguments.
  238: %%%
  239: 
  240: bad_args(Config) when is_list(Config) ->
  241:     ?line bad_args(?MODULE, {name,glurf}, [0]),
  242:     ?line {'EXIT',{system_limit,_}} = 
  243: 	(catch erlang:hibernate(x, y, lists:duplicate(5122, xxx))),
  244:     ?line bad_args(42, name, [0]),
  245:     ?line bad_args(xx, 42, [1]),
  246:     ?line bad_args(xx, 42, glurf),
  247:     ?line bad_args(xx, 42, {}),
  248:     ?line bad_args({}, name, [2]),
  249:     ?line bad_args({1}, name,  [3]),
  250:     ?line bad_args({1,2,3}, name, [4]),
  251:     ?line bad_args({1,2,3}, name, [5]),
  252:     ?line bad_args({1,2,3,4}, name, [6]),
  253:     ?line bad_args({1,2,3,4,5,6}, name,[7]),
  254:     ?line bad_args({1,2,3,4,5}, name, [8]),
  255:     ?line bad_args({1,2}, name, [9]),
  256:     ?line bad_args([1,2], name, [9]),
  257:     ?line bad_args(55.0, name, [9]),
  258:     ok.
  259: 
  260: bad_args(Mod, Name, Args) ->
  261:     Res = (catch erlang:hibernate(Mod, Name, Args)),
  262:     erlang:garbage_collect(),
  263:     case Res of
  264: 	{'EXIT',{badarg,_Where}} ->
  265: 	    io:format("erlang:hibernate(~p, ~p, ~p) -> ~p\n", [Mod,Name,Args,Res]);
  266: 	Other ->
  267: 	    io:format("erlang:hibernate(~p, ~p, ~p) -> ~p\n", [Mod,Name,Args,Res]),
  268: 	    ?t:fail({bad_result,Other})
  269:     end.
  270: 
  271: 
  272: %%%
  273: %%% Testing calling erlang:hibernate/3 with messages already in the message queue.
  274: %%%
  275: 
  276: messages_in_queue(Config) when is_list(Config) ->
  277:     Self = self(),
  278:     Msg = {Self,make_ref(),a,message},
  279:     Pid = spawn_link(fun() -> messages_in_queue_1(Self, Msg) end),
  280:     Pid ! Msg,
  281:     Pid ! go_ahead,
  282:     receive
  283: 	done -> ok;
  284: 	Other ->
  285: 	    ?line io:format("~p\n", [Other]),
  286: 	    ?line ?t:fail(unexpected_message)
  287:     end.
  288: 
  289: messages_in_queue_1(Parent, ExpectedMsg) ->
  290:     receive
  291: 	go_ahead -> ok
  292:     end,
  293:     {message_queue_len,1} = process_info(self(), message_queue_len),
  294:     erlang:hibernate(?MODULE, messages_in_queue_restart,
  295: 		     [Parent,ExpectedMsg]).
  296: 
  297: messages_in_queue_restart(Parent, ExpectedMessage) ->
  298:     ?line receive
  299: 	      ExpectedMessage ->
  300: 		  Parent ! done;
  301: 	      Other ->
  302: 		  io:format("~p\n", [Other]),
  303: 		  ?t:fail(unexpected_message)
  304: 	  end,
  305:     ok.
  306: 
  307: 
  308: %%%
  309: %%% Test that trying to hibernate to an undefined MFA gives the correct
  310: %%% exit behavior.
  311: %%%
  312: 
  313: undefined_mfa(Config) when is_list(Config) ->
  314:     ?line process_flag(trap_exit, true),
  315:     ?line Pid = spawn_link(fun() ->
  316: 				   %% Will be a call_only instruction.
  317: 				   erlang:hibernate(?MODULE, blarf, []) end),
  318:     ?line Pid ! {a,message},
  319:     ?line receive
  320: 	      {'EXIT',Pid,{undef,Undef}} ->
  321: 		  io:format("~p\n", [Undef]),
  322: 		  ok;
  323: 	      Other ->
  324: 		  ?line io:format("~p\n", [Other]),
  325: 		  ?line ?t:fail(unexpected_message)
  326: 	  end,
  327:     undefined_mfa_1().
  328: 
  329: undefined_mfa_1() ->
  330:     ?line Pid = spawn_link(fun() ->
  331: 				   %% Force a call_last instruction by calling bar()
  332: 				   %% (if that is not obvious).
  333: 				   bar(),
  334: 				   erlang:hibernate(?MODULE, blarf, [])
  335: 			   end),
  336:     ?line Pid ! {another,message},
  337:     ?line receive
  338: 	      {'EXIT',Pid,{undef,Undef}} ->
  339: 		  io:format("~p\n", [Undef]),
  340: 		  ok;
  341: 	      Other ->
  342: 		  ?line io:format("~p\n", [Other]),
  343: 		  ?line ?t:fail(unexpected_message)
  344: 	  end,
  345:     ok.
  346: 
  347: bar() ->
  348:     ok.
  349: 
  350: %%
  351: %% No heap
  352: %%
  353: 
  354: no_heap(doc) -> [];
  355: no_heap(suite) -> [];
  356: no_heap(Config) when is_list(Config) ->
  357:     ?line H = spawn_link(fun () -> clean_dict(), no_heap_loop() end),
  358:     ?line lists:foreach(fun (_) ->
  359: 				wait_until(fun () -> is_hibernated(H) end),
  360: 				?line [{heap_size,1},
  361: 				       {total_heap_size,1}]
  362: 				    = process_info(H,
  363: 						   [heap_size,
  364: 						    total_heap_size]),
  365: 				receive after 10 -> ok end,
  366: 				H ! again
  367: 			end,
  368: 			lists:seq(1, 100)),
  369:     ?line unlink(H),
  370:     ?line exit(H, bye).
  371: 
  372: no_heap_loop() ->
  373:     flush(),
  374:     erlang:hibernate(?MODULE, no_heap_loop, []).
  375: 
  376: clean_dict() ->
  377:     {dictionary, Dict} = process_info(self(), dictionary),
  378:     lists:foreach(fun ({Key, _}) -> erase(Key) end, Dict).
  379: 
  380: %%
  381: %% Wake up and then immediatly bif trap with a lengthy computation.
  382: %%
  383: 
  384: wake_up_and_bif_trap(doc) -> [];
  385: wake_up_and_bif_trap(suite) -> [];
  386: wake_up_and_bif_trap(Config) when is_list(Config) ->
  387:     ?line Self = self(),
  388:     ?line Pid = spawn_link(fun() -> erlang:hibernate(?MODULE, characters_to_list_trap, [Self]) end),
  389:     ?line Pid ! wakeup,
  390:     ?line receive
  391:         {ok, Pid0} when Pid0 =:= Pid -> ok
  392:     after 5000 ->
  393:         ?line ?t:fail(process_blocked)
  394:     end,
  395:     ?line unlink(Pid),
  396:     ?line exit(Pid, bye).
  397: 
  398: %% Lengthy computation that traps (in characters_to_list_trap_3).
  399: characters_to_list_trap(Parent) ->
  400:     Bin0 = <<"abcdefghijklmnopqrstuvwxz0123456789">>,
  401:     Bin = binary:copy(Bin0, 1500),
  402:     unicode:characters_to_list(Bin),
  403:     Parent ! {ok, self()}.
  404: 
  405: %%
  406: %% Misc
  407: %%
  408: 
  409: is_hibernated(P) ->
  410:     case process_info(P, [current_function, status]) of
  411: 	[{current_function, {erlang, hibernate, _}},
  412: 	 {status, waiting}] ->
  413: 	    true;
  414: 	_ ->
  415: 	    false
  416:     end.
  417: 
  418: flush() ->
  419:     receive
  420: 	_Msg -> flush()
  421:     after 0 ->
  422: 	    ok
  423:     end.
  424: 	   
  425: 
  426: wait_until(Fun) ->
  427:     case catch Fun() of
  428: 	true -> ok;
  429: 	_ -> receive after 10 -> wait_until(Fun) end
  430:     end.