1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2010-2021. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21-module(dirty_nif_SUITE).
22
23%%-define(line_trace,true).
24-define(CHECK(Exp,Got), check(Exp,Got,?LINE)).
25%%-define(CHECK(Exp,Got), Exp = Got).
26
27-include_lib("common_test/include/ct.hrl").
28
29-export([all/0, suite/0,
30	 init_per_suite/1, end_per_suite/1,
31	 init_per_testcase/2, end_per_testcase/2,
32	 dirty_nif/1, dirty_nif_send/1,
33	 dirty_nif_exception/1, call_dirty_nif_exception/1,
34	 dirty_scheduler_exit/1, dirty_call_while_terminated/1,
35	 dirty_heap_access/1, dirty_process_info/1,
36	 dirty_process_register/1, dirty_process_trace/1,
37	 code_purge/1, literal_area/1, dirty_nif_send_traced/1,
38	 nif_whereis/1, nif_whereis_parallel/1, nif_whereis_proxy/1]).
39
40-define(nif_stub,nif_stub_error(?LINE)).
41
42suite() -> [{ct_hooks,[ts_install_cth]}].
43
44all() ->
45    [dirty_nif,
46     dirty_nif_send,
47     dirty_nif_exception,
48     dirty_scheduler_exit,
49     dirty_call_while_terminated,
50     dirty_heap_access,
51     dirty_process_info,
52     dirty_process_register,
53     dirty_process_trace,
54     code_purge,
55     literal_area,
56     dirty_nif_send_traced,
57     nif_whereis,
58     nif_whereis_parallel].
59
60init_per_suite(Config) ->
61    case erlang:system_info(dirty_cpu_schedulers) of
62	N when N > 0 ->
63	    case lib_loaded() of
64		false ->
65		    ok = erlang:load_nif(
66			   filename:join(?config(data_dir, Config),
67					 "dirty_nif_SUITE"), []);
68		true ->
69		    ok
70	    end,
71	    Config;
72        _ ->
73	    {skipped, "No dirty scheduler support"}
74    end.
75
76end_per_suite(_Config) ->
77    ok.
78
79init_per_testcase(Case, Config) ->
80    [{testcase, Case} | Config].
81
82end_per_testcase(_Case, _Config) ->
83    ok.
84
85dirty_nif(Config) when is_list(Config) ->
86    Val1 = 42,
87    Val2 = "Erlang",
88    Val3 = list_to_binary([Val2, 0]),
89    {Val1, Val2, Val3} = call_dirty_nif(Val1, Val2, Val3),
90    LargeArray = lists:duplicate(1000, ok),
91    LargeArray = call_dirty_nif_zero_args(),
92    ok.
93
94dirty_nif_send(Config) when is_list(Config) ->
95    Parent = self(),
96    Pid = spawn_link(fun() ->
97			     Self = self(),
98			     {ok, Self} = receive_any(),
99			     Parent ! {ok, Self}
100		     end),
101    {ok, Pid} = send_from_dirty_nif(Pid),
102    {ok, Pid} = receive_any(),
103    ok.
104
105dirty_nif_exception(Config) when is_list(Config) ->
106    try
107	%% this checks that the expected exception occurs when the
108	%% dirty NIF returns the result of enif_make_badarg
109	%% directly
110	call_dirty_nif_exception(1),
111	ct:fail(expected_badarg)
112    catch
113	error:badarg:Stk1 ->
114	    [{?MODULE,call_dirty_nif_exception,[1],_}|_] = Stk1,
115	    ok
116    end,
117    try
118	%% this checks that the expected exception occurs when the
119	%% dirty NIF calls enif_make_badarg at some point but then
120	%% returns a value that isn't an exception
121	call_dirty_nif_exception(0),
122	ct:fail(expected_badarg)
123    catch
124	error:badarg:Stk2 ->
125	    [{?MODULE,call_dirty_nif_exception,[0],_}|_] = Stk2,
126	    ok
127    end,
128    %% this checks that a dirty NIF can raise various terms as
129    %% exceptions
130    ok = nif_raise_exceptions(call_dirty_nif_exception).
131
132nif_raise_exceptions(NifFunc) ->
133    ExcTerms = [{error, test}, "a string", <<"a binary">>,
134                42, [1,2,3,4,5], [{p,1},{p,2},{p,3}]],
135    lists:foldl(fun(Term, ok) ->
136                        try
137                            erlang:apply(?MODULE,NifFunc,[Term]),
138                            ct:fail({expected,Term})
139                        catch
140                            error:Term:Stk ->
141                                [{?MODULE,NifFunc,[Term],_}|_] = Stk,
142                                ok
143                        end
144                end, ok, ExcTerms).
145
146dirty_scheduler_exit(Config) when is_list(Config) ->
147    {ok, Node} = start_node(Config, "+SDio 1"),
148    Path = proplists:get_value(data_dir, Config),
149    NifLib = filename:join(Path, atom_to_list(?MODULE)),
150    [ok] = mcall(Node,
151                 [fun() ->
152                          ok = erlang:load_nif(NifLib, []),
153                          %% Perform a dry run to ensure that all required code
154                          %% is loaded. Otherwise the test will fail since code
155                          %% loading is done through dirty IO and it won't make
156                          %% any progress during this test.
157                          _DryRun = test_dirty_scheduler_exit(),
158			  Start = erlang:monotonic_time(millisecond),
159                          ok = test_dirty_scheduler_exit(),
160			  End = erlang:monotonic_time(millisecond),
161			  io:format("Time=~p ms~n", [End-Start]),
162			  ok
163                  end]),
164    stop_node(Node),
165    ok.
166
167test_dirty_scheduler_exit() ->
168    process_flag(trap_exit,true),
169    test_dse(10,[]).
170test_dse(0,Pids) ->
171    timer:sleep(100),
172    kill_dse(Pids,[]);
173test_dse(N,Pids) ->
174    Pid = spawn_link(fun dirty_sleeper/0),
175    test_dse(N-1,[Pid|Pids]).
176
177kill_dse([],Killed) ->
178    wait_dse(Killed, ok);
179kill_dse([Pid|Pids],AlreadyKilled) ->
180    exit(Pid,kill),
181    kill_dse(Pids,[Pid|AlreadyKilled]).
182
183wait_dse([], Result) ->
184    Result;
185wait_dse([Pid|Pids], Result) ->
186    receive
187        {'EXIT', Pid, killed} -> wait_dse(Pids, Result);
188        {'EXIT', Pid, _Other} -> wait_dse(Pids, failed)
189    end.
190
191dirty_call_while_terminated(Config) when is_list(Config) ->
192    Me = self(),
193    Bin = list_to_binary(lists:duplicate(4711, $r)),
194    {value, {BinAddr, 4711, 1}} = lists:keysearch(4711, 2,
195						  element(2,
196							  process_info(self(),
197								       binary))),
198    {Dirty, DM} = spawn_opt(fun () ->
199				    dirty_call_while_terminated_nif(Me),
200				    blipp:blupp(Bin)
201			    end,
202			    [monitor,link]),
203    receive {dirty_alive, _Pid} -> ok end,
204    {value, {BinAddr, 4711, 2}} = lists:keysearch(4711, 2,
205						  element(2,
206							  process_info(self(),
207								       binary))),
208    Reason = die_dirty_process,
209    OT = process_flag(trap_exit, true),
210    exit(Dirty, Reason),
211    receive
212	{'DOWN', DM, process, Dirty, R0} ->
213	    R0 = Reason
214    end,
215    receive
216	{'EXIT', Dirty, R1} ->
217	    R1 = Reason
218    end,
219    undefined = process_info(Dirty),
220    undefined = process_info(Dirty, status),
221    false = erlang:is_process_alive(Dirty),
222    false = lists:member(Dirty, processes()),
223    %% Binary still referred by Dirty process not yet cleaned up
224    %% since the dirty nif has not yet returned...
225    {value, {BinAddr, 4711, 2}} = lists:keysearch(4711, 2,
226						  element(2,
227							  process_info(self(),
228								       binary))),
229    receive
230	Msg ->
231	    ct:fail({unexpected_message, Msg})
232    after
233	1000 ->
234	    ok
235    end,
236    ok = wait_until(fun() ->
237                       {value, {BinAddr, 4711, 1}} ==
238                           lists:keysearch(4711, 2,
239                                           element(2,
240                                                   process_info(self(),
241                                                                binary)))
242               end,
243               10000),
244    process_flag(trap_exit, OT),
245    try
246	blipp:blupp(Bin)
247    catch
248	_ : _ -> ok
249    end.
250
251dirty_heap_access(Config) when is_list(Config) ->
252    {ok, Node} = start_node(Config),
253    Me = self(),
254    RGL = rpc:call(Node,erlang,whereis,[init]),
255    Ref = rpc:call(Node,erlang,make_ref,[]),
256    Dirty = spawn_link(fun () ->
257			       Res = dirty_heap_access_nif(Ref),
258			       garbage_collect(),
259			       Me ! {self(), Res},
260			       receive after infinity -> ok end
261		       end),
262    {N, R} = access_dirty_heap(Dirty, RGL, 0, 0),
263    receive
264	{_Pid, Res} ->
265	    1000 = length(Res),
266	    lists:foreach(fun (X) -> Ref = X end, Res)
267    end,
268    unlink(Dirty),
269    exit(Dirty, kill),
270    stop_node(Node),
271    {comment, integer_to_list(N) ++ " GL change loops; "
272     ++ integer_to_list(R) ++ " while running dirty"}.
273
274access_dirty_heap(Dirty, RGL, N, R) ->
275    case process_info(Dirty, status) of
276	{status, waiting} ->
277	    {N, R};
278	{status, Status} ->
279	    {group_leader, GL} = process_info(Dirty, group_leader),
280	    true = group_leader(RGL, Dirty),
281	    {group_leader, RGL} = process_info(Dirty, group_leader),
282	    true = group_leader(GL, Dirty),
283	    {group_leader, GL} = process_info(Dirty, group_leader),
284	    access_dirty_heap(Dirty, RGL, N+1, case Status of
285						   running ->
286						       R+1;
287						   _ ->
288						       R
289					       end)
290    end.
291
292%% These tests verify that processes that access a process executing a
293%% dirty NIF where the main lock is needed for that access do not get
294%% blocked. Each test passes its pid to dirty_sleeper, which sends a
295%% 'ready' message when it's running on a dirty scheduler and just before
296%% it starts a 2 second sleep. When it receives the message, it verifies
297%% that access to the dirty process is as it expects.  After the dirty
298%% process finishes its 2 second sleep but before it returns from the dirty
299%% scheduler, it sends a 'done' message. If the tester already received
300%% that message, the test fails because it means attempting to access the
301%% dirty process waited for that process to return to a regular scheduler,
302%% so verify that we haven't received that message, and also verify that
303%% the dirty process is still alive immediately after accessing it.
304dirty_process_info(Config) when is_list(Config) ->
305    access_dirty_process(
306      Config,
307      fun() -> ok end,
308      fun(NifPid) ->
309	      PI = process_info(NifPid),
310	      {current_function,{?MODULE,dirty_sleeper,1}} =
311		  lists:keyfind(current_function, 1, PI),
312	      ok
313      end,
314      fun(_) -> ok end).
315
316dirty_process_register(Config) when is_list(Config) ->
317    access_dirty_process(
318      Config,
319      fun() -> ok end,
320      fun(NifPid) ->
321	      register(test_dirty_process_register, NifPid),
322	      NifPid = whereis(test_dirty_process_register),
323	      unregister(test_dirty_process_register),
324	      false = lists:member(test_dirty_process_register,
325				   registered()),
326	      ok
327      end,
328      fun(_) -> ok end).
329
330dirty_process_trace(Config) when is_list(Config) ->
331    access_dirty_process(
332      Config,
333      fun() ->
334	      erlang:trace_pattern({?MODULE,dirty_sleeper,1},
335				   [{'_',[],[{return_trace}]}],
336				   [local,meta]),
337	      ok
338      end,
339      fun(NifPid) ->
340	      erlang:trace(NifPid, true, [call,timestamp]),
341	      ok
342      end,
343      fun(NifPid) ->
344	      receive
345		  done ->
346		      receive
347			  {trace_ts,NifPid,call,{?MODULE,dirty_sleeper,_},_} ->
348			      ok
349		      after
350			  0 ->
351			      error(missing_trace_call_message)
352		      end,
353		      receive
354			  {trace_ts,NifPid,return_from,{?MODULE,dirty_sleeper,1},
355			   ok,_} ->
356			      ok
357		      after
358			  100 ->
359			      error(missing_trace_return_message)
360		      end
361	      after
362		  2500 ->
363		      error(missing_done_message)
364	      end,
365	      ok
366      end).
367
368dirty_code_test_code() ->
369    "
370-module(dirty_code_test).
371
372-export([func/1]).
373
374func(Fun) ->
375  Fun(),
376  blipp:blapp().
377
378".
379
380code_purge(Config) when is_list(Config) ->
381    Path = ?config(data_dir, Config),
382    File = filename:join(Path, "dirty_code_test.erl"),
383    ok = file:write_file(File, dirty_code_test_code()),
384    {ok, dirty_code_test, Bin} = compile:file(File, [binary]),
385    {module, dirty_code_test} = erlang:load_module(dirty_code_test, Bin),
386    Start = erlang:monotonic_time(),
387    {Pid1, Mon1} = spawn_monitor(fun () ->
388				       dirty_code_test:func(fun () ->
389								    %% Sleep for 2 seconds
390								    %% in dirty nif...
391								    dirty_sleeper()
392							    end)
393			       end),
394    {module, dirty_code_test} = erlang:load_module(dirty_code_test, Bin),
395    {Pid2, Mon2} = spawn_monitor(fun () ->
396				       dirty_code_test:func(fun () ->
397								    %% Sleep for 2 seconds
398								    %% in dirty nif...
399								    dirty_sleeper()
400							    end)
401				 end),
402    receive
403	{'DOWN', Mon1, process, Pid1, _} ->
404	    ct:fail(premature_death)
405    after 100 ->
406	    ok
407    end,
408    true = erlang:purge_module(dirty_code_test),
409    receive
410	{'DOWN', Mon1, process, Pid1, Reason1} ->
411	    killed = Reason1
412    end,
413    receive
414	{'DOWN', Mon2, process, Pid2, _} ->
415	    ct:fail(premature_death)
416    after 100 ->
417	    ok
418    end,
419    true = erlang:delete_module(dirty_code_test),
420    receive
421	{'DOWN', Mon2, process, Pid2, _} ->
422	    ct:fail(premature_death)
423    after 100 ->
424	    ok
425    end,
426    true = erlang:purge_module(dirty_code_test),
427    receive
428	{'DOWN', Mon2, process, Pid2, Reason2} ->
429	    killed = Reason2
430    end,
431    End = erlang:monotonic_time(),
432    Time = erlang:convert_time_unit(End-Start, native, milli_seconds),
433    io:format("Time=~p~n", [Time]),
434    true = Time =< 1000,
435    literal_area_collector_test:check_idle(5000),
436    ok.
437
438dirty_nif_send_traced(Config) when is_list(Config) ->
439    Parent = self(),
440    Rcvr = spawn_link(fun() ->
441			      Self = self(),
442			      receive {ok, Self} -> ok end,
443			      Parent ! {Self, received}
444		      end),
445    Sndr = spawn_link(fun () ->
446			      receive {Parent, go} -> ok end,
447			      {ok, Rcvr} = send_wait_from_dirty_nif(Rcvr),
448			      Parent ! {self(), sent}
449		      end),
450    1 = erlang:trace(Sndr, true, [send, running, exiting]),
451    Start = erlang:monotonic_time(),
452    Sndr ! {self(), go},
453
454    receive {Rcvr, received} -> ok end,
455    End1 = erlang:monotonic_time(),
456    Time1 = erlang:convert_time_unit(End1-Start, native, 1000),
457    io:format("Time1: ~p milliseconds~n", [Time1]),
458    true = Time1 < 500,
459    receive {Sndr, sent} -> ok end,
460    End2 = erlang:monotonic_time(),
461    Time2 = erlang:convert_time_unit(End2-Start, native, 1000),
462    io:format("Time2: ~p milliseconds~n", [Time2]),
463    true = Time2 >= 1900,
464
465    %% Make sure that the send trace is
466    %% in between an in and and out trace
467    (fun F() ->
468             %% We got an in trace, look for out or send
469             {trace,Sndr,in,_} = recv_trace_from(Sndr),
470             case recv_trace_from(Sndr) of
471                 {trace,Sndr,out,_} ->
472                     %% We got an out, look for another in
473                     F();
474                 {trace,Sndr,send,_,_} ->
475                     %% We got a send, look for another out
476                     {trace,Sndr,out,_} = recv_trace_from(Sndr),
477                     ok
478             end
479     end)(),
480
481    ok.
482
483recv_trace_from(Sndr) ->
484    receive
485        M when element(1, M) =:= trace;
486               element(1, M) =:= trace_ts,
487               element(2, M) =:= Sndr ->
488            M
489    end.
490
491dirty_literal_test_code() ->
492    "
493-module(dirty_literal_code_test).
494
495-export([get_literal/0]).
496
497get_literal() ->
498    {0,1,2,3,4,5,6,7,8,9}.
499
500".
501
502literal_area(Config) when is_list(Config) ->
503    NifTMO = 3000,
504    ExtraTMO = 1000,
505    TotTMO = NifTMO+ExtraTMO,
506    Path = ?config(data_dir, Config),
507    File = filename:join(Path, "dirty_literal_code_test.erl"),
508    ok = file:write_file(File, dirty_literal_test_code()),
509    {ok, dirty_literal_code_test, Bin} = compile:file(File, [binary]),
510    {module, dirty_literal_code_test} = erlang:load_module(dirty_literal_code_test, Bin),
511    Me = self(),
512    Fun = fun () ->
513                  dirty_terminating_literal_access(
514                    Me,
515                    dirty_literal_code_test:get_literal())
516          end,
517    {Pid, Mon} = spawn_monitor(Fun),
518    receive {dirty_alive, Pid} -> ok end,
519    exit(Pid, kill),
520    Start = erlang:monotonic_time(millisecond),
521    receive {'DOWN', Mon, process, Pid, killed} -> ok end,
522    true = erlang:delete_module(dirty_literal_code_test),
523    true = erlang:purge_module(dirty_literal_code_test),
524    End = erlang:monotonic_time(millisecond),
525    %% Wait for dirty_nif to do its access...
526    TMO = case End - Start of
527              T when T < TotTMO ->
528                  TotTMO-T;
529              _ ->
530                  0
531          end,
532    receive after TMO -> ok end,
533    literal_area_collector_test:check_idle(5000),
534    {comment, "Waited "++integer_to_list(TMO)++" milliseconds after purge"}.
535
536%%
537%% Internal...
538%%
539
540access_dirty_process(Config, Start, Test, Finish) ->
541    {ok, Node} = start_node(Config, ""),
542    [ok] = mcall(Node,
543		 [fun() ->
544                          Path = ?config(data_dir, Config),
545                          Lib = atom_to_list(?MODULE),
546                          ok = erlang:load_nif(filename:join(Path,Lib), []),
547			  ok = test_dirty_process_access(Start, Test, Finish)
548		  end]),
549    stop_node(Node),
550    ok.
551
552test_dirty_process_access(Start, Test, Finish) ->
553    ok = Start(),
554    Self = self(),
555    NifPid = spawn_link(fun() ->
556				ok = dirty_sleeper(Self)
557			end),
558    ok = receive
559	     ready ->
560		 ok = Test(NifPid),
561		 receive
562		     done ->
563			 error(dirty_process_info_blocked)
564		 after
565		     0 ->
566			 true = erlang:is_process_alive(NifPid),
567			 ok
568		 end
569	 after
570	     1000 ->
571		 error(timeout)
572	 end,
573    ok = Finish(NifPid).
574
575receive_any() ->
576    receive M -> M end.
577
578start_node(Config) ->
579    start_node(Config, "").
580
581start_node(Config, Args) when is_list(Config) ->
582    Pa = filename:dirname(code:which(?MODULE)),
583    Name = list_to_atom(atom_to_list(?MODULE)
584			++ "-"
585			++ atom_to_list(proplists:get_value(testcase, Config))
586			++ "-"
587			++ integer_to_list(erlang:system_time(second))
588			++ "-"
589			++ integer_to_list(erlang:unique_integer([positive]))),
590    test_server:start_node(Name, slave, [{args, "-pa "++Pa++" "++Args}]).
591
592stop_node(Node) ->
593    test_server:stop_node(Node).
594
595mcall(Node, Funs) ->
596    Parent = self(),
597    Refs = lists:map(fun (Fun) ->
598                             Ref = make_ref(),
599                             spawn_link(Node,
600                                        fun () ->
601                                                Res = Fun(),
602                                                unlink(Parent),
603                                                Parent ! {Ref, Res}
604                                        end),
605                             Ref
606                     end, Funs),
607    lists:map(fun (Ref) ->
608                      receive
609                          {Ref, Res} ->
610                              Res
611                      end
612              end, Refs).
613
614%% Test enif_whereis_...
615%% These tests are mostly identical to their counterparts in nif_SUITE.erl,
616%% with just name and count changes in the first few lines.
617
618nif_whereis(Config) when is_list(Config) ->
619    erl_ddll:try_load(?config(data_dir, Config), echo_drv, []),
620
621    RegName = dirty_nif_whereis_test_thing,
622    undefined = erlang:whereis(RegName),
623    false = whereis_term(pid, RegName),
624
625    Mgr = self(),
626    Ref = make_ref(),
627    ProcMsg = {Ref, ?LINE},
628    PortMsg = ?MODULE_STRING " whereis hello\n",
629
630    {Pid, Mon} = spawn_monitor(?MODULE, nif_whereis_proxy, [Ref]),
631    true = register(RegName, Pid),
632    Pid = erlang:whereis(RegName),
633    Pid = whereis_term(pid, RegName),
634    false = whereis_term(port, RegName),
635    false = whereis_term(pid, [RegName]),
636
637    ok = whereis_send(pid, RegName, {forward, Mgr, ProcMsg}),
638    ok = receive ProcMsg -> ok end,
639
640    Pid ! {Ref, quit},
641    ok = receive {'DOWN', Mon, process, Pid, normal} -> ok end,
642    undefined = erlang:whereis(RegName),
643    false = whereis_term(pid, RegName),
644
645    Port = open_port({spawn, echo_drv}, [eof]),
646    true = register(RegName, Port),
647    Port = erlang:whereis(RegName),
648    Port = whereis_term(port, RegName),
649    false = whereis_term(pid, RegName),
650    false = whereis_term(port, [RegName]),
651
652    ok = whereis_send(port, RegName, PortMsg),
653    ok = receive {Port, {data, PortMsg}} -> ok end,
654
655    port_close(Port),
656    undefined = erlang:whereis(RegName),
657    false = whereis_term(port, RegName),
658    ok.
659
660nif_whereis_parallel(Config) when is_list(Config) ->
661
662    %% try to be at least a little asymetric
663    NProcs = trunc(3.5 * erlang:system_info(schedulers)),
664    NSeq = lists:seq(1, NProcs),
665    Names = [list_to_atom("dirty_nif_whereis_proc_" ++ integer_to_list(N))
666            || N <- NSeq],
667    Mgr = self(),
668    Ref = make_ref(),
669
670    NotReg = fun(Name) ->
671        erlang:whereis(Name) == undefined
672    end,
673    PidReg = fun({Name, Pid, _Mon}) ->
674        erlang:whereis(Name) == Pid andalso whereis_term(pid, Name) == Pid
675    end,
676    RecvDown = fun({_Name, Pid, Mon}) ->
677        receive {'DOWN', Mon, process, Pid, normal} -> true
678        after   1500 -> false end
679    end,
680    RecvNum = fun(N) ->
681        receive {N, Ref} -> true
682        after   1500 -> false end
683    end,
684
685    true = lists:all(NotReg, Names),
686
687    %% {Name, Pid, Mon}
688    Procs = lists:map(
689        fun(N) ->
690            Name = lists:nth(N, Names),
691            Prev = lists:nth((if N == 1 -> NProcs; true -> (N - 1) end), Names),
692            Next = lists:nth((if N == NProcs -> 1; true -> (N + 1) end), Names),
693            {Pid, Mon} = spawn_monitor(
694                ?MODULE, nif_whereis_proxy, [{N, Ref, Mgr, [Prev, Next]}]),
695            true = register(Name, Pid),
696            {Name, Pid, Mon}
697        end, NSeq),
698
699    true = lists:all(PidReg, Procs),
700
701    %% tell them all to 'fire' as fast as we can
702    [P ! {Ref, send_proc} || {_, P, _} <- Procs],
703
704    %% each gets forwarded through two processes
705    true = lists:all(RecvNum, NSeq),
706    true = lists:all(RecvNum, NSeq),
707
708    %% tell them all to 'quit' by name
709    [N ! {Ref, quit} || {N, _, _} <- Procs],
710    true = lists:all(RecvDown, Procs),
711    true = lists:all(NotReg, Names),
712    ok.
713
714%% exported to be spawned by MFA by whereis tests
715nif_whereis_proxy({N, Ref, Mgr, Targets} = Args) ->
716    receive
717        {forward, To, Data} ->
718            To ! Data,
719            nif_whereis_proxy(Args);
720        {Ref, quit} ->
721            ok;
722        {Ref, send_port} ->
723            Msg = ?MODULE_STRING " whereis " ++ integer_to_list(N) ++ "\n",
724            lists:foreach(
725                fun(T) ->
726                    ok = whereis_send(port, T, Msg)
727                end, Targets),
728            nif_whereis_proxy(Args);
729        {Ref, send_proc} ->
730            lists:foreach(
731                fun(T) ->
732                    ok = whereis_send(pid, T, {forward, Mgr, {N, Ref}})
733                end, Targets),
734            nif_whereis_proxy(Args)
735    end;
736nif_whereis_proxy(Ref) ->
737    receive
738        {forward, To, Data} ->
739            To ! Data,
740            nif_whereis_proxy(Ref);
741        {Ref, quit} ->
742            ok
743    end.
744
745wait_until(Fun, infinity) ->
746    wait_until_aux(Fun, infinity);
747wait_until(Fun, MaxTime) ->
748    End = erlang:monotonic_time(millisecond) + MaxTime,
749    wait_until_aux(Fun, End).
750
751wait_until_aux(Fun, End) ->
752    case Fun() of
753        true ->
754            ok;
755        _ ->
756            if End == infinity ->
757                    receive after 100 -> ok end,
758                    wait_until_aux(Fun, infinity);
759               true ->
760                    Now = erlang:monotonic_time(millisecond),
761                    case End =< Now of
762                        true ->
763                            timeout;
764                        _ ->
765                            Wait = case End - Now of
766                                       Short when End - Now < 100 ->
767                                           Short;
768                                       _ ->
769                                           100
770                                   end,
771                            receive after Wait -> ok end,
772                            wait_until_aux(Fun, End)
773                    end
774            end
775    end.
776
777
778%% The NIFs:
779lib_loaded() -> false.
780call_dirty_nif(_,_,_) -> ?nif_stub.
781send_from_dirty_nif(_) -> ?nif_stub.
782send_wait_from_dirty_nif(_) -> ?nif_stub.
783call_dirty_nif_exception(_) -> ?nif_stub.
784call_dirty_nif_zero_args() -> ?nif_stub.
785dirty_call_while_terminated_nif(_) -> ?nif_stub.
786dirty_sleeper() -> ?nif_stub.
787dirty_sleeper(_) -> ?nif_stub.
788dirty_heap_access_nif(_) -> ?nif_stub.
789whereis_term(_Type,_Name) -> ?nif_stub.
790whereis_send(_Type,_Name,_Msg) -> ?nif_stub.
791dirty_terminating_literal_access(_Me, _Literal) -> ?nif_stub.
792
793nif_stub_error(Line) ->
794    exit({nif_not_loaded,module,?MODULE,line,Line}).
795