%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2010-2017. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(nif_SUITE). %%-define(line_trace,true). -define(CHECK(Exp,Got), Exp = check(Exp,Got,?LINE)). %%-define(CHECK(Exp,Got), Exp = Got). -include_lib("common_test/include/ct.hrl"). -export([all/0, suite/0, groups/0, init_per_group/2, end_per_group/2, init_per_testcase/2, end_per_testcase/2, basic/1, reload_error/1, upgrade/1, heap_frag/1, t_on_load/1, select/1, monitor_process_a/1, monitor_process_b/1, monitor_process_c/1, monitor_process_d/1, demonitor_process/1, monitor_frenzy/1, hipe/1, types/1, many_args/1, binaries/1, get_string/1, get_atom/1, maps/1, api_macros/1, from_array/1, iolist_as_binary/1, resource/1, resource_binary/1, resource_takeover/1, threading/1, send/1, send2/1, send3/1, send_threaded/1, neg/1, is_checks/1, get_length/1, make_atom/1, make_string/1, reverse_list_test/1, otp_9828/1, otp_9668/1, consume_timeslice/1, nif_schedule/1, nif_exception/1, call_nif_exception/1, nif_nan_and_inf/1, nif_atom_too_long/1, nif_monotonic_time/1, nif_time_offset/1, nif_convert_time_unit/1, nif_now_time/1, nif_cpu_time/1, nif_unique_integer/1, nif_is_process_alive/1, nif_is_port_alive/1, nif_term_to_binary/1, nif_binary_to_term/1, nif_port_command/1, nif_snprintf/1, nif_internal_hash/1, nif_internal_hash_salted/1, nif_phash2/1 ]). -export([many_args_100/100]). -define(nif_stub,nif_stub_error(?LINE)). -define(is_resource, is_reference). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [basic] ++ [{group, G} || G <- api_groups()] ++ [reload_error, heap_frag, types, many_args, select, {group, monitor}, monitor_frenzy, hipe, binaries, get_string, get_atom, maps, api_macros, from_array, iolist_as_binary, resource, resource_binary, threading, send, send2, send3, send_threaded, neg, is_checks, get_length, make_atom, make_string,reverse_list_test, otp_9828, otp_9668, consume_timeslice, nif_schedule, nif_exception, nif_nan_and_inf, nif_atom_too_long, nif_monotonic_time, nif_time_offset, nif_convert_time_unit, nif_now_time, nif_cpu_time, nif_unique_integer, nif_is_process_alive, nif_is_port_alive, nif_term_to_binary, nif_binary_to_term, nif_port_command, nif_snprintf, nif_internal_hash, nif_internal_hash_salted, nif_phash2]. groups() -> [{G, [], api_repeaters()} || G <- api_groups()] ++ [{monitor, [], [monitor_process_a, monitor_process_b, monitor_process_c, monitor_process_d, demonitor_process]}]. api_groups() -> [api_latest, api_2_4, api_2_0]. api_repeaters() -> [upgrade, resource_takeover, t_on_load]. init_per_group(api_2_4, Config) -> [{nif_api_version, ".2_4"} | Config]; init_per_group(api_2_0, Config) -> case {os:type(),erlang:system_info({wordsize, internal})} of {{win32,_}, 8} -> %% ERL_NIF_TERM was declared as 32-bit 'long' until 2.3 {skip, "API 2.0 buggy on Windows 64-bit"}; _ -> [{nif_api_version, ".2_0"} | Config] end; init_per_group(_, Config) -> Config. end_per_group(_,_) -> ok. init_per_testcase(t_on_load, Config) -> ets:new(nif_SUITE, [named_table]), Config; init_per_testcase(hipe, Config) -> case erlang:system_info(hipe_architecture) of undefined -> {skip, "HiPE is disabled"}; _ -> Config end; init_per_testcase(select, Config) -> case os:type() of {win32,_} -> {skip, "Test not yet implemented for windows"}; _ -> Config end; init_per_testcase(_Case, Config) -> Config. end_per_testcase(t_on_load, _Config) -> ets:delete(nif_SUITE), testcase_cleanup(); end_per_testcase(_Func, _Config) -> testcase_cleanup(). testcase_cleanup() -> P1 = code:purge(nif_mod), Del = code:delete(nif_mod), P2 = code:purge(nif_mod), io:format("fin purged=~p, deleted=~p and then purged=~p\n",[P1,Del,P2]). %% Basic smoke test of load_nif and a simple NIF call basic(Config) when is_list(Config) -> ensure_lib_loaded(Config), true = (lib_version() =/= undefined), [{load,1,1,101},{lib_version,1,2,102}] = call_history(), [] = call_history(), true = lists:member(?MODULE, erlang:system_info(taints)), ok. %% Test old reload feature now always fails reload_error(Config) when is_list(Config) -> TmpMem = tmpmem(), ensure_lib_loaded(Config), Data = proplists:get_value(data_dir, Config), File = filename:join(Data, "nif_mod"), {ok,nif_mod,Bin} = compile:file(File, [binary,return_errors]), {module,nif_mod} = erlang:load_module(nif_mod,Bin), ok = nif_mod:load_nif_lib(Config, 1), hold_nif_mod_priv_data(nif_mod:get_priv_data_ptr()), [{load,1,1,101},{get_priv_data_ptr,1,2,102}] = nif_mod_call_history(), {error, {reload, _}} = nif_mod:load_nif_lib(Config, 2), 1 = nif_mod:lib_version(), [{lib_version,1,3,103}] = nif_mod_call_history(), {error, {reload, _}} = nif_mod:load_nif_lib(Config, 1), 1 = nif_mod:lib_version(), [{lib_version,1,4,104}] = nif_mod_call_history(), true = erlang:delete_module(nif_mod), [] = nif_mod_call_history(), %%false= check_process_code(Pid, nif_mod), true = erlang:purge_module(nif_mod), [{unload,1,5,105}] = nif_mod_call_history(), true = lists:member(?MODULE, erlang:system_info(taints)), true = lists:member(nif_mod, erlang:system_info(taints)), verify_tmpmem(TmpMem), ok. %% Test upgrade callback in nif lib upgrade(Config) when is_list(Config) -> TmpMem = tmpmem(), ensure_lib_loaded(Config), Data = proplists:get_value(data_dir, Config), File = filename:join(Data, "nif_mod"), {ok,nif_mod,Bin} = compile:file(File, [binary,return_errors]), {module,nif_mod} = erlang:load_module(nif_mod,Bin), ok = nif_mod:load_nif_lib(Config, 1), {Pid,MRef} = nif_mod:start(), 1 = call(Pid,lib_version), hold_nif_mod_priv_data(nif_mod:get_priv_data_ptr()), [{load,1,1,101},{lib_version,1,2,102},{get_priv_data_ptr,1,3,103}] = nif_mod_call_history(), %% Module upgrade with same lib-version {module,nif_mod} = erlang:load_module(nif_mod,Bin), undefined = nif_mod:lib_version(), 1 = call(Pid,lib_version), [{lib_version,1,4,104}] = nif_mod_call_history(), ok = nif_mod:load_nif_lib(Config, 1), 1 = nif_mod:lib_version(), [{upgrade,1,5,105},{lib_version,1,6,106}] = nif_mod_call_history(), upgraded = call(Pid,upgrade), false = check_process_code(Pid, nif_mod), true = erlang:purge_module(nif_mod), [{unload,1,7,107}] = nif_mod_call_history(), 1 = nif_mod:lib_version(), [{lib_version,1,8,108}] = nif_mod_call_history(), true = erlang:delete_module(nif_mod), [] = nif_mod_call_history(), %% Repeat upgrade again but from old (deleted) instance {module,nif_mod} = erlang:load_module(nif_mod,Bin), undefined = nif_mod:lib_version(), 1 = call(Pid,lib_version), [{lib_version,1,9,109}] = nif_mod_call_history(), ok = nif_mod:load_nif_lib(Config, 1), 1 = nif_mod:lib_version(), [{upgrade,1,10,110},{lib_version,1,11,111}] = nif_mod_call_history(), upgraded = call(Pid,upgrade), false = check_process_code(Pid, nif_mod), true = erlang:purge_module(nif_mod), [{unload,1,12,112}] = nif_mod_call_history(), 1 = nif_mod:lib_version(), [{lib_version,1,13,113}] = nif_mod_call_history(), true = erlang:delete_module(nif_mod), [] = nif_mod_call_history(), Pid ! die, {'DOWN', MRef, process, Pid, normal} = receive_any(), false = check_process_code(Pid, nif_mod), true = erlang:purge_module(nif_mod), [{unload,1,14,114}] = nif_mod_call_history(), %% Module upgrade with different lib version {module,nif_mod} = erlang:load_module(nif_mod,Bin), undefined = nif_mod:lib_version(), {Pid2,MRef2} = nif_mod:start(), undefined = call(Pid2,lib_version), ok = nif_mod:load_nif_lib(Config, 1), hold_nif_mod_priv_data(nif_mod:get_priv_data_ptr()), 1 = call(Pid2,lib_version), [{load,1,1,101},{get_priv_data_ptr,1,2,102},{lib_version,1,3,103}] = nif_mod_call_history(), {module,nif_mod} = erlang:load_module(nif_mod,Bin), undefined = nif_mod:lib_version(), [] = nif_mod_call_history(), 1 = call(Pid2,lib_version), [{lib_version,1,4,104}] = nif_mod_call_history(), ok = nif_mod:load_nif_lib(Config, 2), 2 = nif_mod:lib_version(), [{upgrade,2,1,201},{lib_version,2,2,202}] = nif_mod_call_history(), 1 = call(Pid2,lib_version), [{lib_version,1,5,105}] = nif_mod_call_history(), upgraded = call(Pid2,upgrade), false = check_process_code(Pid2, nif_mod), true = erlang:purge_module(nif_mod), [{unload,1,6,106}] = nif_mod_call_history(), 2 = nif_mod:lib_version(), [{lib_version,2,3,203}] = nif_mod_call_history(), true = erlang:delete_module(nif_mod), [] = nif_mod_call_history(), %% Reverse upgrade but from old (deleted) instance {module,nif_mod} = erlang:load_module(nif_mod,Bin), undefined = nif_mod:lib_version(), [] = nif_mod_call_history(), 2 = call(Pid2,lib_version), [{lib_version,2,4,204}] = nif_mod_call_history(), ok = nif_mod:load_nif_lib(Config, 1), 1 = nif_mod:lib_version(), [{upgrade,1,1,101},{lib_version,1,2,102}] = nif_mod_call_history(), 2 = call(Pid2,lib_version), [{lib_version,2,5,205}] = nif_mod_call_history(), upgraded = call(Pid2,upgrade), false = check_process_code(Pid2, nif_mod), true = erlang:purge_module(nif_mod), [{unload,2,6,206}] = nif_mod_call_history(), 1 = nif_mod:lib_version(), [{lib_version,1,3,103}] = nif_mod_call_history(), true = erlang:delete_module(nif_mod), [] = nif_mod_call_history(), Pid2 ! die, {'DOWN', MRef2, process, Pid2, normal} = receive_any(), false= check_process_code(Pid2, nif_mod), true = erlang:purge_module(nif_mod), [{unload,1,4,104}] = nif_mod_call_history(), true = lists:member(?MODULE, erlang:system_info(taints)), true = lists:member(nif_mod, erlang:system_info(taints)), verify_tmpmem(TmpMem), ok. %% Test loading/upgrade in on_load t_on_load(Config) when is_list(Config) -> TmpMem = tmpmem(), ensure_lib_loaded(Config), Data = proplists:get_value(data_dir, Config), File = filename:join(Data, "nif_mod"), {ok,nif_mod,Bin} = compile:file(File, [binary,return_errors, {d,'USE_ON_LOAD'}]), %% Use ETS to tell nif_mod:on_load what to do ets:insert(nif_SUITE, {data_dir, Data}), ets:insert(nif_SUITE, {lib_version, 1}), API = proplists:get_value(nif_api_version, Config, ""), ets:insert(nif_SUITE, {nif_api_version, API}), {module,nif_mod} = code:load_binary(nif_mod,File,Bin), hold_nif_mod_priv_data(nif_mod:get_priv_data_ptr()), [{load,1,1,101},{get_priv_data_ptr,1,2,102}] = nif_mod_call_history(), {Pid,MRef} = nif_mod:start(), 1 = call(Pid,lib_version), [{lib_version,1,3,103}] = nif_mod_call_history(), %% Module upgrade with same lib-version {module,nif_mod} = code:load_binary(nif_mod,File,Bin), 1 = nif_mod:lib_version(), 1 = call(Pid,lib_version), [{upgrade,1,4,104},{lib_version,1,5,105},{lib_version,1,6,106}] = nif_mod_call_history(), upgraded = call(Pid,upgrade), false = check_process_code(Pid, nif_mod), true = code:soft_purge(nif_mod), [{unload,1,7,107}] = nif_mod_call_history(), 1 = nif_mod:lib_version(), [{lib_version,1,8,108}] = nif_mod_call_history(), true = code:delete(nif_mod), [] = nif_mod_call_history(), %% Repeat upgrade again but from old (deleted) instance {module,nif_mod} = code:load_binary(nif_mod,File,Bin), [{upgrade,1,9,109}] = nif_mod_call_history(), 1 = nif_mod:lib_version(), 1 = call(Pid,lib_version), [{lib_version,1,10,110},{lib_version,1,11,111}] = nif_mod_call_history(), upgraded = call(Pid,upgrade), false = check_process_code(Pid, nif_mod), true = code:soft_purge(nif_mod), [{unload,1,12,112}] = nif_mod_call_history(), 1 = nif_mod:lib_version(), [{lib_version,1,13,113}] = nif_mod_call_history(), true = code:delete(nif_mod), [] = nif_mod_call_history(), Pid ! die, {'DOWN', MRef, process, Pid, normal} = receive_any(), false = check_process_code(Pid, nif_mod), true = code:soft_purge(nif_mod), [{unload,1,14,114}] = nif_mod_call_history(), %% Module upgrade with different lib version {module,nif_mod} = code:load_binary(nif_mod,File,Bin), hold_nif_mod_priv_data(nif_mod:get_priv_data_ptr()), [{load,1,1,101},{get_priv_data_ptr,1,2,102}] = nif_mod_call_history(), 1 = nif_mod:lib_version(), {Pid2,MRef2} = nif_mod:start(), 1 = call(Pid2,lib_version), [{lib_version,1,3,103},{lib_version,1,4,104}] = nif_mod_call_history(), true = ets:insert(nif_SUITE,{lib_version,2}), {module,nif_mod} = code:load_binary(nif_mod,File,Bin), [{upgrade,2,1,201}] = nif_mod_call_history(), 2 = nif_mod:lib_version(), 1 = call(Pid2,lib_version), [{lib_version,2,2,202},{lib_version,1,5,105}] = nif_mod_call_history(), upgraded = call(Pid2,upgrade), false = check_process_code(Pid2, nif_mod), true = code:soft_purge(nif_mod), [{unload,1,6,106}] = nif_mod_call_history(), 2 = nif_mod:lib_version(), 2 = call(Pid2,lib_version), [{lib_version,2,3,203},{lib_version,2,4,204}] = nif_mod_call_history(), true = code:delete(nif_mod), [] = nif_mod_call_history(), %% Reverse upgrade but from old (deleted) instance ets:insert(nif_SUITE,{lib_version,1}), {module,nif_mod} = code:load_binary(nif_mod,File,Bin), [{upgrade,1,1,101}] = nif_mod_call_history(), 1 = nif_mod:lib_version(), 2 = call(Pid2,lib_version), [{lib_version,1,2,102},{lib_version,2,5,205}] = nif_mod_call_history(), upgraded = call(Pid2,upgrade), false = check_process_code(Pid2, nif_mod), true = code:soft_purge(nif_mod), [{unload,2,6,206}] = nif_mod_call_history(), 1 = nif_mod:lib_version(), [{lib_version,1,3,103}] = nif_mod_call_history(), true = code:delete(nif_mod), [] = nif_mod_call_history(), Pid2 ! die, {'DOWN', MRef2, process, Pid2, normal} = receive_any(), false= check_process_code(Pid2, nif_mod), true = code:soft_purge(nif_mod), [{unload,1,4,104}] = nif_mod_call_history(), true = lists:member(?MODULE, erlang:system_info(taints)), true = lists:member(nif_mod, erlang:system_info(taints)), verify_tmpmem(TmpMem), ok. -define(ERL_NIF_SELECT_READ, (1 bsl 0)). -define(ERL_NIF_SELECT_WRITE, (1 bsl 1)). -define(ERL_NIF_SELECT_STOP, (1 bsl 2)). -define(ERL_NIF_SELECT_STOP_CALLED, (1 bsl 0)). -define(ERL_NIF_SELECT_STOP_SCHEDULED, (1 bsl 1)). -define(ERL_NIF_SELECT_INVALID_EVENT, (1 bsl 2)). -define(ERL_NIF_SELECT_FAILED, (1 bsl 3)). select(Config) when is_list(Config) -> ensure_lib_loaded(Config), Ref = make_ref(), Ref2 = make_ref(), {{R, R_ptr}, {W, W_ptr}} = pipe_nif(), ok = write_nif(W, <<"hej">>), <<"hej">> = read_nif(R, 3), %% Wait for read eagain = read_nif(R, 3), 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,null,Ref), [] = flush(0), ok = write_nif(W, <<"hej">>), [{select, R, Ref, ready_input}] = flush(), 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,self(),Ref2), [{select, R, Ref2, ready_input}] = flush(), Papa = self(), Pid = spawn_link(fun() -> [{select, R, Ref, ready_input}] = flush(), Papa ! {self(), done} end), 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,Pid,Ref), {Pid, done} = receive_any(1000), <<"hej">> = read_nif(R, 3), %% Wait for write Written = write_full(W, $a), 0 = select_nif(W,?ERL_NIF_SELECT_WRITE,W,self(),Ref), [] = flush(0), Written = read_nif(R,byte_size(Written)), [{select, W, Ref, ready_output}] = flush(), %% Close write and wait for EOF eagain = read_nif(R, 1), check_stop_ret(select_nif(W,?ERL_NIF_SELECT_STOP,W,null,Ref)), [{fd_resource_stop, W_ptr, _}] = flush(), {1, {W_ptr,_}} = last_fd_stop_call(), true = is_closed_nif(W), [] = flush(0), 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,self(),Ref), [{select, R, Ref, ready_input}] = flush(), eof = read_nif(R,1), check_stop_ret(select_nif(R,?ERL_NIF_SELECT_STOP,R,null,Ref)), [{fd_resource_stop, R_ptr, _}] = flush(), {1, {R_ptr,_}} = last_fd_stop_call(), true = is_closed_nif(R), select_2(Config). select_2(Config) -> erlang:garbage_collect(), {_,_,2} = last_resource_dtor_call(), Ref1 = make_ref(), Ref2 = make_ref(), {{R, R_ptr}, {W, W_ptr}} = pipe_nif(), %% Change ref eagain = read_nif(R, 1), 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,null,Ref1), 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,self(),Ref2), [] = flush(0), ok = write_nif(W, <<"hej">>), [{select, R, Ref2, ready_input}] = flush(), <<"hej">> = read_nif(R, 3), %% Change pid eagain = read_nif(R, 1), 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,null,Ref1), Papa = self(), spawn_link(fun() -> 0 = select_nif(R,?ERL_NIF_SELECT_READ,R,null,Ref1), [] = flush(0), Papa ! sync, [{select, R, Ref1, ready_input}] = flush(), <<"hej">> = read_nif(R, 3), Papa ! done end), sync = receive_any(), ok = write_nif(W, <<"hej">>), done = receive_any(), [] = flush(0), check_stop_ret(select_nif(R,?ERL_NIF_SELECT_STOP,R,null,Ref1)), [{fd_resource_stop, R_ptr, _}] = flush(), {1, {R_ptr,_}} = last_fd_stop_call(), true = is_closed_nif(R), %% Stop without previous read/write select ?ERL_NIF_SELECT_STOP_CALLED = select_nif(W,?ERL_NIF_SELECT_STOP,W,null,Ref1), [{fd_resource_stop, W_ptr, 1}] = flush(), {1, {W_ptr,1}} = last_fd_stop_call(), true = is_closed_nif(W), select_3(Config). select_3(_Config) -> erlang:garbage_collect(), {_,_,2} = last_resource_dtor_call(), ok. check_stop_ret(?ERL_NIF_SELECT_STOP_CALLED) -> ok; check_stop_ret(?ERL_NIF_SELECT_STOP_SCHEDULED) -> ok. write_full(W, C) -> write_full(W, C, <<>>). write_full(W, C, Acc) -> case write_nif(W, <>) of ok -> write_full(W, (C+1) band 255, <>); {eagain,0} -> Acc end. %% Basic monitoring of one process that terminates monitor_process_a(Config) -> ensure_lib_loaded(Config), F = fun(Terminator, UseMsgEnv) -> Pid = spawn(fun() -> receive {exit, Arg} -> exit(Arg); return -> ok; BadMatch -> goodmatch = BadMatch end end), R_ptr = alloc_monitor_resource_nif(), {0, Mon1} = monitor_process_nif(R_ptr, Pid, UseMsgEnv, self()), [R_ptr] = monitored_by(Pid), Terminator(Pid), [{monitor_resource_down, R_ptr, Pid, Mon2}] = flush(), 0 = compare_monitors_nif(Mon1, Mon2), [] = last_resource_dtor_call(), ok = release_resource(R_ptr), {R_ptr, _, 1} = last_resource_dtor_call() end, T1 = fun(Pid) -> Pid ! {exit, 17} end, T2 = fun(Pid) -> Pid ! return end, T3 = fun(Pid) -> Pid ! badmatch end, T4 = fun(Pid) -> exit(Pid, 18) end, [F(T, UME) || T <- [T1,T2,T3,T4], UME <- [true, false]], ok. %% Test auto-demonitoring at resource destruction monitor_process_b(Config) -> ensure_lib_loaded(Config), monitor_process_b_do(false), case erlang:system_info(threads) of true -> monitor_process_b_do(true); false -> ok end, ok. monitor_process_b_do(FromThread) -> Pid = spawn_link(fun() -> receive return -> ok end end), R_ptr = alloc_monitor_resource_nif(), {0,_} = monitor_process_nif(R_ptr, Pid, true, self()), [R_ptr] = monitored_by(Pid), case FromThread of false -> ok = release_resource(R_ptr); true -> ok = release_resource_from_thread(R_ptr) end, [] = flush(0), {R_ptr, _, 1} = last_resource_dtor_call(), [] = monitored_by(Pid), Pid ! return, ok. %% Test termination of monitored process holding last resource ref monitor_process_c(Config) -> ensure_lib_loaded(Config), Papa = self(), Pid = spawn_link(fun() -> R_ptr = alloc_monitor_resource_nif(), {0,Mon} = monitor_process_nif(R_ptr, self(), true, Papa), [R_ptr] = monitored_by(self()), put(store, make_resource(R_ptr)), ok = release_resource(R_ptr), [] = last_resource_dtor_call(), Papa ! {self(), done, R_ptr, Mon}, exit end), [{Pid, done, R_ptr, Mon1}, {monitor_resource_down, R_ptr, Pid, Mon2}] = flush(2), compare_monitors_nif(Mon1, Mon2), {R_ptr, _, 1} = last_resource_dtor_call(), ok. %% Test race of resource dtor called when monitored process is exiting monitor_process_d(Config) -> ensure_lib_loaded(Config), Papa = self(), {Target,TRef} = spawn_monitor(fun() -> nothing = receive_any() end), R_ptr = alloc_monitor_resource_nif(), {0,_} = monitor_process_nif(R_ptr, Target, true, self()), [Papa, R_ptr] = monitored_by(Target), exit(Target, die), ok = release_resource(R_ptr), [{'DOWN', TRef, process, Target, die}] = flush(), %% no monitor_resource_down {R_ptr, _, 1} = last_resource_dtor_call(), ok. %% Test basic demonitoring demonitor_process(Config) -> ensure_lib_loaded(Config), Pid = spawn_link(fun() -> receive return -> ok end end), R_ptr = alloc_monitor_resource_nif(), {0,MonBin1} = monitor_process_nif(R_ptr, Pid, true, self()), [R_ptr] = monitored_by(Pid), {0,MonBin2} = monitor_process_nif(R_ptr, Pid, true, self()), [R_ptr, R_ptr] = monitored_by(Pid), 0 = demonitor_process_nif(R_ptr, MonBin1), [R_ptr] = monitored_by(Pid), 1 = demonitor_process_nif(R_ptr, MonBin1), 0 = demonitor_process_nif(R_ptr, MonBin2), [] = monitored_by(Pid), 1 = demonitor_process_nif(R_ptr, MonBin2), ok = release_resource(R_ptr), [] = flush(0), {R_ptr, _, 1} = last_resource_dtor_call(), [] = monitored_by(Pid), Pid ! return, ok. monitored_by(Pid) -> {monitored_by, List0} = process_info(Pid, monitored_by), List1 = lists:map(fun(E) when ?is_resource(E) -> {Ptr, _} = get_resource(monitor_resource_type, E), Ptr; (E) -> E end, List0), erlang:garbage_collect(), lists:sort(List1). -define(FRENZY_RAND_BITS, 25). %% Exercise monitoring from NIF resources by randomly %% create/destruct processes, resources and monitors. monitor_frenzy(Config) -> ensure_lib_loaded(Config), Procs1 = processes(), io:format("~p processes before: ~p\n", [length(Procs1), Procs1]), %% Spawn first worker process Master = self(), spawn_link(fun() -> SelfPix = monitor_frenzy_nif(init, ?FRENZY_RAND_BITS, 0, 0), unlink(Master), frenzy(SelfPix, {undefined, []}) end), receive after 5*1000 -> ok end, io:format("stats = ~p\n", [monitor_frenzy_nif(stats, 0, 0, 0)]), Pids = monitor_frenzy_nif(stop, 0, 0, 0), io:format("stats = ~p\n", [monitor_frenzy_nif(stats, 0, 0, 0)]), lists:foreach(fun(P) -> MRef = monitor(process, P), exit(P, stop), {'DOWN', MRef, process, P, _} = receive_any() end, Pids), io:format("stats = ~p\n", [monitor_frenzy_nif(stats, 0, 0, 0)]), Procs2 = processes(), io:format("~p processes after: ~p\n", [length(Procs2), Procs2]), ok. frenzy(_SelfPix, done) -> ok; frenzy(SelfPix, State0) -> Rnd = rand:uniform(1 bsl (?FRENZY_RAND_BITS+2)) - 1, Op = Rnd band 3, State1 = frenzy_do_op(SelfPix, Op, (Rnd bsr 2), State0), frenzy(SelfPix, State1). frenzy_do_op(SelfPix, Op, Rnd, {Pid0,RBins}=State0) -> case Op of 0 -> % add/remove process Papa = self(), NewPid = case Pid0 of undefined -> % Prepare new process to be added spawn(fun() -> MRef = monitor(process, Papa), case receive_any() of {go, MyPix, MyState} -> demonitor(MRef, [flush]), frenzy(MyPix, MyState); {'DOWN', MRef, process, Papa, _} -> ok end end); _ -> Pid0 end, case monitor_frenzy_nif(Op, Rnd, SelfPix, NewPid) of NewPix when is_integer(NewPix) -> NewPid ! {go, NewPix, {undefined, []}}, {undefined, RBins}; ExitPid when is_pid(ExitPid) -> false = (ExitPid =:= self()), exit(ExitPid,die), {NewPid, RBins}; done -> done end; 3 -> %% Try provoke revival-race of resource from magic ref external format _ = [binary_to_term(B) || B <- RBins], {Pid0, []}; _ -> case monitor_frenzy_nif(Op, Rnd, SelfPix, undefined) of Rsrc when ?is_resource(Rsrc) -> %% Store resource in ext format only, for later revival State1 = {Pid0, [term_to_binary(Rsrc) | RBins]}, gc_and_return(State1); ok -> State0; 0 -> State0; 1 -> State0; done -> done end end. gc_and_return(RetVal) -> erlang:garbage_collect(), RetVal. hipe(Config) when is_list(Config) -> Data = proplists:get_value(data_dir, Config), Priv = proplists:get_value(priv_dir, Config), Src = filename:join(Data, "hipe_compiled"), {ok,hipe_compiled} = c:c(Src, [{outdir,Priv},native]), true = code:is_module_native(hipe_compiled), {error, {notsup,_}} = hipe_compiled:try_load_nif(), true = code:delete(hipe_compiled), false = code:purge(hipe_compiled), ok. %% Test NIF building heap fragments heap_frag(Config) when is_list(Config) -> TmpMem = tmpmem(), ensure_lib_loaded(Config), heap_frag_do(1,1000000), verify_tmpmem(TmpMem), ok. heap_frag_do(N, Max) when N > Max -> ok; heap_frag_do(N, Max) -> io:format("Create list of length ~p\n",[N]), L = lists:seq(1,N), L = list_seq(N), heap_frag_do(((N*5) div 4) + 1, Max). %% Type tests types(Config) when is_list(Config) -> TmpMem = tmpmem(), ensure_lib_loaded(Config), ok = type_test(), lists:foreach(fun(Tpl) -> Lst = erlang:tuple_to_list(Tpl), Lst = tuple_2_list(Tpl) end, [{},{ok},{{}},{[],{}},{1,2,3,4,5}]), Stuff = [[],{},0,0.0,(1 bsl 100),(fun()-> ok end),make_ref(),self()], [eq_cmp(A,clone(B)) || A<-Stuff, B<-Stuff], {IntSz, LongSz} = type_sizes(), UintMax = (1 bsl (IntSz*8)) - 1, IntMax = UintMax bsr 1, IntMin = -(IntMax+1), UlongMax = (1 bsl (LongSz*8)) - 1, LongMax = UlongMax bsr 1, LongMin = -(LongMax+1), Uint64Max = (1 bsl 64) - 1, Int64Max = Uint64Max bsr 1, Int64Min = -(Int64Max+1), Limits = [{IntMin,IntMax},{0,UintMax},{LongMin,LongMax},{0,UlongMax},{Int64Min,Int64Max},{0,Uint64Max}], io:format("Limits = ~p\n", [Limits]), lists:foreach(fun(I) -> R1 = echo_int(I), %%io:format("echo_int(~p) -> ~p\n", [I, R1]), R2 = my_echo_int(I, Limits), R1 = R2, true = (R1 =:= R2), true = (R1 == R2) end, int_list()), verify_tmpmem(TmpMem), true = (compare(-1294536544000, -1178704800000) < 0), true = (compare(-1178704800000, -1294536544000) > 0), true = (compare(-295147905179352825856, -36893488147419103232) < 0), true = (compare(-36893488147419103232, -295147905179352825856) > 0), true = (compare(-29514790517935282585612345678, -36893488147419103232) < 0), true = (compare(-36893488147419103232, -29514790517935282585612345678) > 0), ok. int_list() -> Start = 1 bsl 200, int_list([Start], -Start). int_list([N | _]=List, End) when N List; int_list([N | _]=List, End) -> int_list([N - (1 + (abs(N) div 3)) | List], End). my_echo_int(I, Limits) -> lists:map(fun({Min,Max}) -> if I < Min -> false; I > Max -> false; true -> I end end, Limits). clone(X) -> binary_to_term(term_to_binary(X)). eq_cmp(A,B) -> eq_cmp_do(A,B), eq_cmp_do([A,B],[A,B]), eq_cmp_do({A,B},{A,B}). eq_cmp_do(A,B) -> %%io:format("compare ~p and ~p\n",[A,B]), Eq = (A =:= B), Eq = is_identical(A,B), Cmp = if A < B -> -1; A == B -> 0; A > B -> 1 end, Cmp = case compare(A,B) of C when is_integer(C), C < 0 -> -1; 0 -> 0; C when is_integer(C) -> 1 end, ok. %% Test NIF with many arguments many_args(Config) when is_list(Config) -> TmpMem = tmpmem(), ensure_lib_loaded(Config ,1), ok = apply(?MODULE,many_args_100,lists:seq(1,100)), ok = many_args_100(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100), verify_tmpmem(TmpMem), ok. %% Test NIF binary handling. binaries(Config) when is_list(Config) -> TmpMem = tmpmem(), ensure_lib_loaded(Config, 1), RefcBin = list_to_binary(lists:seq(1,255)), RefcBin = clone_bin(RefcBin), HeapBin = list_to_binary(lists:seq(1,20)), HeapBin = clone_bin(HeapBin), <<_:8,Sub1:6/binary,_/binary>> = RefcBin, <<_:8,Sub2:6/binary,_/binary>> = HeapBin, Sub1 = Sub2, Sub1 = clone_bin(Sub1), Sub2 = clone_bin(Sub2), <<_:9,Sub3:6/binary,_/bitstring>> = RefcBin, <<_:9,Sub4:6/binary,_/bitstring>> = HeapBin, Sub3 = Sub4, Sub3 = clone_bin(Sub3), Sub4 = clone_bin(Sub4), %% When NIFs get bitstring support %%<<_:8,Sub5:27/bitstring,_/bitstring>> = RefcBin, %%<<_:8,Sub6:27/bitstring,_/bitstring>> = HeapBin, %%Sub5 = Sub6, %%Sub5 = clone_bin(Sub5), %%Sub6 = clone_bin(Sub6), %%<<_:9,Sub7:27/bitstring,_/bitstring>> = RefcBin, %%<<_:9,Sub8:27/bitstring,_/bitstring>> = HeapBin, %%Sub7 = Sub8, %%Sub7 = clone_bin(Sub7), %%Sub8 = clone_bin(Sub8), %%<<>> = clone_bin(<<>>), <<_:8,SubBinA:200/binary,_/binary>> = RefcBin, <<_:9,SubBinB:200/binary,_/bitstring>> = RefcBin, <<_:8,SubBinC:17/binary,_/binary>> = HeapBin, <<_:9,SubBinD:17/binary,_/bitstring>> = HeapBin, test_make_sub_bin(RefcBin), test_make_sub_bin(HeapBin), test_make_sub_bin(SubBinA), test_make_sub_bin(SubBinB), test_make_sub_bin(SubBinC), test_make_sub_bin(SubBinD), verify_tmpmem(TmpMem), ok. test_make_sub_bin(Bin) -> Size = byte_size(Bin), Rest10 = Size - 10, Rest1 = Size - 1, Bin = make_sub_bin(Bin, 0, Size), <<_:10/binary,Sub0:Rest10/binary>> = Bin, Sub0 = make_sub_bin(Bin, 10, Rest10), <> = Bin, Sub1 = make_sub_bin(Bin, 0, 10), <<_:7/binary,Sub2:10/binary,_/binary>> = Bin, Sub2 = make_sub_bin(Bin, 7, 10), <<>> = make_sub_bin(Bin, 0, 0), <<>> = make_sub_bin(Bin, 10, 0), <<>> = make_sub_bin(Bin, Rest1, 0), <<>> = make_sub_bin(Bin, Size, 0), ok. %% Test enif_get_string get_string(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), {7, <<"hejsan",0,_:3/binary>>} = string_to_bin("hejsan",10), {7, <<"hejsan",0,_>>} = string_to_bin("hejsan",8), {7, <<"hejsan",0>>} = string_to_bin("hejsan",7), {-6, <<"hejsa",0>>} = string_to_bin("hejsan",6), {-5, <<"hejs",0>>} = string_to_bin("hejsan",5), {-1, <<0>>} = string_to_bin("hejsan",1), {0, <<>>} = string_to_bin("hejsan",0), {1, <<0>>} = string_to_bin("",1), {0, <<>>} = string_to_bin("",0), ok. %% Test enif_get_atom get_atom(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), {7, <<"hejsan",0,_:3/binary>>} = atom_to_bin(hejsan,10), {7, <<"hejsan",0,_>>} = atom_to_bin(hejsan,8), {7, <<"hejsan",0>>} = atom_to_bin(hejsan,7), {0, <<_:6/binary>>} = atom_to_bin(hejsan,6), {0, <<>>} = atom_to_bin(hejsan,0), {1, <<0>>} = atom_to_bin('',1), {0, <<>>} = atom_to_bin('',0), ok. %% Test NIF maps handling. maps(Config) when is_list(Config) -> TmpMem = tmpmem(), Pairs = [{adam, "bert"}] ++ [{I,I}||I <- lists:seq(1,10)] ++ [{a,value},{"a","value"},{<<"a">>,<<"value">>}], ok = ensure_lib_loaded(Config, 1), M = maps_from_list_nif(Pairs), R = {RIs,Is} = sorted_list_from_maps_nif(M), io:format("Pairs: ~p~nMap: ~p~nReturned: ~p~n", [lists:sort(Pairs),M,R]), true = (lists:sort(Is) =:= lists:sort(Pairs)), Is = lists:reverse(RIs), #{} = maps_from_list_nif([]), {[],[]} = sorted_list_from_maps_nif(#{}), 1 = is_map_nif(M), 0 = is_map_nif("no map"), Msz = map_size(M), {1,Msz} = get_map_size_nif(M), {1,0} = get_map_size_nif(#{}), {0,-123} = get_map_size_nif({#{}}), #{} = M0 = make_new_map_nif(), {1, #{key := value}=M1} = make_map_put_nif(M0, key, value), {1, #{key := value, "key2" := "value2"}=M2} = make_map_put_nif(M1, "key2", "value2"), {1, #{key := "value", "key2" := "value2"}=M3} = make_map_put_nif(M2, key, "value"), {0, undefined} = make_map_put_nif(666, key, value), {1, "value2"} = get_map_value_nif(M3,"key2"), {0, undefined} = get_map_value_nif(M3,"key3"), {0, undefined} = get_map_value_nif(false,key), {0, undefined} = make_map_update_nif(M0, key, value), {0, undefined} = make_map_update_nif(M1, "key2", "value2"), {1, #{key := "value", "key2" := "value2"}} = make_map_update_nif(M2, key, "value"), {0, undefined} = make_map_update_nif(666, key, value), {1, #{}} = make_map_remove_nif(M1, key), {1, M1} = make_map_remove_nif(M2, "key2"), {1, M2} = make_map_remove_nif(M2, "key3"), {0, undefined} = make_map_remove_nif(self(), key), verify_tmpmem(TmpMem), ok. %% Test macros enif_make_list and enif_make_tuple api_macros(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), Expected = {[lists:seq(1,N) || N <- lists:seq(1,9)], [list_to_tuple(lists:seq(1,N)) || N <- lists:seq(1,9)] }, Expected = macros(list_to_tuple(lists:seq(1,9))), ok. %% enif_make_[tuple|list]_from_array from_array(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), lists:foreach(fun(Tpl) -> Lst = tuple_to_list(Tpl), {Lst,Tpl} = tuple_2_list_and_tuple(Tpl) end, [{}, {1,2,3}, {[4,5],[],{},{6,7}}, {{}}, {[]}]), ok. %% enif_inspect_iolist_as_binary iolist_as_binary(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), TmpMem = tmpmem(), List = [<<"hejsan">>, <<>>, [], [17], [<<>>], [127,128,255,0], [1, 2, 3, <<"abc">>, [<<"def">>,4], 5, <<"ghi">>], [1, 2, 3, <<"abc">>, [<<"def">>,4], 5 | <<"ghi">>]], lists:foreach(fun(IoL) -> B1 = erlang:iolist_to_binary(IoL), B2 = iolist_2_bin(IoL), B1 = B2 end, List), verify_tmpmem(TmpMem), ok. %% Test memory managed objects, aka 'resources' resource(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), Type = get_resource_type(0), resource_hugo(Type), resource_otto(Type), resource_new(Type), resource_neg(Type), ok. resource_hugo(Type) -> DtorCall = resource_hugo_do(Type), erlang:garbage_collect(), DtorCall = last_resource_dtor_call(), ok. resource_hugo_do(Type) -> HugoBin = <<"Hugo Hacker">>, HugoPtr = alloc_resource(Type, HugoBin), Hugo = make_resource(HugoPtr), true = is_reference(Hugo), release_resource(HugoPtr), erlang:garbage_collect(), {HugoPtr,HugoBin} = get_resource(Type,Hugo), Pid = spawn_link(fun() -> receive {Pid, Type, Resource, Ptr, Bin} -> Pid ! {self(), got_it}, receive {Pid, check_it} -> {Ptr,Bin} = get_resource(Type,Resource), Pid ! {self(), ok} end end end), Pid ! {self(), Type, Hugo, HugoPtr, HugoBin}, {Pid, got_it} = receive_any(), erlang:garbage_collect(), % just to make our ProcBin move in memory Pid ! {self(), check_it}, {Pid, ok} = receive_any(), [] = last_resource_dtor_call(), {HugoPtr,HugoBin} = get_resource(Type,Hugo), {HugoPtr, HugoBin, 1}. resource_otto(Type) -> {OttoPtr, OttoBin} = resource_otto_do(Type), erlang:garbage_collect(), [] = last_resource_dtor_call(), release_resource(OttoPtr), {OttoPtr,OttoBin,1} = last_resource_dtor_call(), ok. resource_otto_do(Type) -> OttoBin = <<"Otto Ordonnans">>, OttoPtr = alloc_resource(Type, OttoBin), Otto = make_resource(OttoPtr), true = is_reference(Otto), %% forget resource term but keep referenced by NIF {OttoPtr, OttoBin}. resource_new(Type) -> {PtrB,BinB} = resource_new_do1(Type), erlang:garbage_collect(), {PtrB,BinB,1} = last_resource_dtor_call(), ok. resource_new_do1(Type) -> {{PtrA,BinA}, {ResB,PtrB,BinB}} = resource_new_do2(Type), erlang:garbage_collect(), {PtrA,BinA,1} = last_resource_dtor_call(), {PtrB,BinB} = get_resource(Type, ResB), %% forget ResB and make it garbage {PtrB,BinB}. resource_new_do2(Type) -> BinA = <<"NewA">>, BinB = <<"NewB">>, ResA = make_new_resource(Type, BinA), ResB = make_new_resource(Type, BinB), true = is_reference(ResA), true = is_reference(ResB), true = (ResA /= ResB), {PtrA,BinA} = get_resource(Type, ResA), {PtrB,BinB} = get_resource(Type, ResB), true = (PtrA =/= PtrB), %% forget ResA and make it garbage {{PtrA,BinA}, {ResB,PtrB,BinB}}. resource_neg(TypeA) -> resource_neg_do(TypeA), catch exit(42), % dummy exception to purge saved stacktraces from earlier exception erlang:garbage_collect(), {_,_,2} = last_resource_dtor_call(), ok. resource_neg_do(TypeA) -> TypeB = get_resource_type(1), ResA = make_new_resource(TypeA, <<"Arnold">>), ResB= make_new_resource(TypeB, <<"Bobo">>), {'EXIT',{badarg,_}} = (catch get_resource(TypeA, ResB)), {'EXIT',{badarg,_}} = (catch get_resource(TypeB, ResA)), ok. %% Test enif_make_resource_binary resource_binary(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), {Ptr,Bin} = resource_binary_do(), erlang:garbage_collect(), Last = last_resource_dtor_call(), ?CHECK({Ptr,Bin,1}, Last), ok. resource_binary_do() -> Bin = <<"Hej Hopp i lingonskogen">>, {Ptr,ResBin1} = make_new_resource_binary(Bin), ResBin1 = Bin, ResInfo = {Ptr,_} = get_resource(binary_resource_type,ResBin1), Papa = self(), Forwarder = spawn_link(fun() -> forwarder(Papa) end), io:format("sending to forwarder pid=~p\n",[Forwarder]), Forwarder ! ResBin1, ResBin2 = receive_any(), ResBin2 = ResBin1, ResInfo = get_resource(binary_resource_type,ResBin2), Forwarder ! terminate, {Forwarder, 1} = receive_any(), erlang:garbage_collect(), ResInfo = get_resource(binary_resource_type,ResBin1), ResInfo = get_resource(binary_resource_type,ResBin2), ResInfo. -define(RT_CREATE,1). -define(RT_TAKEOVER,2). %% Test resource takeover by module upgrade resource_takeover(Config) when is_list(Config) -> TmpMem = tmpmem(), ensure_lib_loaded(Config), Data = proplists:get_value(data_dir, Config), File = filename:join(Data, "nif_mod"), {ok,nif_mod,ModBin} = compile:file(File, [binary,return_errors]), {module,nif_mod} = erlang:load_module(nif_mod,ModBin), ok = nif_mod:load_nif_lib(Config, 1, [{resource_type, 0, ?RT_CREATE, "resource_type_A",resource_dtor_A, ?RT_CREATE}, {resource_type, 1, ?RT_CREATE, "resource_type_null_A",null, ?RT_CREATE}, {resource_type, 2, ?RT_CREATE bor ?RT_TAKEOVER, "resource_type_A_null",resource_dtor_A, ?RT_CREATE}, {resource_type, 3, ?RT_CREATE, "resource_type_B_goneX",resource_dtor_B, ?RT_CREATE}, {resource_type, 4, ?RT_CREATE, "resource_type_null_goneX",null, ?RT_CREATE}, {resource_type, null, ?RT_TAKEOVER, "Pink unicorn", resource_dtor_A, ?RT_TAKEOVER} ]), hold_nif_mod_priv_data(nif_mod:get_priv_data_ptr()), [{load,1,1,101},{get_priv_data_ptr,1,2,102}] = nif_mod_call_history(), {Holder, _MRef} = spawn_opt(fun resource_holder/0, [link, monitor]), {A1,BinA1} = make_resource(0,Holder,"A1"), {A2,BinA2} = make_resource(0,Holder,"A2"), {A3,BinA3} = make_resource(0,Holder,"A3"), {NA1,_BinNA1} = make_resource(1,Holder,"NA1"), {NA2,BinNA2} = make_resource(1,Holder,"NA2"), {NA3,_BinNA3} = make_resource(1,Holder,"NA3"), {AN1,BinAN1} = make_resource(2,Holder,"AN1"), {AN2,_BinAN2} = make_resource(2,Holder,"AN2"), {AN3,BinAN3} = make_resource(2,Holder,"AN3"), {BGX1,BinBGX1} = make_resource(3,Holder,"BGX1"), {BGX2,BinBGX2} = make_resource(3,Holder,"BGX2"), {NGX1,_BinNGX1} = make_resource(4,Holder,"NGX1"), {NGX2,_BinNGX2} = make_resource(4,Holder,"NGX2"), [] = nif_mod_call_history(), ok = forget_resource(A1), [{{resource_dtor_A_v1,BinA1},1,3,103}] = nif_mod_call_history(), ok = forget_resource(NA1), [] = nif_mod_call_history(), % no dtor ok = forget_resource(AN1), ?CHECK([{{resource_dtor_A_v1,BinAN1},1,4,104}] , nif_mod_call_history()), ok = forget_resource(BGX1), ?CHECK([{{resource_dtor_B_v1,BinBGX1},1,5,105}], nif_mod_call_history()), ok = forget_resource(NGX1), ?CHECK([], nif_mod_call_history()), % no dtor {module,nif_mod} = erlang:load_module(nif_mod,ModBin), ok = nif_mod:load_nif_lib(Config, 2, [{resource_type, 0, ?RT_TAKEOVER, "resource_type_A",resource_dtor_A, ?RT_TAKEOVER}, {resource_type, 1, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A",resource_dtor_A, ?RT_TAKEOVER}, {resource_type, 2, ?RT_TAKEOVER, "resource_type_A_null",null, ?RT_TAKEOVER}, {resource_type, null, ?RT_TAKEOVER, "Pink unicorn", resource_dtor_A, ?RT_TAKEOVER}, {resource_type, null, ?RT_CREATE, "resource_type_B_goneX",resource_dtor_B, ?RT_CREATE}, {resource_type, null, ?RT_CREATE, "resource_type_null_goneX",null, ?RT_CREATE}, {resource_type, 3, ?RT_CREATE, "resource_type_B_goneY",resource_dtor_B, ?RT_CREATE}, {resource_type, 4, ?RT_CREATE, "resource_type_null_goneY",null, ?RT_CREATE} ]), ?CHECK([{upgrade,2,1,201}], nif_mod_call_history()), true = erlang:purge_module(nif_mod), ?CHECK([], nif_mod_call_history()), % BGX2 keeping lib loaded BinA2 = read_resource(0,A2), ok = forget_resource(A2), ?CHECK([{{resource_dtor_A_v2,BinA2},2,2,202}], nif_mod_call_history()), ok = forget_resource(NA2), ?CHECK([{{resource_dtor_A_v2,BinNA2},2,3,203}], nif_mod_call_history()), ok = forget_resource(AN2), ?CHECK([], nif_mod_call_history()), % no dtor ok = forget_resource(BGX2), % calling dtor in orphan library v1 still loaded ?CHECK([{{resource_dtor_B_v1,BinBGX2},1,6,106}, {unload,1,7,107}], nif_mod_call_history()), ok = forget_resource(NGX2), ?CHECK([], nif_mod_call_history()), % no dtor {BGY1,BinBGY1} = make_resource(3,Holder,"BGY1"), {NGY1,_BinNGY1} = make_resource(4,Holder,"NGY1"), %% Module upgrade with same lib-version {module,nif_mod} = erlang:load_module(nif_mod,ModBin), undefined = nif_mod:lib_version(), ok = nif_mod:load_nif_lib(Config, 2, [{resource_type, 2, ?RT_TAKEOVER, "resource_type_A",resource_dtor_B, ?RT_TAKEOVER}, {resource_type, 0, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A",null, ?RT_TAKEOVER}, {resource_type, 1, ?RT_TAKEOVER, "resource_type_A_null",resource_dtor_A, ?RT_TAKEOVER}, {resource_type, null, ?RT_TAKEOVER, "Pink elephant", resource_dtor_A, ?RT_TAKEOVER}, {resource_type, 3, ?RT_CREATE, "resource_type_B_goneZ",resource_dtor_B, ?RT_CREATE}, {resource_type, 4, ?RT_CREATE, "resource_type_null_goneZ",null, ?RT_CREATE} ]), 2 = nif_mod:lib_version(), ?CHECK([{upgrade,2,4,204},{lib_version,2,5,205}], nif_mod_call_history()), ok = forget_resource(A3), ?CHECK([{{resource_dtor_B_v2,BinA3},2,6,206}], nif_mod_call_history()), ok = forget_resource(NA3), ?CHECK([], nif_mod_call_history()), ok = forget_resource(AN3), ?CHECK([{{resource_dtor_A_v2,BinAN3},2,7,207}], nif_mod_call_history()), {A4,BinA4} = make_resource(2,Holder, "A4"), {NA4,BinNA4} = make_resource(0,Holder, "NA4"), {AN4,_BinAN4} = make_resource(1,Holder, "AN4"), {BGZ1,BinBGZ1} = make_resource(3,Holder,"BGZ1"), {NGZ1,_BinNGZ1} = make_resource(4,Holder,"NGZ1"), false = code:purge(nif_mod), [] = nif_mod_call_history(), ok = forget_resource(NGY1), [] = nif_mod_call_history(), ok = forget_resource(BGY1), % calling dtor in orphan library v2 still loaded [{{resource_dtor_B_v2,BinBGY1},2,8,208},{unload,2,9,209}] = nif_mod_call_history(), %% Module upgrade with other lib-version {module,nif_mod} = erlang:load_module(nif_mod,ModBin), undefined = nif_mod:lib_version(), ok = nif_mod:load_nif_lib(Config, 1, [{resource_type, 2, ?RT_TAKEOVER, "resource_type_A",resource_dtor_A, ?RT_TAKEOVER}, {resource_type, 0, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A",resource_dtor_A, ?RT_TAKEOVER}, {resource_type, 1, ?RT_TAKEOVER, "resource_type_A_null",null, ?RT_TAKEOVER}, {resource_type, null, ?RT_TAKEOVER, "Mr Pink", resource_dtor_A, ?RT_TAKEOVER} ]), 1 = nif_mod:lib_version(), [{upgrade,1,1,101},{lib_version,1,2,102}] = nif_mod_call_history(), %%false= check_process_code(Pid, nif_mod), false = code:purge(nif_mod), %% no unload here as we still have instances with destructors [] = nif_mod_call_history(), ok = forget_resource(BGZ1), % calling dtor in orphan library v2 still loaded [{{resource_dtor_B_v2,BinBGZ1},2,10,210},{unload,2,11,211}] = nif_mod_call_history(), ok = forget_resource(NGZ1), [] = nif_mod_call_history(), ok = forget_resource(A4), [{{resource_dtor_A_v1,BinA4},1,3,103}] = nif_mod_call_history(), ok = forget_resource(NA4), [{{resource_dtor_A_v1,BinNA4},1,4,104}] = nif_mod_call_history(), ok = forget_resource(AN4), [] = nif_mod_call_history(), %% %% Test rollback after failed upgrade of same lib-version %% {A5,BinA5} = make_resource(2, Holder, "A5"), {NA5,BinNA5} = make_resource(0, Holder, "NA5"), {AN5,_BinAN5} = make_resource(1, Holder, "AN5"), {A6,BinA6} = make_resource(2, Holder, "A6"), {NA6,BinNA6} = make_resource(0, Holder, "NA6"), {AN6,_BinAN6} = make_resource(1, Holder, "AN6"), {module,nif_mod} = erlang:load_module(nif_mod,ModBin), undefined = nif_mod:lib_version(), {error,{upgrade,_}} = nif_mod:load_nif_lib(Config, 1, [{resource_type, 4, ?RT_TAKEOVER, "resource_type_A",resource_dtor_B, ?RT_TAKEOVER}, {resource_type, 4, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A",null, ?RT_TAKEOVER}, {resource_type, 4, ?RT_TAKEOVER, "resource_type_A_null",resource_dtor_A, ?RT_TAKEOVER}, {resource_type, 4, ?RT_CREATE, "Mr Pink", resource_dtor_A, ?RT_CREATE}, {return, 1} % FAIL ]), undefined = nif_mod:lib_version(), [{upgrade,1,5,105}] = nif_mod_call_history(), %% Make sure dtor was not changed (from A to B) ok = forget_resource(A5), [{{resource_dtor_A_v1,BinA5},1,6,106}] = nif_mod_call_history(), %% Make sure dtor was not nullified (from A to null) ok = forget_resource(NA5), [{{resource_dtor_A_v1,BinNA5},1,7,107}] = nif_mod_call_history(), %% Make sure dtor was not added (from null to A) ok = forget_resource(AN5), [] = nif_mod_call_history(), %% %% Test rollback after failed upgrade of other lib-version %% {error,{upgrade,_}} = nif_mod:load_nif_lib(Config, 2, [{resource_type, 4, ?RT_TAKEOVER, "resource_type_A",resource_dtor_B, ?RT_TAKEOVER}, {resource_type, 4, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A",null, ?RT_TAKEOVER}, {resource_type, 4, ?RT_TAKEOVER, "resource_type_A_null",resource_dtor_A, ?RT_TAKEOVER}, {resource_type, null, ?RT_TAKEOVER, "Mr Pink", resource_dtor_A, ?RT_TAKEOVER}, {resource_type, 4, ?RT_CREATE, "Mr Pink", resource_dtor_A, ?RT_CREATE}, {return, 1} % FAIL ]), undefined = nif_mod:lib_version(), [{upgrade,2,_,_}] = nif_mod_call_history(), %% Make sure dtor was not changed (from A to B) ok = forget_resource(A6), [{{resource_dtor_A_v1,BinA6},1,_,_}] = nif_mod_call_history(), %% Make sure dtor was not nullified (from A to null) ok = forget_resource(NA6), [{{resource_dtor_A_v1,BinNA6},1,_,_}] = nif_mod_call_history(), %% Make sure dtor was not added (from null to A) ok = forget_resource(AN6), [] = nif_mod_call_history(), %% %% Test rolback after failed initial load %% false = code:purge(nif_mod), [{unload,1,_,_}] = nif_mod_call_history(), true = code:delete(nif_mod), false = code:purge(nif_mod), [] = nif_mod_call_history(), {module,nif_mod} = erlang:load_module(nif_mod,ModBin), undefined = nif_mod:lib_version(), {error,{load,_}} = nif_mod:load_nif_lib(Config, 1, [{resource_type, null, ?RT_TAKEOVER, "resource_type_A",resource_dtor_A, ?RT_TAKEOVER}, {resource_type, 4, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A",null, ?RT_CREATE}, {resource_type, 4, ?RT_CREATE, "resource_type_A_null",resource_dtor_A, ?RT_CREATE}, {resource_type, 4, ?RT_CREATE, "Mr Pink", resource_dtor_A, ?RT_CREATE}, {return, 1} % FAIL ]), undefined = nif_mod:lib_version(), ok = nif_mod:load_nif_lib(Config, 1, [{resource_type, null, ?RT_TAKEOVER, "resource_type_A",resource_dtor_A, ?RT_TAKEOVER}, {resource_type, 0, ?RT_TAKEOVER bor ?RT_CREATE, "resource_type_null_A", resource_dtor_A, ?RT_CREATE}, {resource_type, 1, ?RT_CREATE, "resource_type_A_null", null, ?RT_CREATE}, {resource_type, null, ?RT_TAKEOVER, "Mr Pink", resource_dtor_A, ?RT_TAKEOVER}, {return, 0} % SUCCESS ]), hold_nif_mod_priv_data(nif_mod:get_priv_data_ptr()), [{load,1,1,101},{get_priv_data_ptr,1,2,102}] = nif_mod_call_history(), {NA7,BinNA7} = make_resource(0, Holder, "NA7"), {AN7,_BinAN7} = make_resource(1, Holder, "AN7"), ok = forget_resource(NA7), [{{resource_dtor_A_v1,BinNA7},1,_,_}] = nif_mod_call_history(), ok = forget_resource(AN7), [] = nif_mod_call_history(), true = erlang:delete_module(nif_mod), true = erlang:purge_module(nif_mod), true = lists:member(?MODULE, erlang:system_info(taints)), true = lists:member(nif_mod, erlang:system_info(taints)), verify_tmpmem(TmpMem), ok. make_resource(Type,Holder,Str) when is_list(Str) -> Bin = list_to_binary(Str), A1 = make_resource_do(Type,Holder,Bin), Bin = read_resource(Type,A1), {A1,Bin}. make_resource_do(Type, Holder, Bin) -> Holder ! {self(), make, Type, Bin}, {Holder, make_ok, Id} = receive_any(), {Holder,Id}. read_resource(Type, {Holder,Id}) -> Holder ! {self(), get, Type, Id}, {Holder, get_ok, Bin} = receive_any(), Bin. forget_resource({Holder,Id}) -> Holder ! {self(), forget, Id}, {Holder, forget_ok, Id} = receive_any(), ok. resource_holder() -> resource_holder([]). resource_holder(List) -> %%io:format("resource_holder waiting for msg\n", []), Msg = receive_any(), %%io:format("resource_holder got ~p with list = ~p\n", [Msg,List]), case Msg of {Pid, make, Type, Bin} -> Resource = nif_mod:make_new_resource(Type, Bin), Id = {make_ref(),Bin}, Pid ! {self(), make_ok, Id}, resource_holder([{Id,Resource} | List]); {Pid, get, Type, Id} -> {Id,Resource} = lists:keyfind(Id, 1, List), Pid ! {self(), get_ok, nif_mod:get_resource(Type, Resource)}, resource_holder(List); {Pid, forget, Id} -> NewList = lists:keydelete(Id, 1, List), %%io:format("resource_holder forget: NewList = ~p\n", [NewList]), resource_holder(Pid, {self(),forget_ok,Id}, NewList) end. resource_holder(Pid,Reply,List) -> erlang:garbage_collect(), %%io:format("resource_holder GC'ed, now send ~p to ~p\n", [Reply,Pid]), Pid ! Reply, resource_holder(List). %% Test the threading API functions (reuse tests from driver API) threading(Config) when is_list(Config) -> case erlang:system_info(threads) of true -> threading_do(Config); false -> {skipped,"No thread support"} end. threading_do(Config) -> Data = proplists:get_value(data_dir, Config), File = filename:join(Data, "tester"), {ok,tester,ModBin} = compile:file(File, [binary,return_errors]), {module,tester} = erlang:load_module(tester,ModBin), ok = tester:load_nif_lib(Config, "basic"), ok = tester:run(), erlang:load_module(tester,ModBin), erlang:purge_module(tester), ok = tester:load_nif_lib(Config, "rwlock"), ok = tester:run(), erlang:load_module(tester,ModBin), erlang:purge_module(tester), ok = tester:load_nif_lib(Config, "tsd"), ok = tester:run(), erlang:delete_module(tester), erlang:purge_module(tester). %% Test NIF message sending send(Config) when is_list(Config) -> ensure_lib_loaded(Config), N = 1500, List = lists:seq(1,N), {ok,1} = send_list_seq(N, self), {ok,1} = send_list_seq(N, self()), List = receive_any(), List = receive_any(), Papa = self(), spawn_link(fun() -> {ok,1} = send_list_seq(N, Papa) end), List = receive_any(), {ok, 1, BlobS} = send_new_blob(self(), other_term()), BlobR = receive_any(), io:format("Sent ~p\nGot ~p\n", [BlobS, BlobR]), BlobR = BlobS, %% send to dead pid {DeadPid, DeadMon} = spawn_monitor(fun() -> void end), {'DOWN', DeadMon, process, DeadPid, normal} = receive_any(), {ok,0} = send_list_seq(7, DeadPid), ok. %% More NIF message sending send2(Config) when is_list(Config) -> ensure_lib_loaded(Config), send2_do1(fun send_blob_dbg/2), ok. %% Send msg from user thread send_threaded(Config) when is_list(Config) -> case erlang:system_info(smp_support) of true -> send2_do1(fun(ME,To) -> send_blob_thread_dbg(ME,To,join) end), send2_do1(fun(ME,To) -> send_blob_thread_and_join(ME,To) end), ok; false -> {skipped,"No threaded send on non-SMP"} end. send2_do1(SendBlobF) -> io:format("sending to self=~p\n",[self()]), send2_do2(SendBlobF, self()), Papa = self(), Forwarder = spawn_link(fun() -> forwarder(Papa) end), io:format("sending to forwarder pid=~p\n",[Forwarder]), send2_do2(SendBlobF, Forwarder), Forwarder ! terminate, {Forwarder, 4} = receive_any(), ok. send2_do2(SendBlobF, To) -> MsgEnv = alloc_msgenv(), repeat(50, fun(_) -> grow_blob(MsgEnv,other_term()) end, []), {ok,1,Blob0} = SendBlobF(MsgEnv, To), Blob1 = receive_any(), Blob1 = Blob0, clear_msgenv(MsgEnv), repeat(50, fun(_) -> grow_blob(MsgEnv,other_term()) end, []), {ok,1,Blob2} = SendBlobF(MsgEnv, To), Blob3 = receive_any(), Blob3 = Blob2, clear_msgenv(MsgEnv), repeat(50, fun(_) -> grow_blob(MsgEnv,other_term()) end, []), clear_msgenv(MsgEnv), repeat(50, fun(_) -> grow_blob(MsgEnv,other_term()) end, []), {ok,1,Blob4} = SendBlobF(MsgEnv, To), Blob5 = receive_any(), Blob5 = Blob4, clear_msgenv(MsgEnv), clear_msgenv(MsgEnv), repeat(50, fun(_) -> grow_blob(MsgEnv,other_term()) end, []), {ok,1,Blob6} = SendBlobF(MsgEnv, To), Blob7 = receive_any(), Blob7 = Blob6, ok. send_blob_thread_and_join(MsgEnv, To) -> {ok,Blob} = send_blob_thread_dbg(MsgEnv, To, no_join), {ok,SendRes} = join_send_thread(MsgEnv), {ok,SendRes,Blob}. send_blob_dbg(MsgEnv, To) -> Ret = send_blob(MsgEnv, To), %%io:format("send_blob to ~p returned ~p\n",[To,Ret]), Ret. send_blob_thread_dbg(MsgEnv, To, Join) -> Ret = send_blob_thread(MsgEnv, To, Join), %%io:format("send_blob_thread to ~p Join=~p returned ~p\n",[To,Join,Ret]), Ret. forwarder(To) -> forwarder(To, 0). forwarder(To, N) -> case receive_any() of terminate -> To ! {self(), N}; Msg -> To ! Msg, forwarder(To, N+1) end. other_term() -> {fun(X,Y) -> X*Y end, make_ref()}. %% Message sending stress test send3(Config) when is_list(Config) -> %% Let a number of processes send random message blobs between each other %% using enif_send. Kill and spawn new ones randomly to keep a ~constant %% number of workers running. rand:seed(exsplus), io:format("seed: ~p\n",[rand:export_seed()]), ets:new(nif_SUITE,[named_table,public]), true = ets:insert(nif_SUITE,{send3,0,0,0,0}), timer:send_after(10000, timeout), % Run for 10 seconds SpawnCnt = send3_controller(0, [], [], 20), [{_,Rcv,SndOk,SndFail,Balance}] = ets:lookup(nif_SUITE,send3), io:format("spawns=~p received=~p, sent=~p send-failure=~p balance=~p\n", [SpawnCnt,Rcv,SndOk,SndFail,Balance]), ets:delete(nif_SUITE). send3_controller(SpawnCnt, [], _, infinity) -> SpawnCnt; send3_controller(SpawnCnt0, Mons0, Pids0, Tick) -> receive timeout -> io:format("Timeout. Sending 'halt' to ~p\n",[Pids0]), lists:foreach(fun(P) -> P ! {halt,self()} end, Pids0), lists:foreach(fun(P) -> receive {halted,P} -> ok end end, Pids0), QTot = lists:foldl(fun(P,QSum) -> {message_queue_len,QLen} = erlang:process_info(P,message_queue_len), QSum + QLen end, 0, Pids0), io:format("Total queue length ~p\n",[QTot]), lists:foreach(fun(P) -> P ! die end, Pids0), send3_controller(SpawnCnt0, Mons0, [], infinity); {'DOWN', MonRef, process, _Pid, _} -> Mons1 = lists:delete(MonRef, Mons0), %%io:format("Got DOWN from ~p. Monitors left: ~p\n",[Pid,Mons1]), send3_controller(SpawnCnt0, Mons1, Pids0, Tick) after Tick -> Max = 20, N = length(Pids0), PidN = rand:uniform(Max), %%io:format("N=~p PidN=~p Pids0=~p\n", [N,PidN,Pids0]), case PidN > N of true -> {NewPid,Mon} = spawn_opt(fun send3_proc/0, [link,monitor]), lists:foreach(fun(P) -> P ! {is_born,NewPid} end, Pids0), Balance = ets:lookup_element(nif_SUITE,send3,5), Inject = (Balance =< 0), case Inject of true -> ok; false -> ets:update_element(nif_SUITE,send3,{5,-1}) end, NewPid ! {pids,Pids0,Inject}, send3_controller(SpawnCnt0+1, [Mon|Mons0], [NewPid|Pids0], Tick); false -> KillPid = lists:nth(PidN,Pids0), KillPid ! die, Pids1 = lists:delete(KillPid, Pids0), lists:foreach(fun(P) -> P ! {is_dead,KillPid} end, Pids1), send3_controller(SpawnCnt0, Mons0, Pids1, Tick) end end. send3_proc() -> %%io:format("Process ~p spawned\n",[self()]), send3_proc([self()], {0,0,0}, {1,2,3,4,5}). send3_proc(Pids0, Counters={Rcv,SndOk,SndFail}, State0) -> %%io:format("~p: Pids0=~p", [self(), Pids0]), %%timer:sleep(10), receive {pids, Pids1, Inject} -> %%io:format("~p: got ~p Inject=~p\n", [self(), Pids1, Inject]), Pids0 = [self()], Pids2 = [self() | Pids1], case Inject of true -> send3_proc_send(Pids2, Counters, State0); false -> send3_proc(Pids2, Counters, State0) end; {is_born, Pid} -> %%io:format("~p: is_born ~p, got ~p\n", [self(), Pid, Pids0]), send3_proc([Pid | Pids0], Counters, State0); {is_dead, Pid} -> Pids1 = lists:delete(Pid,Pids0), %%io:format("~p: is_dead ~p, got ~p\n", [self(), Pid, Pids1]), send3_proc(Pids1, Counters, State0); {blob, Blob0} -> %%io:format("~p: blob ~p\n", [self(), Blob0]), State1 = send3_new_state(State0, Blob0), send3_proc_send(Pids0, {Rcv+1,SndOk,SndFail}, State1); die -> %%io:format("Process ~p terminating, stats = ~p\n",[self(),Counters]), {message_queue_len,Dropped} = erlang:process_info(self(),message_queue_len), _R = ets:update_counter(nif_SUITE,send3, [{2,Rcv},{3,SndOk},{4,SndFail},{5,1-Dropped}]), %%io:format("~p: dies R=~p\n", [self(), R]), ok; {halt,Papa} -> Papa ! {halted,self()}, io:format("~p halted\n",[self()]), receive die -> ok end, io:format("~p dying\n",[self()]) end. send3_proc_send(Pids, {Rcv,SndOk,SndFail}, State0) -> To = lists:nth(rand:uniform(length(Pids)),Pids), Blob = send3_make_blob(), State1 = send3_new_state(State0,Blob), case send3_send(To, Blob) of true -> send3_proc(Pids, {Rcv,SndOk+1,SndFail}, State1); false -> send3_proc(Pids, {Rcv,SndOk,SndFail+1}, State1) end. send3_make_blob() -> case rand:uniform(20)-1 of 0 -> {term,[]}; N -> MsgEnv = alloc_msgenv(), repeat(N bsr 1, fun(_) -> grow_blob(MsgEnv,other_term(),rand:uniform(1 bsl 20)) end, void), case (N band 3) of 0 -> {term,copy_blob(MsgEnv)}; 1 -> {copy,copy_blob(MsgEnv)}; _ -> {msgenv,MsgEnv} end end. send3_send(Pid, Msg) -> %% 90% enif_send and 10% normal bang case rand:uniform(10) of 1 -> send3_send_bang(Pid,Msg); _ -> send3_send_nif(Pid,Msg) end. send3_send_nif(Pid, {term,Blob}) -> %%io:format("~p send term nif\n",[self()]), send_term(Pid, {blob, Blob}) =:= 1; send3_send_nif(Pid, {copy,Blob}) -> %%io:format("~p send term nif\n",[self()]), send_copy_term(Pid, {blob, Blob}) =:= 1; send3_send_nif(Pid, {msgenv,MsgEnv}) -> %%io:format("~p send blob nif\n",[self()]), send3_blob(MsgEnv, Pid, blob) =:= 1. send3_send_bang(Pid, {term,Blob}) -> %%io:format("~p send term bang\n",[self()]), Pid ! {blob, Blob}, true; send3_send_bang(Pid, {copy,Blob}) -> %%io:format("~p send term bang\n",[self()]), Pid ! {blob, Blob}, true; send3_send_bang(Pid, {msgenv,MsgEnv}) -> %%io:format("~p send blob bang\n",[self()]), Pid ! {blob, copy_blob(MsgEnv)}, true. send3_new_state(State, Blob) -> case rand:uniform(5+2) of N when N =< 5-> setelement(N, State, Blob); _ -> State % Don't store blob end. %% Negative testing of load_nif neg(Config) when is_list(Config) -> TmpMem = tmpmem(), {'EXIT',{badarg,_}} = (catch erlang:load_nif(badarg, 0)), Data = proplists:get_value(data_dir, Config), File = filename:join(Data, "nif_mod"), {ok,nif_mod,Bin} = compile:file(File, [binary,return_errors]), {module,nif_mod} = erlang:load_module(nif_mod,Bin), {error,{load_failed,_}} = nif_mod:load_nif_lib(Config, 0), {error,{bad_lib,_}} = nif_mod:load_nif_lib(Config, no_init), verify_tmpmem(TmpMem), ok. %% Test all enif_is functions is_checks(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), ok = check_is(hejsan, <<19,98>>, make_ref(), ok, fun() -> ok end, self(), hd(erlang:ports()), [], [1,9,9,8], {hejsan, "hejsan", [$h,"ejs",<<"an">>]}, 12), ok = check_is(hejsan, <<19,98>>, make_ref(), ok, fun() -> ok end, self(), hd(erlang:ports()), [], [1,9,9,8], {hejsan, "hejsan", [$h,"ejs",<<"an">>]}, -12), ok = check_is(hejsan, <<19,98>>, make_ref(), ok, fun() -> ok end, self(), hd(erlang:ports()), [], [1,9,9,8], {hejsan, "hejsan", [$h,"ejs",<<"an">>]}, 18446744073709551617), ok = check_is(hejsan, <<19,98>>, make_ref(), ok, fun() -> ok end, self(), hd(erlang:ports()), [], [1,9,9,8], {hejsan, "hejsan", [$h,"ejs",<<"an">>]}, -18446744073709551617), ok = check_is(hejsan, <<19,98>>, make_ref(), ok, fun() -> ok end, self(), hd(erlang:ports()), [], [1,9,9,8], {hejsan, "hejsan", [$h,"ejs",<<"an">>]}, 99.146), ok = check_is(hejsan, <<19,98>>, make_ref(), ok, fun() -> ok end, self(), hd(erlang:ports()), [], [1,9,9,8], {hejsan, "hejsan", [$h,"ejs",<<"an">>]}, -99.146), ok = check_is(hejsan, <<19,98>>, make_ref(), ok, fun() -> ok end, self(), hd(erlang:ports()), [], [1,9,9,8], {hejsan, "hejsan", [$h,"ejs",<<"an">>]}, 18446744073709551616.2e2), ok = check_is(hejsan, <<19,98>>, make_ref(), ok, fun() -> ok end, self(), hd(erlang:ports()), [], [1,9,9,8], {hejsan, "hejsan", [$h,"ejs",<<"an">>]}, -18446744073709551616.2e2), try check_is_exception(), throw(expected_badarg) catch error:badarg -> ok end. %% Test all enif_get_length functions get_length(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), ok = length_test(hejsan, "hejsan", [], [], not_a_list, [1,2|3]). ensure_lib_loaded(Config) -> ensure_lib_loaded(Config, 1). ensure_lib_loaded(Config, Ver) -> Path = ?config(data_dir, Config), case lib_version() of undefined -> Lib = "nif_SUITE." ++ integer_to_list(Ver), ok = erlang:load_nif(filename:join(Path,Lib), []); Ver when is_integer(Ver) -> ok end, erl_ddll:try_load(Path, echo_drv, []), ok. make_atom(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), An0Atom = an0atom, An0Atom0 = 'an\000atom\000', Atoms = make_atoms(), 7 = size(Atoms), Atoms = {An0Atom,An0Atom,An0Atom,An0Atom0,An0Atom,An0Atom,An0Atom0}. make_string(Config) when is_list(Config) -> ensure_lib_loaded(Config, 1), Strings = make_strings(), 5 = size(Strings), A0String = "a0string", A0String0 = [$a,0,$s,$t,$r,$i,$n,$g,0], AStringWithAccents = [$E,$r,$l,$a,$n,$g,$ ,16#e4,$r,$ ,$e,$t,$t,$ ,$g,$e,$n,$e,$r,$e,$l,$l,$t,$ ,$p,$r,$o,$g,$r,$a,$m,$s,$p,$r,16#e5,$k], Strings = {A0String,A0String,A0String,A0String0, AStringWithAccents}. reverse_list_test(Config) -> ensure_lib_loaded(Config, 1), List = lists:seq(1,100), RevList = lists:reverse(List), RevList = reverse_list(List), badarg = reverse_list(foo). %% Memory leak of tmp-buffer when inspecting iolist or unaligned binary in unbound environment otp_9668(Config) -> ensure_lib_loaded(Config, 1), TmpMem = tmpmem(), IOList = ["This",' ',<<"is">>,' ',[<<"an iolist">>,'.']], otp_9668_nif(IOList), <<_:5/bitstring,UnalignedBin:10/binary,_/bitstring>> = <<"Abuse me as unaligned">>, otp_9668_nif(UnalignedBin), verify_tmpmem(TmpMem), ok. %% Copy of writable binary otp_9828(Config) -> ensure_lib_loaded(Config, 1), otp_9828_loop(<<"I'm alive!">>, 1000). otp_9828_loop(_Bin, 0) -> ok; otp_9828_loop(Bin, Val) -> WrtBin = <>, ok = otp_9828_nif(WrtBin), otp_9828_loop(WrtBin, Val-1). consume_timeslice(Config) when is_list(Config) -> case {erlang:system_info(debug_compiled), erlang:system_info(lock_checking)} of {false, false} -> consume_timeslice_test(Config); {false, true} -> {skipped, "Lock checking enabled"}; _ -> {skipped, "Debug compiled"} end. consume_timeslice_test(Config) when is_list(Config) -> ensure_lib_loaded(Config), CONTEXT_REDS = 4000, Me = self(), Go = make_ref(), RedDiff = make_ref(), Done = make_ref(), DummyMFA = {?MODULE,dummy_call,1}, P = spawn(fun () -> receive Go -> ok end, {reductions, R1} = process_info(self(), reductions), 1 = consume_timeslice_nif(100, false), dummy_call(111), 0 = consume_timeslice_nif(90, false), dummy_call(222), 1 = consume_timeslice_nif(10, false), dummy_call(333), 0 = consume_timeslice_nif(25, false), 0 = consume_timeslice_nif(25, false), 0 = consume_timeslice_nif(25, false), 1 = consume_timeslice_nif(25, false), 0 = consume_timeslice_nif(25, false), ok = case consume_timeslice_nif(1, true) of Cnt when Cnt > 70, Cnt < 80 -> ok; Other -> Other end, dummy_call(444), {reductions, R2} = process_info(self(), reductions), Me ! {RedDiff, R2 - R1}, exit(Done) end), erlang:yield(), erlang:trace_pattern(DummyMFA, [], [local]), 1 = erlang:trace(P, true, [call, running, procs, {tracer, self()}]), P ! Go, %% receive Go -> ok end, {trace, P, in, _} = next_tmsg(P), %% consume_timeslice_nif(100), %% dummy_call(111) {trace, P, out, _} = next_tmsg(P), {trace, P, in, _} = next_tmsg(P), {trace, P, call, {?MODULE,dummy_call,[111]}} = next_tmsg(P), %% consume_timeslice_nif(90), %% dummy_call(222) {trace, P, call, {?MODULE,dummy_call,[222]}} = next_tmsg(P), %% consume_timeslice_nif(10), %% dummy_call(333) {trace, P, out, _} = next_tmsg(P), {trace, P, in, _} = next_tmsg(P), {trace, P, call, {?MODULE,dummy_call,[333]}} = next_tmsg(P), %% 25,25,25,25, 25 {trace, P, out, {?MODULE,consume_timeslice_nif,2}} = next_tmsg(P), {trace, P, in, {?MODULE,consume_timeslice_nif,2}} = next_tmsg(P), %% consume_timeslice(1,true) %% dummy_call(444) {trace, P, out, DummyMFA} = next_tmsg(P), {trace, P, in, DummyMFA} = next_tmsg(P), {trace, P, call, {?MODULE,dummy_call,[444]}} = next_tmsg(P), %% exit(Done) {trace, P, exit, Done} = next_tmsg(P), ExpReds = (100 + 90 + 10 + 25*5 + 75) * CONTEXT_REDS div 100, receive {RedDiff, Reductions} when Reductions < (ExpReds + 10), Reductions > (ExpReds - 10) -> io:format("Reductions = ~p~n", [Reductions]), ok; {RedDiff, Reductions} -> ct:fail({unexpected_reduction_count, Reductions, ExpReds}) end, none = next_msg(P), ok. nif_schedule(Config) when is_list(Config) -> ensure_lib_loaded(Config), A = "this is a string", B = {this,is,a,tuple}, {B,A} = call_nif_schedule(A, B), ok = try call_nif_schedule(1, 2) catch error:badarg -> [{?MODULE,call_nif_schedule,[1,2],_}|_] = erlang:get_stacktrace(), ok end, ok. nif_exception(Config) when is_list(Config) -> ensure_lib_loaded(Config), try %% this checks that the expected exception occurs when the NIF %% calls enif_make_badarg at some point but then tries to return a %% value that isn't an exception call_nif_exception(0), ct:fail(expected_badarg) catch error:badarg -> ok end, %% this checks that a NIF can raise various terms as exceptions ok = nif_raise_exceptions(call_nif_exception), ok. nif_nan_and_inf(Config) when is_list(Config) -> ensure_lib_loaded(Config), try call_nif_nan_or_inf(nan), ct:fail(expected_badarg) catch error:badarg -> ok end, try call_nif_nan_or_inf(inf), ct:fail(expected_badarg) catch error:badarg -> ok end, try call_nif_nan_or_inf(tuple), ct:fail(expected_badarg) catch error:badarg -> ok end. nif_atom_too_long(Config) when is_list(Config) -> ensure_lib_loaded(Config), try call_nif_atom_too_long(all), ct:fail(expected_badarg) catch error:badarg -> ok end, try call_nif_atom_too_long(len), ct:fail(expected_badarg) catch error:badarg -> ok end. next_msg(_Pid) -> receive M -> M after 100 -> none end. next_tmsg(Pid) -> receive TMsg when is_tuple(TMsg), element(1, TMsg) == trace, element(2, TMsg) == Pid -> TMsg after 100 -> none end. dummy_call(_) -> ok. tmpmem() -> case erlang:system_info({allocator,temp_alloc}) of false -> undefined; MemInfo -> MSBCS = lists:foldl( fun ({instance, 0, _}, Acc) -> Acc; % Ignore instance 0 ({instance, _, L}, Acc) -> {value,{_,MBCS}} = lists:keysearch(mbcs, 1, L), {value,{_,SBCS}} = lists:keysearch(sbcs, 1, L), [MBCS,SBCS | Acc] end, [], MemInfo), lists:foldl( fun(L, {Bl0,BlSz0}) -> {value,{_,Bl,_,_}} = lists:keysearch(blocks, 1, L), {value,{_,BlSz,_,_}} = lists:keysearch(blocks_size, 1, L), {Bl0+Bl,BlSz0+BlSz} end, {0,0}, MSBCS) end. verify_tmpmem(MemInfo) -> %%wait_for_test_procs(), case tmpmem() of MemInfo -> io:format("Tmp mem info: ~p", [MemInfo]), case MemInfo of {notsup,undefined} -> %% Use 'erl +Mea max' to do more complete memory leak testing. {comment,"Incomplete or no mem leak testing"}; _ -> ok end; Other -> ct:fail("Expected: ~p\nActual: ~p", [MemInfo, Other]) end. call(Pid,Cmd) -> %%io:format("~p calling ~p with ~p\n",[self(), Pid, Cmd]), Pid ! {self(), Cmd}, receive {Pid,Reply} -> Reply end. receive_any() -> receive M -> M end. receive_any(Timeout) -> receive M -> M after Timeout -> timeout end. flush() -> flush(1). flush(0) -> flush(0, 10); % don't waste too much time waiting for nothing flush(N) -> flush(N, 1000). flush(N, Timeout) -> receive M -> [M | flush(N-1)] after Timeout -> [] end. repeat(0, _, Arg) -> Arg; repeat(N, Fun, Arg0) -> repeat(N-1, Fun, Fun(Arg0)). check(Exp,Got,Line) -> case Got of Exp -> Exp; _ -> io:format("CHECK at line ~p\nExpected: ~p\nGot : ~p\n", [Line,Exp,Got]), Got end. nif_raise_exceptions(NifFunc) -> ExcTerms = [{error, test}, "a string", <<"a binary">>, 42, [1,2,3,4,5], [{p,1},{p,2},{p,3}]], lists:foldl(fun(Term, ok) -> try erlang:apply(?MODULE,NifFunc,[Term]), ct:fail({expected,Term}) catch error:Term -> [{?MODULE,NifFunc,[Term],_}|_] = erlang:get_stacktrace(), ok end end, ok, ExcTerms). -define(ERL_NIF_TIME_ERROR, -9223372036854775808). -define(TIME_UNITS, [second, millisecond, microsecond, nanosecond]). nif_monotonic_time(_Config) -> ?ERL_NIF_TIME_ERROR = monotonic_time(invalid_time_unit), mtime_loop(1000000). mtime_loop(0) -> ok; mtime_loop(N) -> chk_mtime(?TIME_UNITS), mtime_loop(N-1). chk_mtime([]) -> ok; chk_mtime([TU|TUs]) -> A = erlang:monotonic_time(TU), B = monotonic_time(TU), C = erlang:monotonic_time(TU), try true = A =< B, true = B =< C catch _ : _ -> ct:fail({monotonic_time_missmatch, TU, A, B, C}) end, chk_mtime(TUs). nif_time_offset(_Config) -> ?ERL_NIF_TIME_ERROR = time_offset(invalid_time_unit), toffs_loop(1000000). toffs_loop(0) -> ok; toffs_loop(N) -> chk_toffs(?TIME_UNITS), toffs_loop(N-1). chk_toffs([]) -> ok; chk_toffs([TU|TUs]) -> TO = erlang:time_offset(TU), NifTO = time_offset(TU), case TO =:= NifTO of true -> ok; false -> case erlang:system_info(time_warp_mode) of no_time_warp -> ct:fail({time_offset_mismatch, TU, TO, NifTO}); _ -> %% Most frequent time offset change %% is currently only every 15:th %% second so this should currently %% work... NTO = erlang:time_offset(TU), case NifTO =:= NTO of true -> ok; false -> ct:fail({time_offset_mismatch, TU, TO, NifTO, NTO}) end end end, chk_toffs(TUs). nif_convert_time_unit(_Config) -> ?ERL_NIF_TIME_ERROR = convert_time_unit(0, second, invalid_time_unit), ?ERL_NIF_TIME_ERROR = convert_time_unit(0, invalid_time_unit, second), ?ERL_NIF_TIME_ERROR = convert_time_unit(0, invalid_time_unit, invalid_time_unit), lists:foreach(fun (Offset) -> lists:foreach(fun (Diff) -> chk_ctu(Diff+(Offset*1000*1000*1000)) end, [999999999999, 99999999999, 9999999999, 999999999, 99999999, 9999999, 999999, 99999, 999, 99, 9, 1, 11, 101, 1001, 10001, 100001, 1000001, 10000001, 100000001, 1000000001, 100000000001, 1000000000001, 5, 50, 500, 5000, 50000, 500000, 5000000, 50000000, 500000000, 5000000000, 50000000000, 500000000000]) end, [-4711, -1000, -475, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 475, 1000, 4711]), ctu_loop(1000000). ctu_loop(0) -> ok; ctu_loop(N) -> chk_ctu(erlang:monotonic_time(nanosecond)), ctu_loop(N-1). chk_ctu(Time) -> chk_ctu(Time, ?TIME_UNITS). chk_ctu(_Time, []) -> ok; chk_ctu(Time, [FromTU|FromTUs]) -> chk_ctu(Time, FromTU, ?TIME_UNITS), chk_ctu(Time, FromTUs). chk_ctu(_Time, _FromTU, []) -> ok; chk_ctu(Time, FromTU, [ToTU|ToTUs]) -> T = erlang:convert_time_unit(Time, nanosecond, FromTU), TE = erlang:convert_time_unit(T, FromTU, ToTU), TN = convert_time_unit(T, FromTU, ToTU), case TE =:= TN of false -> ct:fail({conversion_mismatch, FromTU, T, ToTU, TE, TN}); true -> chk_ctu(Time, FromTU, ToTUs) end. nif_now_time(Config) -> ensure_lib_loaded(Config), N1 = now(), NifN1 = now_time(), NifN2 = now_time(), N2 = now(), true = N1 < NifN1, true = NifN1 < NifN2, true = NifN2 < N2. nif_cpu_time(Config) -> ensure_lib_loaded(Config), try cpu_time() of {_, _, _} -> ok catch error:badarg -> {comment, "cpu_time not supported"} end. nif_unique_integer(Config) -> ensure_lib_loaded(Config), UM1 = erlang:unique_integer([monotonic]), UM2 = unique_integer_nif([monotonic]), UM3 = erlang:unique_integer([monotonic]), true = UM1 < UM2, true = UM2 < UM3, UMP1 = erlang:unique_integer([monotonic, positive]), UMP2 = unique_integer_nif([monotonic, positive]), UMP3 = erlang:unique_integer([monotonic, positive]), true = 0 =< UMP1, true = UMP1 < UMP2, true = UMP2 < UMP3, UP1 = erlang:unique_integer([positive]), UP2 = unique_integer_nif([positive]), UP3 = erlang:unique_integer([positive]), true = 0 =< UP1, true = 0 =< UP2, true = 0 =< UP3, true = is_integer(unique_integer_nif([])), true = is_integer(unique_integer_nif([])), true = is_integer(unique_integer_nif([])). nif_is_process_alive(Config) -> ensure_lib_loaded(Config), {Pid,_} = spawn_monitor(fun() -> receive ok -> nok end end), true = is_process_alive_nif(Pid), exit(Pid, die), receive _ -> ok end, %% Clear monitor false = is_process_alive_nif(Pid). nif_is_port_alive(Config) -> ensure_lib_loaded(Config), Port = open_port({spawn,echo_drv},[eof]), true = is_port_alive_nif(Port), port_close(Port), false = is_port_alive_nif(Port). nif_term_to_binary(Config) -> ensure_lib_loaded(Config), T = {#{ok => nok}, <<0:8096>>, lists:seq(1,100)}, Bin = term_to_binary(T), ct:log("~p",[Bin]), Bin = term_to_binary_nif(T, undefined), true = term_to_binary_nif(T, self()), receive Bin -> ok end. -define(ERL_NIF_BIN2TERM_SAFE, 16#20000000). nif_binary_to_term(Config) -> ensure_lib_loaded(Config), T = {#{ok => nok}, <<0:8096>>, lists:seq(1,100)}, Bin = term_to_binary(T), Len = byte_size(Bin), {Len,T} = binary_to_term_nif(Bin, undefined, 0), Len = binary_to_term_nif(Bin, self(), 0), T = receive M -> M after 1000 -> timeout end, {Len, T} = binary_to_term_nif(Bin, undefined, ?ERL_NIF_BIN2TERM_SAFE), false = binary_to_term_nif(<<131,100,0,14,"undefined_atom">>, undefined, ?ERL_NIF_BIN2TERM_SAFE), false = binary_to_term_nif(Bin, undefined, 1), ok. nif_port_command(Config) -> ensure_lib_loaded(Config), Port = open_port({spawn,echo_drv},[eof]), true = port_command_nif(Port, "hello\n"), receive {Port,{data,"hello\n"}} -> ok after 1000 -> ct:fail(timeout) end, RefcBin = lists:flatten([lists:duplicate(100, "hello"),"\n"]), true = port_command_nif(Port, iolist_to_binary(RefcBin)), receive {Port,{data,RefcBin}} -> ok after 1000 -> ct:fail(timeout) end, %% Test that invalid arguments correctly returns %% badarg and that the port survives. {'EXIT', {badarg, _}} = (catch port_command_nif(Port, [ok])), IoList = [lists:duplicate(100,<<"hello">>),"\n"], true = port_command_nif(Port, [IoList]), FlatIoList = binary_to_list(iolist_to_binary(IoList)), receive {Port,{data,FlatIoList}} -> ok after 1000 -> ct:fail(timeout) end, port_close(Port), {'EXIT', {badarg, _}} = (catch port_command_nif(Port, "hello\n")), ok. nif_snprintf(Config) -> ensure_lib_loaded(Config), <<"ok",0>> = format_term_nif(3,ok), <<"o",0>> = format_term_nif(2,ok), <<"\"hello world\"",0>> = format_term_nif(14,"hello world"), <<"{{hello,world,-33},3.14",_/binary>> = format_term_nif(50,{{hello,world, -33}, 3.14, self()}), <<"{{hello,world,-33},",0>> = format_term_nif(20,{{hello,world, -33}, 3.14, self()}), ok. nif_internal_hash(Config) -> ensure_lib_loaded(Config), HashValueBitSize = nif_hash_result_bitsize(internal), Terms = unique([random_term() || _ <- lists:seq(1, 5000)]), HashValues = [hash_nif(internal, Term, 0) || Term <- Terms], test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.05). nif_internal_hash_salted(Config) -> ensure_lib_loaded(Config), test_salted_nif_hash(internal). nif_phash2(Config) -> ensure_lib_loaded(Config), HashValueBitSize = nif_hash_result_bitsize(phash2), Terms = unique([random_term() || _ <- lists:seq(1, 5000)]), HashValues = lists:map( fun (Term) -> HashValue = erlang:phash2(Term), Salt = random_uint32(), % phash2 should ignore salt NifHashValue = hash_nif(phash2, Term, Salt), (HashValue =:= NifHashValue orelse ct:fail("Expected: ~p\nActual: ~p", [HashValue, NifHashValue])), HashValue end, Terms), test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.05). test_salted_nif_hash(HashType) -> HashValueBitSize = nif_hash_result_bitsize(HashType), Terms = unique([random_term() || _ <- lists:seq(1, 5000)]), Salts = unique([random_uint32() || _ <- lists:seq(1, 100)]), {HashValuesPerSalt, HashValuesPerTerm} = lists:mapfoldl( fun (Salt, Acc) -> {HashValues, NewAcc} = lists:mapfoldl( fun (Term, AccB) -> HashValue = hash_nif(HashType, Term, Salt), NewAccB = dict:append(Term, HashValue, AccB), {HashValue, NewAccB} end, Acc, Terms), {{Salt, HashValues}, NewAcc} end, dict:new(), Salts), % Test per-salt hash distribution of different terms lists:foreach( fun ({_Salt, HashValues}) -> test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.05) end, HashValuesPerSalt), % Test per-term hash distribution of different salts dict:fold( fun (_Term, HashValues, Acc) -> % Be more tolerant of relative deviation, % as there's fewer hash values here. test_bit_distribution_fitness(HashValues, HashValueBitSize, 0.30), Acc end, ok, HashValuesPerTerm). test_bit_distribution_fitness(Integers, BitSize, MaxRelativeDeviation) -> MaxInteger = (1 bsl BitSize) - 1, OnesPerBit = lists:foldl( fun (Integer, Acc) when Integer >= 0, Integer =< MaxInteger -> lists:foldl( fun (BitIndex, AccB) -> BitValue = (Integer band (1 bsl BitIndex)) bsr BitIndex, orddict:update_counter(BitIndex, BitValue, AccB) end, Acc, lists:seq(0, BitSize - 1)) end, orddict:new(), Integers), ExpectedNrOfOnes = length(Integers) div 2, FailureText = orddict:fold( fun (BitIndex, NrOfOnes, Acc) -> RelativeDeviation = abs(NrOfOnes - ExpectedNrOfOnes) / length(Integers), case RelativeDeviation >= MaxRelativeDeviation of false -> Acc; true -> [Acc, io_lib:format( "Unreasonable deviation on number of set bits (i=~p): " "expected ~p, got ~p (relative dev. ~.3f)~n", [BitIndex, ExpectedNrOfOnes, NrOfOnes, RelativeDeviation])] end end, [], OnesPerBit), (FailureText =:= [] orelse ct:fail(FailureText)). nif_hash_result_bitsize(internal) -> 32; nif_hash_result_bitsize(phash2) -> 27. unique(List) -> lists:usort(List). random_uint32() -> rand:uniform(1 bsl 32) - 1. random_term() -> case rand:uniform(6) of 1 -> rand:uniform(1 bsl 27) - 1; % small 2 -> (1 bsl 27) + rand:uniform(1 bsl 128); % big 3 -> random_sign() * (rand:uniform() * (1 bsl 53)); % float 4 -> random_binary(); 5 -> random_pid(); 6 -> Length = rand:uniform(10), List = [random_term() || _ <- lists:seq(1, Length)], case rand:uniform(2) of 1 -> List; 2 -> list_to_tuple(List) end end. random_sign() -> case rand:uniform(2) of 1 -> -1.0; 2 -> 1.0 end. random_binary() -> list_to_binary(random_bytes(rand:uniform(32) - 1)). random_bytes(0) -> []; random_bytes(N) when N > 0 -> [rand:uniform(256) - 1 | random_bytes(N - 1)]. random_pid() -> Processes = erlang:processes(), lists:nth(rand:uniform(length(Processes)), Processes). %% The NIFs: lib_version() -> undefined. call_history() -> ?nif_stub. hold_nif_mod_priv_data(_Ptr) -> ?nif_stub. nif_mod_call_history() -> ?nif_stub. list_seq(_To) -> ?nif_stub. type_test() -> ?nif_stub. tuple_2_list(_) -> ?nif_stub. is_identical(_,_) -> ?nif_stub. compare(_,_) -> ?nif_stub. hash_nif(_Type, _Term, _Salt) -> ?nif_stub. many_args_100(_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_) -> ?nif_stub. clone_bin(_) -> ?nif_stub. make_sub_bin(_,_,_) -> ?nif_stub. string_to_bin(_,_) -> ?nif_stub. atom_to_bin(_,_) -> ?nif_stub. macros(_) -> ?nif_stub. tuple_2_list_and_tuple(_) -> ?nif_stub. iolist_2_bin(_) -> ?nif_stub. get_resource_type(_) -> ?nif_stub. alloc_resource(_,_) -> ?nif_stub. make_resource(_) -> ?nif_stub. get_resource(_,_) -> ?nif_stub. release_resource(_) -> ?nif_stub. release_resource_from_thread(_) -> ?nif_stub. last_resource_dtor_call() -> ?nif_stub. make_new_resource(_,_) -> ?nif_stub. check_is(_,_,_,_,_,_,_,_,_,_,_) -> ?nif_stub. check_is_exception() -> ?nif_stub. length_test(_,_,_,_,_,_) -> ?nif_stub. make_atoms() -> ?nif_stub. make_strings() -> ?nif_stub. make_new_resource_binary(_) -> ?nif_stub. send_list_seq(_,_) -> ?nif_stub. send_new_blob(_,_) -> ?nif_stub. alloc_msgenv() -> ?nif_stub. clear_msgenv(_) -> ?nif_stub. grow_blob(_,_) -> ?nif_stub. grow_blob(_,_,_) -> ?nif_stub. send_blob(_,_) -> ?nif_stub. send3_blob(_,_,_) -> ?nif_stub. send_blob_thread(_,_,_) -> ?nif_stub. join_send_thread(_) -> ?nif_stub. copy_blob(_) -> ?nif_stub. send_term(_,_) -> ?nif_stub. send_copy_term(_,_) -> ?nif_stub. reverse_list(_) -> ?nif_stub. echo_int(_) -> ?nif_stub. type_sizes() -> ?nif_stub. otp_9668_nif(_) -> ?nif_stub. otp_9828_nif(_) -> ?nif_stub. consume_timeslice_nif(_,_) -> ?nif_stub. call_nif_schedule(_,_) -> ?nif_stub. call_nif_exception(_) -> ?nif_stub. call_nif_nan_or_inf(_) -> ?nif_stub. call_nif_atom_too_long(_) -> ?nif_stub. unique_integer_nif(_) -> ?nif_stub. is_process_alive_nif(_) -> ?nif_stub. is_port_alive_nif(_) -> ?nif_stub. term_to_binary_nif(_, _) -> ?nif_stub. binary_to_term_nif(_, _, _) -> ?nif_stub. port_command_nif(_, _) -> ?nif_stub. format_term_nif(_,_) -> ?nif_stub. select_nif(_,_,_,_,_) -> ?nif_stub. pipe_nif() -> ?nif_stub. write_nif(_,_) -> ?nif_stub. read_nif(_,_) -> ?nif_stub. is_closed_nif(_) -> ?nif_stub. last_fd_stop_call() -> ?nif_stub. alloc_monitor_resource_nif() -> ?nif_stub. monitor_process_nif(_,_,_,_) -> ?nif_stub. demonitor_process_nif(_,_) -> ?nif_stub. compare_monitors_nif(_,_) -> ?nif_stub. monitor_frenzy_nif(_,_,_,_) -> ?nif_stub. %% maps is_map_nif(_) -> ?nif_stub. get_map_size_nif(_) -> ?nif_stub. make_new_map_nif() -> ?nif_stub. make_map_put_nif(_,_,_) -> ?nif_stub. get_map_value_nif(_,_) -> ?nif_stub. make_map_update_nif(_,_,_) -> ?nif_stub. make_map_remove_nif(_,_) -> ?nif_stub. maps_from_list_nif(_) -> ?nif_stub. sorted_list_from_maps_nif(_) -> ?nif_stub. %% Time monotonic_time(_) -> ?nif_stub. time_offset(_) -> ?nif_stub. convert_time_unit(_,_,_) -> ?nif_stub. now_time() -> ?nif_stub. cpu_time() -> ?nif_stub. nif_stub_error(Line) -> exit({nif_not_loaded,module,?MODULE,line,Line}).