Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:25
erlang
2081-erts-On-halt-and-delayed-halt-support-for-...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 2081-erts-On-halt-and-delayed-halt-support-for-NIFs.patch of Package erlang
From 988a0e9f44d2d6a0c114acd1d19bc21b3dee2aea Mon Sep 17 00:00:00 2001 From: Rickard Green <rickard@erlang.org> Date: Thu, 6 Oct 2022 11:59:31 +0200 Subject: [PATCH] [erts] On-halt and delayed halt support for NIFs --- erts/doc/src/erl_nif.xml | 175 +++++++ erts/doc/src/erlang.xml | 190 ++++++-- erts/emulator/beam/break.c | 2 +- erts/emulator/beam/erl_db.c | 42 +- erts/emulator/beam/erl_init.c | 49 +- erts/emulator/beam/erl_lock_check.c | 1 + erts/emulator/beam/erl_nif.c | 280 ++++++++++- erts/emulator/beam/erl_nif.h | 12 +- erts/emulator/beam/erl_nif_api_funcs.h | 3 + erts/emulator/beam/erl_process.c | 12 +- erts/emulator/beam/erl_process.h | 5 +- erts/emulator/beam/global.h | 8 +- erts/emulator/beam/io.c | 2 +- erts/emulator/test/dirty_nif_SUITE.erl | 444 +++++++++++++++++- .../test/dirty_nif_SUITE_data/Makefile.src | 2 +- .../dirty_nif_SUITE_data/dirty_nif_SUITE.c | 281 ++++++++++- .../test/dirty_nif_SUITE_data/on_halt_a.c | 23 + .../test/dirty_nif_SUITE_data/on_halt_b.c | 23 + .../test/dirty_nif_SUITE_data/on_halt_c.c | 23 + .../test/dirty_nif_SUITE_data/on_halt_d.c | 23 + .../test/dirty_nif_SUITE_data/on_halt_e.c | 23 + .../test/dirty_nif_SUITE_data/on_halt_f.c | 23 + .../test/dirty_nif_SUITE_data/on_halt_nif.c | 96 ++++ erts/preloaded/src/erlang.erl | 26 +- 24 files changed, 1661 insertions(+), 107 deletions(-) create mode 100644 erts/emulator/test/dirty_nif_SUITE_data/on_halt_a.c create mode 100644 erts/emulator/test/dirty_nif_SUITE_data/on_halt_b.c create mode 100644 erts/emulator/test/dirty_nif_SUITE_data/on_halt_c.c create mode 100644 erts/emulator/test/dirty_nif_SUITE_data/on_halt_d.c create mode 100644 erts/emulator/test/dirty_nif_SUITE_data/on_halt_e.c create mode 100644 erts/emulator/test/dirty_nif_SUITE_data/on_halt_f.c create mode 100644 erts/emulator/test/dirty_nif_SUITE_data/on_halt_nif.c diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml index 4208aa269a..cb300a976d 100644 --- a/erts/doc/src/erl_nif.xml +++ b/erts/doc/src/erl_nif.xml @@ -785,6 +785,41 @@ typedef struct { To compare two monitors, <seecref marker="#enif_compare_monitors"> <c>enif_compare_monitors</c></seecref> must be used.</p> </item> + <tag><marker id="ErlNifOnHaltCallback"/><c>ErlNifOnHaltCallback</c></tag> + <item> + <code type="none"> +typedef void ErlNifOnHaltCallback(void *priv_data);</code> + <p> + The function prototype of an <i>on halt</i> callback function. + </p> + <p> + An <i>on halt</i> callback can be installed using + <seecref marker="#on_halt"><c>enif_set_option()</c></seecref>. Such + an installed callback will be called when the runtime system is + halting. + </p> + </item> + <tag><marker id="ErlNifOption"/><c>ErlNifOption</c></tag> + <item> + <p> + An enumeration of the options that can be set using + <seecref marker="#enif_set_option"><c>enif_set_option()</c></seecref>. + </p> + <p>Currently valid options:</p> + <taglist> + <tag><seecref marker="#delay_halt"><c>ERL_NIF_OPT_DELAY_HALT</c></seecref></tag> + <item><p> + Enable delay of runtime system halt with flushing enabled until + all calls to NIFs in the NIF library have returned. + </p></item> + <tag><seecref marker="#on_halt"><c>ERL_NIF_OPT_ON_HALT</c></seecref></tag> + <item><p> + Install a callback that will be called when the runtime system + halts with flushing enabled. + </p></item> + </taglist> + </item> + <tag><marker id="ErlNifPid"/><c>ErlNifPid</c></tag> <item> <p>A process identifier (pid). In contrast to pid terms (instances of @@ -3412,6 +3447,146 @@ if (retval & ERL_NIF_SELECT_STOP_CALLED) { </desc> </func> + <func> + <name since="OTP 26.0"> + <ret>int</ret><nametext>enif_set_option(ErlNifEnv *env, ErlNifOption opt, ...)</nametext> + </name> + <fsummary> + Set an option. + </fsummary> + <desc> + <marker id="enif_set_option"/> + <p> + Set an option. On success, zero will be returned. On failure, a non + zero value will be returned. Currently the following options can be set: + </p> + <taglist> + <tag><marker id="delay_halt"/> + <c>int enif_set_option(ErlNifEnv *env, + </c><seecref marker="#ErlNifOption"><c>ERL_NIF_OPT_DELAY_HALT</c></seecref><c>)</c> + </tag> + <item> + <p> + Enable delay of runtime system halt with flushing enabled until + all calls to NIFs in the NIF library have returned. If the + <i>delay halt</i> feature has not been enabled, a halt with + flushing enabled may complete even though processes are still + executing inside NIFs in the NIF library. Note that by + <i>returning</i> we here mean the first point where the NIF + returns control back to the runtime system, and <em>not</em> the + point where a call to a NIF return a value back to the Erlang + code that called the NIF. That is, if you schedule execution of + a NIF, using <seecref marker="#enif_schedule_nif"> + <c>enif_schedule_nif()</c></seecref>, from within a NIF while + the system is halting, the scheduled NIF call will <em>not</em> + be executed even though <i>delay halt</i> has been enabled for + the NIF library. + </p> + <p> + The runtime system halts when one of the + <seemfa marker="erlang#halt/0"><c>erlang:halt()</c></seemfa> + BIFs are called. By default flushing is enabled, but can be + disabled using the + <seemfa marker="erlang#halt/2"><c>erlang:halt/2</c></seemfa> BIF. + When flushing has been disabled, the <i>delay halt</i> setting + will have no effect. That is, the runtime system will halt without + waiting for NIFs to return even if the <i>delay halt</i> setting + has been enabled. See the + <seeerl marker="erlang#halt_flush"><c>{flush, boolean()}</c></seeerl> + option of <c>erlang:halt/2</c> for more information. + </p> + <p> + The <c>ERL_NIF_OPT_DELAY_HALT</c> option can only be set during + loading of a NIF library in a call to <c>enif_set_option()</c> + inside a NIF library + <seecref marker="#load"><c>load()</c></seecref> or + <seecref marker="#upgrade"><c>upgrade()</c></seecref> call, + and will fail if set somewhere else. The <c>env</c> + argument <i>must</i> be the callback environment passed to the + <c>load()</c> or the <c>upgrade()</c> call. This option can also + only be set once. That is, the <i>delay halt</i> setting cannot + be changed once it has been enabled. The <i>delay halt</i> + setting is tied to the module instance with which the NIF library + instance has been loaded. That is, in case both a new and old + version of a module using the NIF library are loaded, they can + have the same or different <i>delay halt</i> settings. + </p> + <p> + The <i>delay halt</i> feature can be used in combination with an + <seecref marker="#on_halt"><i>on halt</i></seecref> callback. + The <i>on halt</i> callback is in this case typically used to + notify processes blocked in NIFs in the library that it is time + to return in order to let the runtime system complete the + halting. Such NIFs should be dirty NIFs, since ordinary NIFs + should never block for a long time. + </p> + </item> + <tag><marker id="on_halt"/> + <c>int enif_set_option(ErlNifEnv *env, + </c><seecref marker="#ErlNifOption"><c>ERL_NIF_OPT_ON_HALT</c></seecref><c>, + </c><seecref marker="#ErlNifOnHaltCallback"><c>ErlNifOnHaltCallback</c></seecref><c> + *on_halt)</c> + </tag> + <item> + <p> + Install a callback that will be called when the runtime system + halts with flushing enabled. + </p> + <p> + The runtime system halts when one of the + <seemfa marker="erlang#halt/0"><c>erlang:halt()</c></seemfa> BIFs + are called. By default flushing is enabled, but can be disabled + using the + <seemfa marker="erlang#halt/2"><c>erlang:halt/2</c></seemfa> BIF. + When flushing has been disabled, the runtime system will halt + without calling any <i>on halt</i> callbacks even if such are + installed. See the + <seeerl marker="erlang#halt_flush"><c>{flush, boolean()}</c></seeerl> + option of <c>erlang:halt/2</c> for more information. + </p> + <p> + The <c>ERL_NIF_OPT_ON_HALT</c> option can only be set during + loading of a NIF library in a call to <c>enif_set_option()</c> + inside a NIF library + <seecref marker="#load"><c>load()</c></seecref> or + <seecref marker="#upgrade"><c>upgrade()</c></seecref> call, + and will fail if called somewhere else. The <c>env</c> + argument <i>must</i> be the callback environment passed to the + <c>load()</c> or the <c>upgrade()</c> call. The <c>on_halt</c> + argument should be a function pointer to the callback to install. + The <i>on halt</i> callback will be tied to the module instance + with which the NIF library instance has been loaded. That is, in + case both a new and old version of a module using the NIF library + are loaded, they can both have different, none, or the same + <i>on halt</i> callbacks installed. When unloading the NIF + library during a + <seemfa marker="kernel:code#purge/1">code purge</seemfa>, an + installed <i>on halt</i> callback will be uninstalled. + The <c>ERL_NIF_OPT_ON_HALT</c> option can also only be set + once. That is, the <i>on halt</i> callback cannot be changed + or removed once it has been installed by any other means than + purging the module instance that loaded the NIF library. + </p> + <p> + When the installed <i>on halt</i> callback is called, it will be + passed a pointer to <c>priv_data</c> as argument. The + <c>priv_data</c> pointer can be set when loading the NIF library. + </p> + <p> + The <i>on halt</i> callback can be used in combination with + <seecref marker="#delay_halt"><i>delay of halt</i></seecref> until + all calls into the library have returned. The <i>on halt</i> + callback is in this case typically used to notify processes + blocked in NIFs in the library that it is time to return in order + to let the runtime system complete the halting. Such NIFs should + be dirty NIFs, since ordinary NIFs should never block for a long + time. + </p> + </item> + </taglist> + </desc> + </func> + <func> <name since="OTP 22.0"><ret>void</ret> <nametext>enif_set_pid_undefined(ErlNifPid* pid)</nametext></name> diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml index 8a8a219b97..ece6764017 100644 --- a/erts/doc/src/erlang.xml +++ b/erts/doc/src/erlang.xml @@ -2925,7 +2925,7 @@ uncompiled code with the same arity are mapped to the same list by <fsummary>Halt the Erlang runtime system and indicate normal exit to the calling environment.</fsummary> <desc> - <p>The same as + <p>The same as calling <seemfa marker="#halt/2"><c>halt(0, [])</c></seemfa>. Example:</p> <pre> > <input>halt().</input> @@ -2934,10 +2934,11 @@ os_prompt%</pre> </func> <func> - <name name="halt" arity="1" since=""/> + <name name="halt" arity="1" clause_i="1" + anchor="halt_status_code_1" since=""/> <fsummary>Halt the Erlang runtime system.</fsummary> <desc> - <p>The same as <seemfa marker="#halt/2"> + <p>The same as calling <seemfa marker="#halt/2"> <c>halt(<anno>Status</anno>, [])</c></seemfa>. Example:</p> <pre> > <input>halt(17).</input> @@ -2948,44 +2949,161 @@ os_prompt%</pre> </func> <func> - <name name="halt" arity="2" since="OTP R15B01"/> + <name name="halt" arity="1" clause_i="2" + anchor="halt_abort_1" since="OTP R15B01"/> + <fsummary>Halt the Erlang runtime system by aborting.</fsummary> + <desc> + <p> + The same as calling <seeerl marker="#halt_abort_2"> + <c>halt(abort, [])</c></seeerl>. + </p> + </desc> + </func> + + <func> + <name name="halt" arity="1" clause_i="3" + anchor="halt_crash_dump_1" since=""/> + <fsummary> + Halt the Erlang runtime system and create an Erlang crash dump. + </fsummary> + <desc> + <p> + The same as calling <seeerl marker="#halt_crash_dump_2"> + <c>halt(<anno>CrashDumpSlogan</anno>, [])</c></seeerl>. + </p> + </desc> + </func> + + <func> + <name name="halt" arity="2" clause_i="1" + anchor="halt_status_code_2" since="OTP R15B01"/> <fsummary>Halt the Erlang runtime system.</fsummary> + <type name="halt_options"/> <desc> - <p><c><anno>Status</anno></c> must be a non-negative integer, a string, - or the atom <c>abort</c>. - Halts the Erlang runtime system. Has no return value. - Depending on <c><anno>Status</anno></c>, the following occurs:</p> + <p> + Halt the runtime system with status code + <c><anno>Status</anno></c>. + </p> + <note> + <p> + On many platforms, the OS supports only status codes 0-255. A too + large status code is truncated by clearing the high bits. + </p> + </note> + <p> + Currently the following options are valid: + </p> <taglist> - <tag>integer()</tag> - <item>The runtime system exits with integer value - <c><anno>Status</anno></c> - as status code to the calling environment (OS). - <note> - <p>On many platforms, the OS supports only status - codes 0-255. A too large status code is truncated by clearing - the high bits.</p> - </note> - </item> - <tag>string()</tag> - <item>An Erlang crash dump is produced with <c><anno>Status</anno></c> - as slogan. Then the runtime system exits with status code <c>1</c>. - The string will be truncated if longer than 200 characters. - <note> - <p>Before ERTS 9.1 (OTP-20.1) only code points in the range 0-255 - was accepted in the string. Now any unicode string is valid.</p> - </note> - </item> - <tag><c>abort</c></tag> - <item>The runtime system aborts producing a core dump, if that is - enabled in the OS. + <tag><marker id="halt_flush"/><c>{flush, EnableFlushing}</c></tag> + <item> + <p> + If <c>EnableFlushing</c> equals <c>true</c>, which also is the + default behavior, the runtime system will perform the following + operations before terminating: + </p> + <list> + <item><p> + Flush all outstanding output. + </p></item> + <item><p> + Send all Erlang ports exit signals and wait for them + to exit. + </p></item> + <item><p> + Wait for all async threads to complete all outstanding + async jobs. + </p></item> + <item><p> + Call all installed <seecref marker="erl_nif#on_halt">NIF + <i>on halt</i> callbacks</seecref>. + </p></item> + <item><p> + Wait for all ongoing <seecref marker="erl_nif#delay_halt">NIF + calls with the <i>delay halt</i> setting</seecref> enabled to + return. + </p></item> + <item><p> + Call all installed <c>atexit</c>/<c>on_exit</c> callbacks. + </p></item> + </list> + <p> + If <c>EnableFlushing</c> equals <c>false</c>, the runtime system + will terminate immediately without performing any of the above + listed operations. + </p> + <p> + Behavior changes compared to earlier versions: + </p> + <list> + <item> + <p> + Runtime systems prior to OTP 26.0 called all installed + <c>atexit</c>/<c>on_exit</c> callbacks also when <c>flush</c> + was disabled, but as of OTP 26.0 this is no longer the case. + </p> + </item> + </list> </item> </taglist> - <p>For integer <c><anno>Status</anno></c>, the Erlang runtime system - closes all ports and allows async threads to finish their - operations before exiting. To exit without such flushing, use - <c><anno>Option</anno></c> as <c>{flush,false}</c>.</p> - <p>For statuses <c>string()</c> and <c>abort</c>, option - <c>flush</c> is ignored and flushing is <em>not</em> done.</p> + </desc> + </func> + + <func> + <name name="halt" arity="2" clause_i="2" + anchor="halt_abort_2" since="OTP R15B01"/> + <fsummary>Halt the Erlang runtime system by aborting.</fsummary> + <type name="halt_options"/> + <desc> + <p> + Halt the Erlang runtime system by aborting and produce a core dump + if core dumping has been enabled in the environment that the + runtime system is executing in. + </p> + <note><p> + The <seeerl marker="#halt_flush"><c>{flush, boolean()}</c></seeerl> + option will be ignored, and flushing will be disabled. + </p></note> + </desc> + </func> + + <func> + <name name="halt" arity="2" clause_i="3" + anchor="halt_crash_dump_2" since="OTP R15B01"/> + <fsummary> + Halt the Erlang runtime system and create an Erlang crash dump. + </fsummary> + <type name="halt_options"/> + <desc> + <p> + Halt the Erlang runtime system and generate an + <seeguide marker="crash_dump">Erlang crash dump</seeguide>. The + string <c><anno>CrashDumpSlogan</anno></c> will be used as slogan + in the Erlang crash dump created. The slogan will be trunkated if + <c><anno>CrashDumpSlogan</anno></c> is longer than 1023 characters. + </p> + <note><p> + The <seeerl marker="#halt_flush"><c>{flush, boolean()}</c></seeerl> + option will be ignored, and flushing will be disabled. + </p></note> + <p> + Behavior changes compared to earlier versions: + </p> + <list> + <item> + <p> + Before OTP 24.2, the slogan was truncated if + <c><anno>CrashDumpSlogan</anno></c> was longer than 200 + characters. Now it will be truncated if longer than 1023 + characters. + </p> + </item> + <item> + <p> + Before OTP 20.1, only code points in the range 0-255 were + accepted in the slogan. Now any Unicode string is valid. + </p> + </item> + </list> </desc> </func> diff --git a/erts/emulator/beam/break.c b/erts/emulator/beam/break.c index 3b6de45587..8097ed337b 100644 --- a/erts/emulator/beam/break.c +++ b/erts/emulator/beam/break.c @@ -781,7 +781,7 @@ crash_dump_limited_writer(void* vfdp, char* buf, size_t len) } /* We assume that crash dump was called from erts_exit_vv() */ - erts_exit_epilogue(); + erts_exit_epilogue(0); } /* XXX THIS SHOULD BE IN SYSTEM !!!! */ diff --git a/erts/emulator/beam/erl_db.c b/erts/emulator/beam/erl_db.c index bdcb136a05..3c2df49138 100644 --- a/erts/emulator/beam/erl_db.c +++ b/erts/emulator/beam/erl_db.c @@ -506,13 +506,13 @@ save_sched_table(Process *c_p, DbTable *tb) DbTable *first; ASSERT(esdp); - erts_atomic_inc_nob(&esdp->ets_tables.count); + erts_atomic_inc_nob(&esdp->u.ets_tables.count); erts_refc_inc(&tb->common.refc, 1); - first = esdp->ets_tables.clist; + first = esdp->u.ets_tables.clist; if (!first) { tb->common.all.next = tb->common.all.prev = tb; - esdp->ets_tables.clist = tb; + esdp->u.ets_tables.clist = tb; } else { tb->common.all.prev = first->common.all.prev; @@ -530,14 +530,14 @@ remove_sched_table(ErtsSchedulerData *esdp, DbTable *tb) ASSERT(erts_get_ref_numbers_thr_id(ERTS_MAGIC_BIN_REFN(tb->common.btid)) == (Uint32) esdp->no); - ASSERT(erts_atomic_read_nob(&esdp->ets_tables.count) > 0); - erts_atomic_dec_nob(&esdp->ets_tables.count); + ASSERT(erts_atomic_read_nob(&esdp->u.ets_tables.count) > 0); + erts_atomic_dec_nob(&esdp->u.ets_tables.count); eaydp = ERTS_SCHED_AUX_YIELD_DATA(esdp, ets_all); if (eaydp->ongoing) { /* ets:all() op process list from last to first... */ if (eaydp->tab == tb) { - if (eaydp->tab == esdp->ets_tables.clist) + if (eaydp->tab == esdp->u.ets_tables.clist) eaydp->tab = NULL; else eaydp->tab = tb->common.all.prev; @@ -546,23 +546,23 @@ remove_sched_table(ErtsSchedulerData *esdp, DbTable *tb) if (tb->common.all.next == tb) { ASSERT(tb->common.all.prev == tb); - ASSERT(esdp->ets_tables.clist == tb); - esdp->ets_tables.clist = NULL; + ASSERT(esdp->u.ets_tables.clist == tb); + esdp->u.ets_tables.clist = NULL; } else { #ifdef DEBUG - DbTable *tmp = esdp->ets_tables.clist; + DbTable *tmp = esdp->u.ets_tables.clist; do { if (tmp == tb) break; tmp = tmp->common.all.next; - } while (tmp != esdp->ets_tables.clist); + } while (tmp != esdp->u.ets_tables.clist); ASSERT(tmp == tb); #endif tb->common.all.prev->common.all.next = tb->common.all.next; tb->common.all.next->common.all.prev = tb->common.all.prev; - if (esdp->ets_tables.clist == tb) - esdp->ets_tables.clist = tb->common.all.next; + if (esdp->u.ets_tables.clist == tb) + esdp->u.ets_tables.clist = tb->common.all.next; } @@ -3207,7 +3207,7 @@ ets_all_reply(ErtsSchedulerData *esdp, ErtsEtsAllReq **reqpp, hp = &hfragp->mem[hfragp->used_size]; list = *hp; hfragp->used_size = hfragp->alloc_size; - first = esdp->ets_tables.clist; + first = esdp->u.ets_tables.clist; tb = *tablepp; } else { @@ -3215,7 +3215,7 @@ ets_all_reply(ErtsSchedulerData *esdp, ErtsEtsAllReq **reqpp, ASSERT(!*tablepp); /* Max heap size needed... */ - sz = erts_atomic_read_nob(&esdp->ets_tables.count); + sz = erts_atomic_read_nob(&esdp->u.ets_tables.count); sz *= ERTS_MAGIC_REF_THING_SIZE + 2; sz += 3 + ERTS_REF_THING_SIZE; hfragp = new_message_buffer(sz); @@ -3223,7 +3223,7 @@ ets_all_reply(ErtsSchedulerData *esdp, ErtsEtsAllReq **reqpp, hp = &hfragp->mem[0]; ohp = &hfragp->off_heap; list = NIL; - first = esdp->ets_tables.clist; + first = esdp->u.ets_tables.clist; tb = first ? first->common.all.prev : NULL; } @@ -3309,7 +3309,7 @@ erts_handle_yielded_ets_all_request(ErtsAuxWorkData *awdp) return 0; /* All work completed! */ if (yc < ERTS_ETS_ALL_TB_YCNT_START && - yc > erts_atomic_read_nob(&esdp->ets_tables.count)) + yc > erts_atomic_read_nob(&esdp->u.ets_tables.count)) return 1; /* Yield! */ eaydp->ongoing = ongoing = eaydp->queue; @@ -4511,8 +4511,8 @@ erts_ets_sched_spec_data_init(ErtsSchedulerData *esdp) eaydp->hfrag = NULL; eaydp->tab = NULL; eaydp->queue = NULL; - esdp->ets_tables.clist = NULL; - erts_atomic_init_nob(&esdp->ets_tables.count, 0); + esdp->u.ets_tables.clist = NULL; + erts_atomic_init_nob(&esdp->u.ets_tables.count, 0); } @@ -5349,7 +5349,7 @@ erts_db_foreach_table(void (*func)(DbTable *, void *), void *arg, int alive_only for (ix = 0; ix < erts_no_schedulers; ix++) { ErtsSchedulerData *esdp = ERTS_SCHEDULER_IX(ix); - DbTable *first = esdp->ets_tables.clist; + DbTable *first = esdp->u.ets_tables.clist; if (first) { DbTable *tb = first; do { @@ -5393,7 +5393,7 @@ Uint erts_ets_table_count(void) for (six = 0; six < erts_no_schedulers; six++) { ErtsSchedulerData *esdp = &erts_aligned_scheduler_data[six].esd; - tb_count += erts_atomic_read_nob(&esdp->ets_tables.count); + tb_count += erts_atomic_read_nob(&esdp->u.ets_tables.count); } return tb_count; } @@ -5460,7 +5460,7 @@ static void lcnt_update_db_locks_per_sched(void *enable) { DbTable *head; esdp = erts_get_scheduler_data(); - head = esdp->ets_tables.clist; + head = esdp->u.ets_tables.clist; if(head) { DbTable *iterator = head; diff --git a/erts/emulator/beam/erl_init.c b/erts/emulator/beam/erl_init.c index 95e41226fc..ee22672391 100644 --- a/erts/emulator/beam/erl_init.c +++ b/erts/emulator/beam/erl_init.c @@ -2538,7 +2538,7 @@ __decl_noreturn void erts_thr_fatal_error(int err, const char *what) static void -system_cleanup(int flush_async) +system_cleanup(int flush) { /* * Make sure only one thread exits the runtime system. @@ -2568,24 +2568,43 @@ system_cleanup(int flush_async) * (in threaded non smp case). */ - if (!flush_async - || !erts_initialized - ) + if (!flush || !erts_initialized) return; + /* + * We only flush as a result of calling erts_halt() (which in turn + * is called from the erlang:halt() BIF when flushing is enabled); + * otherwise, flushing wont work properly. If erts_halt() has + * been called, 'erts_halt_code' won't equal INT_MIN... + */ + ASSERT(erts_halt_code != INT_MIN); + + /* + * Nif on-halt handlers may have been added after we initiated + * a halt. If so, make sure that these late added handlers are + * executed as well.. + */ + erts_nif_execute_on_halt(); + #ifdef ERTS_ENABLE_LOCK_CHECK erts_lc_check_exact(NULL, 0); #endif erts_exit_flush_async(); + + /* + * Wait for all NIF calls with delayed halt functionality + * enabled to complete before we continue... + */ + erts_nif_wait_calls(); } static int erts_exit_code; static __decl_noreturn void __noreturn -erts_exit_vv(int n, int flush_async, const char *fmt, va_list args1, va_list args2) +erts_exit_vv(int n, int flush, const char *fmt, va_list args1, va_list args2) { - system_cleanup(flush_async); + system_cleanup(flush); if (fmt != NULL && *fmt != '\0') erl_error(fmt, args2); /* Print error message. */ @@ -2598,25 +2617,25 @@ erts_exit_vv(int n, int flush_async, const char *fmt, va_list args1, va_list arg erl_crash_dump_v((char*) NULL, 0, fmt, args1); } - erts_exit_epilogue(); + erts_exit_epilogue(flush); } -__decl_noreturn void __noreturn erts_exit_epilogue(void) +__decl_noreturn void __noreturn erts_exit_epilogue(int flush) { int n = erts_exit_code; sys_tty_reset(n); if (n == ERTS_INTR_EXIT) - exit(0); + (void) (flush ? exit(0) : _exit(0)); else if (n == ERTS_DUMP_EXIT) ERTS_EXIT_AFTER_DUMP(1); else if (n == ERTS_ERROR_EXIT || n == ERTS_ABORT_EXIT) abort(); - exit(n); + (void) (flush ? exit(n) : _exit(n)); } -/* Exit without flushing async threads */ +/* Exit without flushing */ __decl_noreturn void __noreturn erts_exit(int n, const char *fmt, ...) { va_list args1, args2; @@ -2627,8 +2646,12 @@ __decl_noreturn void __noreturn erts_exit(int n, const char *fmt, ...) va_end(args1); } -/* Exit after flushing async threads */ -__decl_noreturn void __noreturn erts_flush_async_exit(int n, char *fmt, ...) +/* + * Exit after flushing. This is a continuation of erts_halt() and wont + * work properly if called by its own without proper initialization + * as made in erts_halt(). + */ +__decl_noreturn void __noreturn erts_flush_exit(int n, char *fmt, ...) { va_list args1, args2; va_start(args1, fmt); diff --git a/erts/emulator/beam/erl_lock_check.c b/erts/emulator/beam/erl_lock_check.c index c6f127e5a4..f299889dcc 100644 --- a/erts/emulator/beam/erl_lock_check.c +++ b/erts/emulator/beam/erl_lock_check.c @@ -108,6 +108,7 @@ static erts_lc_lock_order_t erts_lock_order[] = { { "fun_tab", NULL }, { "environ", NULL }, { "release_literal_areas", NULL }, + { "on_halt", NULL }, { "drv_ev_state_grow", NULL, }, { "drv_ev_state", "address" }, { "safe_hash", "address" }, diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c index 50ac6b92d9..f3cc8be6ed 100644 --- a/erts/emulator/beam/erl_nif.c +++ b/erts/emulator/beam/erl_nif.c @@ -71,6 +71,17 @@ #include <limits.h> #include <stddef.h> /* offsetof */ +#define ERTS_NIF_HALT_INFO_FLAG_BLOCK (1 << 0) +#define ERTS_NIF_HALT_INFO_FLAG_HALTING (1 << 1) +#define ERTS_NIF_HALT_INFO_FLAG_WAITING (1 << 2) + +typedef struct ErtsNifOnHaltData_ ErtsNifOnHaltData; +struct ErtsNifOnHaltData_ { + ErtsNifOnHaltData *next; + ErtsNifOnHaltData *prev; + ErlNifOnHaltCallback *callback; +}; + /* Information about a loaded nif library. * Each successful call to erlang:load_nif will allocate an instance of * erl_module_nif. Two calls opening the same library will thus have the same @@ -95,11 +106,21 @@ struct erl_module_nif { +1 for each owned resource type with callbacks +1 for each ongoing dirty NIF call */ + int flags; + ErtsNifOnHaltData on_halt; Module* mod; /* Can be NULL if purged and dynlib_refc > 0 */ ErlNifFunc _funcs_copy_[1]; /* only used for old libs */ }; +#define ERTS_MOD_NIF_FLG_LOADING (1 << 0) +#define ERTS_MOD_NIF_FLG_DELAY_HALT (1 << 1) +#define ERTS_MOD_NIF_FLG_ON_HALT (1 << 2) + +static erts_atomic_t halt_tse; +static erts_mtx_t on_halt_mtx; +static ErtsNifOnHaltData *on_halt_requests; + typedef ERL_NIF_TERM (*NativeFunPtr)(ErlNifEnv*, int, const ERL_NIF_TERM[]); #ifdef DEBUG @@ -131,6 +152,8 @@ void dtrace_nifenv_str(ErlNifEnv *, char *); #define MIN_HEAP_FRAG_SZ 200 static Eterm* alloc_heap_heavy(ErlNifEnv* env, size_t need, Eterm* hp); +static void install_on_halt_callback(ErtsNifOnHaltData *ohdp); +static void uninstall_on_halt_callback(ErtsNifOnHaltData *ohdp); static ERTS_INLINE int is_scheduler(void) @@ -370,6 +393,25 @@ schedule(ErlNifEnv* env, NativeFunPtr direct_fp, NativeFunPtr indirect_fp, return (ERL_NIF_TERM) THE_NON_VALUE; } +static ERTS_NOINLINE void +eternal_sleep(void) +{ + while (!0) + erts_milli_sleep(1000*1000); +} + +static ERTS_NOINLINE void +handle_halting_unblocked_halt(erts_aint32_t info) +{ + if (info & ERTS_NIF_HALT_INFO_FLAG_WAITING) { + erts_tse_t *tse; + ERTS_THR_MEMORY_BARRIER; + tse = (erts_tse_t *) erts_atomic_read_nob(&halt_tse); + ASSERT(tse); + erts_tse_set(tse); + } + eternal_sleep(); +} static ERL_NIF_TERM dirty_nif_finalizer(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM dirty_nif_exception(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -419,7 +461,34 @@ erts_call_dirty_nif(ErtsSchedulerData *esdp, erts_proc_unlock(c_p, ERTS_PROC_LOCK_MAIN); - result = (*dirty_nif)(&env, codemfa->arity, argv); /* Call dirty NIF */ + if (!(env.mod_nif->flags & ERTS_MOD_NIF_FLG_DELAY_HALT)) { + result = (*dirty_nif)(&env, codemfa->arity, argv); /* Call dirty NIF */ + } + else { + erts_atomic32_t *dirty_nif_halt_info = &esdp->u.dirty_nif_halt_info; + erts_aint32_t info; + info = erts_atomic32_cmpxchg_nob(dirty_nif_halt_info, + ERTS_NIF_HALT_INFO_FLAG_BLOCK, + 0); + if (info != 0) { + ASSERT(info == ERTS_NIF_HALT_INFO_FLAG_HALTING + || info == (ERTS_NIF_HALT_INFO_FLAG_HALTING + | ERTS_NIF_HALT_INFO_FLAG_WAITING)); + eternal_sleep(); + } + result = (*dirty_nif)(&env, codemfa->arity, argv); /* Call dirty NIF */ + info = erts_atomic32_read_band_relb(dirty_nif_halt_info, + ~ERTS_NIF_HALT_INFO_FLAG_BLOCK); + if (info & ERTS_NIF_HALT_INFO_FLAG_HALTING) { + ASSERT(info == (ERTS_NIF_HALT_INFO_FLAG_BLOCK + | ERTS_NIF_HALT_INFO_FLAG_HALTING) + || info == (ERTS_NIF_HALT_INFO_FLAG_BLOCK + | ERTS_NIF_HALT_INFO_FLAG_HALTING + | ERTS_NIF_HALT_INFO_FLAG_WAITING)); + handle_halting_unblocked_halt(info); + } + ASSERT(info == ERTS_NIF_HALT_INFO_FLAG_BLOCK); + } erts_proc_lock(c_p, ERTS_PROC_LOCK_MAIN); @@ -2277,6 +2346,9 @@ static void close_dynlib(struct erl_module_nif* lib) ASSERT(lib->handle != NULL); ASSERT(erts_refc_read(&lib->dynlib_refc,0) == 0); + if (lib->flags & ERTS_MOD_NIF_FLG_ON_HALT) + uninstall_on_halt_callback(&lib->on_halt); + if (lib->entry.unload != NULL) { struct enif_msg_environment_t msg_env; pre_nif_noproc(&msg_env, lib, NULL); @@ -4524,6 +4596,8 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args) lib->handle = handle; erts_refc_init(&lib->refc, 2); /* Erlang code + NIF code */ erts_refc_init(&lib->dynlib_refc, 1); + lib->flags = 0; + lib->on_halt.callback = NULL; ASSERT(opened_rt_list == NULL); lib->mod = module_p; @@ -4642,7 +4716,7 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args) /* Call load or upgrade: */ env.mod_nif = lib; - + lib->flags |= ERTS_MOD_NIF_FLG_LOADING; lib->priv_data = NULL; if (prev_mi->nif != NULL) { /**************** Upgrade ***************/ void* prev_old_data = prev_mi->nif->priv_data; @@ -4676,11 +4750,14 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args) ret = load_nif_error(c_p, "load", "Library load-call unsuccessful (%d).", veto); } } + lib->flags &= ~ERTS_MOD_NIF_FLG_LOADING; if (ret == am_ok) { /* * Everything ok, make NIF code callable. */ + if (lib->flags & ERTS_MOD_NIF_FLG_ON_HALT) + install_on_halt_callback(&lib->on_halt); this_mi->nif = lib; prepare_opened_rt(lib); /* @@ -5046,6 +5123,20 @@ void erl_nif_init() nif_call_table_init(); static_nifs_init(); + + erts_atomic_init_nob(&halt_tse, (erts_aint_t) NULL); + erts_mtx_init(&on_halt_mtx, "on_halt", NIL, + ERTS_LOCK_FLAGS_CATEGORY_GENERIC); + on_halt_requests = NULL; +} + +void +erts_nif_sched_init(ErtsSchedulerData *esdp) +{ + if (esdp->type == ERTS_SCHED_DIRTY_CPU + || esdp->type == ERTS_SCHED_DIRTY_IO) { + erts_atomic32_init_nob(&esdp->u.dirty_nif_halt_info, 0); + } } int erts_nif_get_funcs(struct erl_module_nif* mod, @@ -5133,6 +5224,191 @@ Eterm erts_nif_call_function(Process *p, Process *tracee, return nif_result; } +/* + * Set options... + */ + +int +enif_set_option(ErlNifEnv *env, ErlNifOption opt, ...) +{ + if (!env) + return EINVAL; + + switch (opt) { + + case ERL_NIF_OPT_DELAY_HALT: { + struct erl_module_nif *m = env->mod_nif; + + if (!m || !(m->flags & ERTS_MOD_NIF_FLG_LOADING) + || (m->flags & ERTS_MOD_NIF_FLG_DELAY_HALT)) { + return EINVAL; + } + + m->flags |= ERTS_MOD_NIF_FLG_DELAY_HALT; + + return 0; + } + + case ERL_NIF_OPT_ON_HALT: { + struct erl_module_nif *m = env->mod_nif; + ErlNifOnHaltCallback *on_halt; + va_list argp; + + if (!m || ((m->flags & (ERTS_MOD_NIF_FLG_LOADING + | ERTS_MOD_NIF_FLG_ON_HALT)) + != ERTS_MOD_NIF_FLG_LOADING)) { + return EINVAL; + } + + ASSERT(!m->on_halt.callback); + + va_start(argp, opt); + on_halt = va_arg(argp, ErlNifOnHaltCallback *); + va_end(argp); + if (!on_halt) + return EINVAL; + + m->on_halt.callback = on_halt; + m->flags |= ERTS_MOD_NIF_FLG_ON_HALT; + + return 0; + } + + default: + return EINVAL; + + } +} + +/* + * Halt functionality... + */ + +void +erts_nif_execute_on_halt(void) +{ + ErtsNifOnHaltData *ohdp; + + erts_mtx_lock(&on_halt_mtx); + for (ohdp = on_halt_requests; ohdp; ohdp = ohdp->next) { + struct erl_module_nif *m; + m = ErtsContainerStruct(ohdp, struct erl_module_nif, on_halt); + ohdp->callback(m->priv_data); + } + on_halt_requests = NULL; + erts_mtx_unlock(&on_halt_mtx); +} + +void +erts_nif_notify_halt(void) +{ + int ix; + + erts_nif_execute_on_halt(); + + for (ix = 0; ix < erts_no_dirty_cpu_schedulers; ix++) { + ErtsSchedulerData *esdp = ERTS_DIRTY_CPU_SCHEDULER_IX(ix); + ASSERT(esdp->type == ERTS_SCHED_DIRTY_CPU); + (void) erts_atomic32_read_bor_nob(&esdp->u.dirty_nif_halt_info, + ERTS_NIF_HALT_INFO_FLAG_HALTING); + } + for (ix = 0; ix < erts_no_dirty_io_schedulers; ix++) { + ErtsSchedulerData *esdp = ERTS_DIRTY_IO_SCHEDULER_IX(ix); + ASSERT(esdp->type == ERTS_SCHED_DIRTY_IO); + (void) erts_atomic32_read_bor_nob(&esdp->u.dirty_nif_halt_info, + ERTS_NIF_HALT_INFO_FLAG_HALTING); + } +} + +static ERTS_INLINE void +wait_dirty_call_blocking_halt(ErtsSchedulerData *esdp, erts_tse_t *tse) +{ + erts_atomic32_t *dirty_nif_halt_info = &esdp->u.dirty_nif_halt_info; + erts_aint32_t info = erts_atomic32_read_acqb(dirty_nif_halt_info); + ASSERT(info & ERTS_NIF_HALT_INFO_FLAG_HALTING); + if (!(info & ERTS_NIF_HALT_INFO_FLAG_BLOCK)) + return; + info = erts_atomic32_read_bor_mb(dirty_nif_halt_info, + ERTS_NIF_HALT_INFO_FLAG_WAITING); + if (!(info & ERTS_NIF_HALT_INFO_FLAG_BLOCK)) + return; + while (!0) { + erts_tse_reset(tse); + info = erts_atomic32_read_acqb(dirty_nif_halt_info); + if (!(info & ERTS_NIF_HALT_INFO_FLAG_BLOCK)) + return; + while (!0) { + if (erts_tse_wait(tse) != EINTR) + break; + } + } +} + +void +erts_nif_wait_calls(void) +{ + erts_tse_t *tse; + int ix; + + tse = erts_tse_fetch(); + erts_atomic_set_nob(&halt_tse, (erts_aint_t) tse); + + for (ix = 0; ix < erts_no_dirty_cpu_schedulers; ix++) { + ErtsSchedulerData *esdp = ERTS_DIRTY_CPU_SCHEDULER_IX(ix); + ASSERT(esdp->type == ERTS_SCHED_DIRTY_CPU); + wait_dirty_call_blocking_halt(esdp, tse); + } + for (ix = 0; ix < erts_no_dirty_io_schedulers; ix++) { + ErtsSchedulerData *esdp = ERTS_DIRTY_IO_SCHEDULER_IX(ix); + ASSERT(esdp->type == ERTS_SCHED_DIRTY_IO); + wait_dirty_call_blocking_halt(esdp, tse); + } +} + +static void +install_on_halt_callback(ErtsNifOnHaltData *ohdp) +{ + ASSERT(ohdp->callback); + + erts_mtx_lock(&on_halt_mtx); + ohdp->next = on_halt_requests; + ohdp->prev = NULL; + if (on_halt_requests) + on_halt_requests->prev = ohdp; + on_halt_requests = ohdp; + erts_mtx_unlock(&on_halt_mtx); +} + +static void +uninstall_on_halt_callback(ErtsNifOnHaltData *ohdp) +{ + erts_mtx_lock(&on_halt_mtx); + ohdp->callback = NULL; + if (ohdp->prev) { + ASSERT(on_halt_requests != ohdp); + ohdp->prev->next = ohdp->next; + } + else if (on_halt_requests == ohdp) { + ASSERT(erts_halt_code == INT_MIN); + on_halt_requests = ohdp->next; + } + else { + /* + * Uninstall during halt; and our callback + * has already been called... + */ + ASSERT(erts_halt_code != INT_MIN); + } + if (ohdp->next) { + ohdp->next->prev = ohdp->prev; + } + erts_mtx_unlock(&on_halt_mtx); +} + +/* + * End of halt functionality... + */ + #ifdef USE_VM_PROBES void dtrace_nifenv_str(ErlNifEnv *env, char *process_buf) { diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h index a9a81483f6..1ed1201cf2 100644 --- a/erts/emulator/beam/erl_nif.h +++ b/erts/emulator/beam/erl_nif.h @@ -57,9 +57,10 @@ ** 2.15: 22.0 ERL_NIF_SELECT_CANCEL, enif_select_(read|write) ** enif_term_type ** 2.16: 24.0 enif_init_resource_type, enif_dynamic_resource_call +** 2.17 26.0 enif_set_option */ #define ERL_NIF_MAJOR_VERSION 2 -#define ERL_NIF_MINOR_VERSION 16 +#define ERL_NIF_MINOR_VERSION 17 /* * WHEN CHANGING INTERFACE VERSION, also replace erts version below with @@ -70,7 +71,7 @@ * If you're not on the OTP team, you should use a placeholder like * erts-@MyName@ instead. */ -#define ERL_NIF_MIN_ERTS_VERSION "erts-12.0" +#define ERL_NIF_MIN_ERTS_VERSION "erts-13.1" /* * The emulator will refuse to load a nif-lib with a major version @@ -201,6 +202,8 @@ typedef struct typedef ErlDrvMonitor ErlNifMonitor; +typedef void ErlNifOnHaltCallback(void *priv_data); + typedef struct enif_resource_type_t ErlNifResourceType; typedef void ErlNifResourceDtor(ErlNifEnv*, void*); typedef void ErlNifResourceStop(ErlNifEnv*, void*, ErlNifEvent, int is_direct_call); @@ -325,6 +328,11 @@ typedef enum { #define ERL_NIF_THR_DIRTY_CPU_SCHEDULER 2 #define ERL_NIF_THR_DIRTY_IO_SCHEDULER 3 +typedef enum { + ERL_NIF_OPT_DELAY_HALT = 1, + ERL_NIF_OPT_ON_HALT = 2 +} ErlNifOption; + #if (defined(__WIN32__) || defined(_WIN32) || defined(_WIN32_)) # define ERL_NIF_API_FUNC_DECL(RET_TYPE, NAME, ARGS) RET_TYPE (*NAME) ARGS typedef struct { diff --git a/erts/emulator/beam/erl_nif_api_funcs.h b/erts/emulator/beam/erl_nif_api_funcs.h index f9004eb64b..a2a1052b59 100644 --- a/erts/emulator/beam/erl_nif_api_funcs.h +++ b/erts/emulator/beam/erl_nif_api_funcs.h @@ -219,6 +219,8 @@ ERL_NIF_API_FUNC_DECL(ErlNifTermType,enif_term_type,(ErlNifEnv* env, ERL_NIF_TER ERL_NIF_API_FUNC_DECL(ErlNifResourceType*,enif_init_resource_type,(ErlNifEnv*, const char* name_str, const ErlNifResourceTypeInit*, ErlNifResourceFlags flags, ErlNifResourceFlags* tried)); ERL_NIF_API_FUNC_DECL(int,enif_dynamic_resource_call,(ErlNifEnv*, ERL_NIF_TERM mod, ERL_NIF_TERM name, ERL_NIF_TERM rsrc, void* call_data)); +ERL_NIF_API_FUNC_DECL(int, enif_set_option, (ErlNifEnv *env, ErlNifOption opt, ...)); + /* ** ADD NEW ENTRIES HERE (before this comment) !!! */ @@ -408,6 +410,7 @@ ERL_NIF_API_FUNC_DECL(int,enif_dynamic_resource_call,(ErlNifEnv*, ERL_NIF_TERM m # define enif_term_type ERL_NIF_API_FUNC_MACRO(enif_term_type) # define enif_init_resource_type ERL_NIF_API_FUNC_MACRO(enif_init_resource_type) # define enif_dynamic_resource_call ERL_NIF_API_FUNC_MACRO(enif_dynamic_resource_call) +# define enif_set_option ERL_NIF_API_FUNC_MACRO(enif_set_option) /* ** ADD NEW ENTRIES HERE (before this comment) */ diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c index 17780a7e87..2e45c00cb8 100644 --- a/erts/emulator/beam/erl_process.c +++ b/erts/emulator/beam/erl_process.c @@ -2498,7 +2498,7 @@ notify_reap_ports_relb(void) } erts_atomic32_t erts_halt_progress; -int erts_halt_code; +int erts_halt_code = INT_MIN; static ERTS_INLINE erts_aint32_t handle_reap_ports(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting) @@ -2543,7 +2543,7 @@ handle_reap_ports(ErtsAuxWorkData *awdp, erts_aint32_t aux_work, int waiting) erts_port_release(prt); } if (erts_atomic32_dec_read_nob(&erts_halt_progress) == 0) { - erts_flush_async_exit(erts_halt_code, ""); + erts_flush_exit(erts_halt_code, ""); } } return aux_work & ~ERTS_SSI_AUX_WORK_REAP_PORTS; @@ -6298,7 +6298,7 @@ erts_init_scheduling(int no_schedulers, int no_schedulers_online, int no_poll_th erts_atomic32_init_relb(&erts_halt_progress, -1); - erts_halt_code = 0; + erts_halt_code = INT_MIN; } @@ -8656,6 +8656,7 @@ sched_thread_func(void *vesdp) erts_ets_sched_spec_data_init(esdp); erts_utils_sched_spec_data_init(); + erts_nif_sched_init(esdp); process_main(esdp); @@ -8706,6 +8707,8 @@ sched_dirty_cpu_thread_func(void *vesdp) erts_proc_lock_prepare_proc_lock_waiter(); + erts_nif_sched_init(esdp); + erts_dirty_process_main(esdp); /* No schedulers should *ever* terminate */ erts_exit(ERTS_ABORT_EXIT, @@ -8754,6 +8757,8 @@ sched_dirty_io_thread_func(void *vesdp) erts_proc_lock_prepare_proc_lock_waiter(); + erts_nif_sched_init(esdp); + erts_dirty_process_main(esdp); /* No schedulers should *ever* terminate */ erts_exit(ERTS_ABORT_EXIT, @@ -14820,6 +14825,7 @@ void erts_halt(int code) ERTS_RUNQ_FLGS_SET(ERTS_DIRTY_CPU_RUNQ, ERTS_RUNQ_FLG_HALTING); ERTS_RUNQ_FLGS_SET(ERTS_DIRTY_IO_RUNQ, ERTS_RUNQ_FLG_HALTING); erts_halt_code = code; + erts_nif_notify_halt(); } } diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h index 8dc55a8da0..a5922e0bf3 100644 --- a/erts/emulator/beam/erl_process.h +++ b/erts/emulator/beam/erl_process.h @@ -727,7 +727,10 @@ struct ErtsSchedulerData_ { ErtsSchedWallTime sched_wall_time; ErtsGCInfo gc_info; ErtsPortTaskHandle nosuspend_port_task_handle; - ErtsEtsTables ets_tables; + union { + ErtsEtsTables ets_tables; + erts_atomic32_t dirty_nif_halt_info; + } u; #ifdef ERTS_DO_VERIFY_UNUSED_TEMP_ALLOC erts_alloc_verify_func_t verify_unused_temp_alloc; Allctr_t *verify_unused_temp_alloc_data; diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h index 8c4dd90966..a6b26bc50b 100644 --- a/erts/emulator/beam/global.h +++ b/erts/emulator/beam/global.h @@ -127,6 +127,10 @@ Eterm erts_load_nif(Process *c_p, ErtsCodePtr I, Eterm filename, Eterm args); void erts_unload_nif(struct erl_module_nif* nif); extern void erl_nif_init(void); +extern void erts_nif_sched_init(ErtsSchedulerData *esdp); +extern void erts_nif_execute_on_halt(void); +extern void erts_nif_notify_halt(void); +extern void erts_nif_wait_calls(void); extern int erts_nif_get_funcs(struct erl_module_nif*, struct enif_func_t **funcs); extern Module *erts_nif_get_module(struct erl_module_nif*); @@ -1044,9 +1048,9 @@ double erts_get_positive_zero_float(void); /* config.c */ -__decl_noreturn void __noreturn erts_exit_epilogue(void); +__decl_noreturn void __noreturn erts_exit_epilogue(int flush); __decl_noreturn void __noreturn erts_exit(int n, const char*, ...); -__decl_noreturn void __noreturn erts_flush_async_exit(int n, char*, ...); +__decl_noreturn void __noreturn erts_flush_exit(int n, char*, ...); void erl_error(const char*, va_list); /* This controls whether sharing-preserving copy is used by Erlang */ diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c index dfdbe475b3..81c3ca29ae 100644 --- a/erts/emulator/beam/io.c +++ b/erts/emulator/beam/io.c @@ -3772,7 +3772,7 @@ terminate_port(Port *prt) if ((state & ERTS_PORT_SFLG_HALT) && (erts_atomic32_dec_read_nob(&erts_halt_progress) == 0)) { erts_port_release(prt); /* We will exit and never return */ - erts_flush_async_exit(erts_halt_code, ""); + erts_flush_exit(erts_halt_code, ""); } if (is_internal_port(send_closed_port_id)) deliver_result(NULL, send_closed_port_id, connected_id, am_closed); diff --git a/erts/emulator/test/dirty_nif_SUITE.erl b/erts/emulator/test/dirty_nif_SUITE.erl index 59d791eb2b..2bba329234 100644 --- a/erts/emulator/test/dirty_nif_SUITE.erl +++ b/erts/emulator/test/dirty_nif_SUITE.erl @@ -29,13 +29,49 @@ -export([all/0, suite/0, init_per_suite/1, end_per_suite/1, init_per_testcase/2, end_per_testcase/2, + init_per_group/2, end_per_group/2, groups/0, dirty_nif/1, dirty_nif_send/1, dirty_nif_exception/1, call_dirty_nif_exception/1, dirty_scheduler_exit/1, dirty_call_while_terminated/1, dirty_heap_access/1, dirty_process_info/1, dirty_process_register/1, dirty_process_trace/1, code_purge/1, literal_area/1, dirty_nif_send_traced/1, - nif_whereis/1, nif_whereis_parallel/1, nif_whereis_proxy/1]). + nif_whereis/1, nif_whereis_parallel/1, nif_whereis_proxy/1, + set_halt_options_from_nif/1, + delay_halt/1, + delay_halt_old_code/1, + delay_halt_old_and_new_code/1, + flush_false/1, + on_halt/1, + on_halt_old_code/1, + on_halt_old_and_new_code/1, + sync_halt/1, + many_delay_halt/1, + many_on_halt/1]). + +-export([load_nif/2]). + +-nifs([lib_loaded/0, + call_dirty_nif/3, + send_from_dirty_nif/1, + send_wait_from_dirty_nif/1, + call_dirty_nif_exception/1, + call_dirty_nif_zero_args/0, + dirty_call_while_terminated_nif/1, + dirty_sleeper/0, + dirty_sleeper/1, + dirty_heap_access_nif/1, + whereis_term/2, + whereis_send/3, + dirty_terminating_literal_access/2, + delay_halt_normal/3, + delay_halt_io_bound/3, + delay_halt_cpu_bound/3, + sync_halt_io_bound/2, + sync_halt_cpu_bound/2, + set_halt_option_from_nif_normal/1, + set_halt_option_from_nif_io_bound/1, + set_halt_option_from_nif_cpu_bound/1]). -define(nif_stub,nif_stub_error(?LINE)). @@ -55,23 +91,38 @@ all() -> literal_area, dirty_nif_send_traced, nif_whereis, - nif_whereis_parallel]. + nif_whereis_parallel, + {group, halt_normal}, + {group, halt_dirty_cpu}, + {group, halt_dirty_io}, + {group, halt_misc}]. + +halt_sched_tests() -> + [set_halt_options_from_nif, delay_halt, delay_halt_old_code, delay_halt_old_and_new_code]. +halt_dirty_sched_tests() -> + [sync_halt, flush_false]. + +groups() -> + [{halt_normal, [parallel], halt_sched_tests()}, + {halt_dirty_cpu, [parallel], halt_sched_tests()++halt_dirty_sched_tests()}, + {halt_dirty_io, [parallel], halt_sched_tests()++halt_dirty_sched_tests()}, + {halt_misc, [parallel], [on_halt, on_halt_old_code, on_halt_old_and_new_code, many_on_halt, many_delay_halt]}]. + +init_per_group(Group, Config) -> + [{group, Group} | Config]. + +end_per_group(_, Config) -> + proplists:delete(group, Config). init_per_suite(Config) -> - case erlang:system_info(dirty_cpu_schedulers) of - N when N > 0 -> - case lib_loaded() of - false -> - ok = erlang:load_nif( - filename:join(?config(data_dir, Config), - "dirty_nif_SUITE"), []); - true -> - ok - end, - Config; - _ -> - {skipped, "No dirty scheduler support"} - end. + case lib_loaded() of + false -> + ok = erlang:load_nif(filename:join(?config(data_dir, Config), + "dirty_nif_SUITE"), []); + true -> + ok + end, + Config. end_per_suite(_Config) -> ok. @@ -82,6 +133,9 @@ init_per_testcase(Case, Config) -> end_per_testcase(_Case, _Config) -> ok. +load_nif(NifLib, LibInfo) -> + erlang:load_nif(NifLib, LibInfo). + dirty_nif(Config) when is_list(Config) -> Val1 = 42, Val2 = "Erlang", @@ -536,6 +590,356 @@ literal_area(Config) when is_list(Config) -> literal_area_collector_test:check_idle(5000), {comment, "Waited "++integer_to_list(TMO)++" milliseconds after purge"}. +set_halt_options_from_nif(Config) when is_list(Config) -> + case ?config(group, Config) of + halt_normal -> + error = set_halt_option_from_nif_normal(set_on_halt_handler), + error = set_halt_option_from_nif_normal(delay_halt); + halt_dirty_cpu -> + error = set_halt_option_from_nif_cpu_bound(set_on_halt_handler), + error = set_halt_option_from_nif_cpu_bound(delay_halt); + halt_dirty_io -> + error = set_halt_option_from_nif_io_bound(set_on_halt_handler), + error = set_halt_option_from_nif_io_bound(delay_halt) + end, + ok. + +delay_halt(Config) when is_list(Config) -> + delay_halt(Config, new_code). + +delay_halt_old_code(Config) when is_list(Config) -> + delay_halt(Config, old_code). + +delay_halt_old_and_new_code(Config) when is_list(Config) -> + delay_halt(Config, old_and_new_code). + +delay_halt(Config, Type) -> + Suite = filename:join(?config(data_dir, Config), ?MODULE_STRING), + Priv = proplists:get_value(priv_dir, Config), + TypeSuffix = "_"++atom_to_list(Type), + {Fun, FileName} = case ?config(group, Config) of + halt_normal -> + {fun delay_halt_normal/3, "delay_halt_normal"++TypeSuffix}; + halt_dirty_io -> + {fun delay_halt_io_bound/3, "delay_halt_io_bound"++TypeSuffix}; + halt_dirty_cpu -> + {fun delay_halt_cpu_bound/3, "delay_halt_cpu_bound"++TypeSuffix} + end, + Tester = self(), + NifFileName = filename:join(Priv, FileName), + {ok, Peer, Node} = ?CT_PEER(), + Mon = erlang:monitor(process, Peer), + ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {delay_halt}]), + ok = erpc:call(Node, file, set_cwd, [Priv]), + Proxy = spawn_link(Node, + fun () -> + receive + {delay_halt, _} = Msg -> + unlink(Tester), + Tester ! Msg + end + end), + Start = erlang:monotonic_time(millisecond), + ok = erpc:cast(Node, fun () -> Fun(Proxy, FileName, 2) end), + receive {delay_halt, Pid} when is_pid(Pid), Node == node(Pid) -> ok end, + case Type of + new_code -> + ok; + old_code -> + true = erpc:call(Node, erlang, delete_module, [dirty_nif_SUITE]); + old_and_new_code -> + true = erpc:call(Node, erlang, delete_module, [dirty_nif_SUITE]), + ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {delay_halt}]), + Proxy2 = spawn_link(Node, + fun () -> + receive + {delay_halt, _} = Msg -> + unlink(Tester), + Tester ! Msg + end + end), + ok = erpc:cast(Node, fun () -> Fun(Proxy2, FileName++"_new_code", 2) end), + receive {delay_halt, Pid2} when is_pid(Pid2), Node == node(Pid2) -> ok end + end, + ok = erpc:cast(Node, erlang, halt, []), + ok = wait_until(fun () -> + {ok, <<"ok">>} == file:read_file(NifFileName) + andalso + (Type /= old_and_new_code + orelse {ok, <<"ok">>} == file:read_file(NifFileName++"_new_code")) + end, + 6000), + Time = erlang:monotonic_time(millisecond) - Start, + ct:log("~s time=~pms", [FileName, Time]), + true = Time >= 2000, + receive {'DOWN', Mon, process, Peer, _} -> ok end, + ok. + +flush_false(Config) when is_list(Config) -> + Suite = filename:join(?config(data_dir, Config), ?MODULE_STRING), + Priv = proplists:get_value(priv_dir, Config), + {Fun, FileName} = case ?config(group, Config) of + halt_dirty_io -> + {fun delay_halt_io_bound/3, "flush_false_io_bound"}; + halt_dirty_cpu -> + {fun delay_halt_cpu_bound/3, "flush_false_cpu_bound"} + end, + Tester = self(), + NifFileName = filename:join(Priv, FileName), + OnHaltBaseName = FileName++"_on_halt", + OnHaltFileName = filename:join(Priv, OnHaltBaseName), + {ok, Peer, Node} = ?CT_PEER(), + Mon = erlang:monitor(process, Peer), + ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {sync_halt, OnHaltBaseName, 1}]), + ok = erpc:call(Node, file, set_cwd, [Priv]), + Proxy = spawn_link(Node, + fun () -> + receive + {delay_halt, _} = Msg -> + unlink(Tester), + Tester ! Msg + end + end), + Start = erlang:monotonic_time(millisecond), + ok = erpc:cast(Node, fun () -> Fun(Proxy, FileName, 1) end), + receive {delay_halt, Pid} when is_pid(Pid), Node == node(Pid) -> ok end, + ok = erpc:cast(Node, erlang, halt, [0, [{flush,false}]]), + receive {'DOWN', Mon, process, Peer, _} -> ok end, + Time = erlang:monotonic_time(millisecond) - Start, + ct:log("~s time=~pms", [FileName, Time]), + Wait = 3000-Time, + if Wait > 0 -> receive after Wait -> ok end; + true -> ok + end, + {error,enoent} = file:read_file(NifFileName), + {error,enoent} = file:read_file(OnHaltFileName), + ok. + +many_delay_halt(Config) when is_list(Config) -> + try + many_delay_halt_test(Config) + catch + throw:{skip, _} = Skip -> + Skip + end. + +many_delay_halt_test(Config) -> + Suite = filename:join(?config(data_dir, Config), ?MODULE_STRING), + Priv = proplists:get_value(priv_dir, Config), + Tester = self(), + {ok, Peer, Node} = ?CT_PEER(), + Chk = fun () -> + case erlang:system_info(schedulers_online) of + 1 -> throw({skip, "Too few schedulers online"}); + _ -> ok + end, + case erlang:system_info(dirty_cpu_schedulers_online) of + 1 -> throw({skip, "Too few dirty cpu schedulers online"}); + _ -> ok + end, + case erlang:system_info(dirty_io_schedulers) of + 1 -> throw({skip, "Too few dirty io schedulers online"}); + _ -> ok + end + end, + try + erpc:call(Node, Chk) + catch + throw:{skip, _} = Skip -> + peer:stop(Peer), + throw(Skip) + end, + Mon = erlang:monitor(process, Peer), + ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {delay_halt}]), + ok = erpc:call(Node, file, set_cwd, [Priv]), + ProxyFun = fun (Tag) -> + fun () -> + receive + {delay_halt, _} = Msg -> + unlink(Tester), + Tester ! {Tag, Msg} + end + end + end, + [P1, P2, P3, P4, P5] = [spawn_link(Node, ProxyFun(X)) || X <- lists:seq(1, 5)], + Start = erlang:monotonic_time(millisecond), + ok = erpc:cast(Node, fun () -> delay_halt_io_bound(P1, "many_delay_halt_io2", 2) end), + ok = erpc:cast(Node, fun () -> delay_halt_io_bound(P2, "many_delay_halt_io1", 1) end), + ok = erpc:cast(Node, fun () -> delay_halt_cpu_bound(P3, "many_delay_halt_cpu1", 1) end), + ok = erpc:cast(Node, fun () -> delay_halt_cpu_bound(P4, "many_delay_halt_cpu2", 2) end), + _ = [receive + {X, {delay_halt, Pid}} when is_pid(Pid), Node == node(Pid) -> + ok + end || X <- lists:seq(1, 4)], + ok = erpc:cast(Node, fun () -> delay_halt_normal(P5, "many_delay_halt_normal", 1) end), + receive + {5, {delay_halt, Pid}} when is_pid(Pid), Node == node(Pid) -> + ok + end, + ok = erpc:cast(Node, erlang, halt, []), + ok = wait_until(fun () -> + {ok, <<"ok">>} == file:read_file(filename:join(Priv, "many_delay_halt_io2")) + andalso {ok, <<"ok">>} == file:read_file(filename:join(Priv, "many_delay_halt_cpu2")) + end, + 3000), + {ok, <<"ok">>} = file:read_file(filename:join(Priv, "many_delay_halt_cpu1")), + {ok, <<"ok">>} = file:read_file(filename:join(Priv, "many_delay_halt_io1")), + {ok, <<"ok">>} = file:read_file(filename:join(Priv, "many_delay_halt_normal")), + Time = erlang:monotonic_time(millisecond) - Start, + ct:log("many_delay_halt time=~pms", [Time]), + true = Time >= 2000, + receive {'DOWN', Mon, process, Peer, _} -> ok end, + ok. + +on_halt(Config) when is_list(Config) -> + on_halt(Config, new_code). + +on_halt_old_code(Config) when is_list(Config) -> + on_halt(Config, old_code). + +on_halt_old_and_new_code(Config) when is_list(Config) -> + on_halt(Config, old_and_new_code). + +on_halt(Config, Type) -> + Suite = filename:join(?config(data_dir, Config), ?MODULE_STRING), + Priv = proplists:get_value(priv_dir, Config), + FileName = "on_halt_"++atom_to_list(Type), + OnHaltFileName = filename:join(Priv, FileName), + {ok, Peer, Node} = ?CT_PEER(), + Mon = erlang:monitor(process, Peer), + ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {on_halt, FileName, 1}]), + case Type of + new_code -> + ok; + old_code -> + true = erpc:call(Node, erlang, delete_module, [dirty_nif_SUITE]); + old_and_new_code -> + true = erpc:call(Node, erlang, delete_module, [dirty_nif_SUITE]), + ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {on_halt, FileName++"_new", 1}]) + end, + ok = erpc:call(Node, file, set_cwd, [Priv]), + Start = erlang:monotonic_time(millisecond), + ok = erpc:cast(Node, erlang, halt, []), + ok = wait_until(fun () -> + {ok, <<"ok">>} == file:read_file(OnHaltFileName) + andalso (Type /= old_and_new_code + orelse {ok, <<"ok">>} == file:read_file(OnHaltFileName++"_new")) + end, + 3000), + Time = erlang:monotonic_time(millisecond) - Start, + ct:log("~s time=~pms", [FileName, Time]), + if Type == old_and_new_code -> true = Time >= 2000; + true -> true = Time >= 1000 + end, + receive {'DOWN', Mon, process, Peer, _} -> ok end, + ok. + +on_halt_module_code_format() -> + lists:flatten(["-module(~s).~n", + "-export([load/1, lib_loaded/0]).~n", + "-nifs([lib_loaded/0]).~n", + "load(SoFile) -> erlang:load_nif(SoFile, ?MODULE_STRING).~n", + "lib_loaded() -> false.~n"]). + +many_on_halt(Config) when is_list(Config) -> + DDir = ?config(data_dir, Config), + Priv = proplists:get_value(priv_dir, Config), + OnHaltModules = ["on_halt_a","on_halt_b","on_halt_c","on_halt_d","on_halt_e","on_halt_f"], + DeleteOnHaltModules = ["on_halt_a","on_halt_c","on_halt_d","on_halt_f"], + PurgeOnHaltModules = DeleteOnHaltModules -- ["on_halt_d"], + ActiveOnHaltModules = OnHaltModules -- PurgeOnHaltModules, + lists:foreach(fun (ModStr) -> + Code = io_lib:format(on_halt_module_code_format(), [ModStr]), + ok = file:write_file(filename:join(DDir, ModStr++".erl"), Code) + end, + OnHaltModules), + {ok, Peer, Node} = ?CT_PEER(), + Mon = erlang:monitor(process, Peer), + ok = erpc:call(Node, + fun () -> + ok = file:set_cwd(Priv), + lists:foreach(fun (ModStr) -> + AbsModStr = filename:join(DDir, ModStr), + {ok,Mod,Bin} = compile:file(AbsModStr, [binary]), + {module, Mod} = erlang:load_module(Mod, Bin), + ok = Mod:load(AbsModStr), + true = Mod:lib_loaded() + end, + OnHaltModules), + lists:foreach(fun (ModStr) -> + Mod = list_to_atom(ModStr), + true = erlang:delete_module(Mod) + end, DeleteOnHaltModules), + lists:foreach(fun (ModStr) -> + Mod = list_to_atom(ModStr), + true = erlang:purge_module(Mod) + end, PurgeOnHaltModules), + ok + end), + Start = erlang:monotonic_time(millisecond), + ok = erpc:cast(Node, erlang, halt, []), + ok = wait_until(fun () -> + try + lists:foreach(fun (ModStr) -> + FileName = filename:join(Priv, ModStr), + {ok, <<"ok">>} = file:read_file(FileName) + end, ActiveOnHaltModules), + true + catch + _:_ -> + false + end + end, + 1000*(length(ActiveOnHaltModules)+1)), + Time = erlang:monotonic_time(millisecond) - Start, + ct:log("many_on_halt time=~pms", [Time]), + true = Time >= length(ActiveOnHaltModules)*1000, + receive {'DOWN', Mon, process, Peer, _} -> ok end, + ok. + +sync_halt(Config) when is_list(Config) -> + Suite = filename:join(?config(data_dir, Config), ?MODULE_STRING), + Priv = proplists:get_value(priv_dir, Config), + {Fun, FileName} = case ?config(group, Config) of + halt_dirty_io -> + {fun sync_halt_io_bound/2, "sync_halt_io_bound"}; + halt_dirty_cpu -> + {fun sync_halt_cpu_bound/2, "sync_halt_cpu_bound"} + end, + Tester = self(), + NifFileName = filename:join(Priv, FileName), + OnHaltBaseFileName = FileName++".onhalt", + OnHaltFileName = filename:join(Priv, OnHaltBaseFileName), + {ok, Peer, Node} = ?CT_PEER(), + Mon = erlang:monitor(process, Peer), + ok = erpc:call(Node, ?MODULE, load_nif, [Suite, {sync_halt, OnHaltBaseFileName, 1}]), + ok = erpc:call(Node, file, set_cwd, [Priv]), + Proxy = spawn_link(Node, + fun () -> + receive + {sync_halt, _} = Msg -> + unlink(Tester), + Tester ! Msg + end + end), + ok = erpc:cast(Node, fun () -> Fun(Proxy, FileName) end), + receive {sync_halt, Pid} when is_pid(Pid), Node == node(Pid) -> ok end, + Start = erlang:monotonic_time(millisecond), + ok = erpc:cast(Node, erlang, halt, []), + ok = wait_until(fun () -> + {ok, <<"ok">>} == file:read_file(OnHaltFileName) + end, + 2000), + ok = wait_until(fun () -> + {ok, <<"ok">>} == file:read_file(NifFileName) + end, + 4000), + Time = erlang:monotonic_time(millisecond) - Start, + ct:log("~s time=~pms", [FileName, Time]), + true = Time >= 1000, + receive {'DOWN', Mon, process, Peer, _} -> ok end, + ok. + %% %% Internal... %% @@ -774,6 +1178,14 @@ dirty_heap_access_nif(_) -> ?nif_stub. whereis_term(_Type,_Name) -> ?nif_stub. whereis_send(_Type,_Name,_Msg) -> ?nif_stub. dirty_terminating_literal_access(_Me, _Literal) -> ?nif_stub. +delay_halt_normal(_Pid, _FileName, _Delay) -> ?nif_stub. +delay_halt_io_bound(_Pid, _FileName, _Delay) -> ?nif_stub. +delay_halt_cpu_bound(_Pid, _FileName, _Delay) -> ?nif_stub. +sync_halt_io_bound(_Pid, _FileName) -> ?nif_stub. +sync_halt_cpu_bound(_Pid, _FileName) -> ?nif_stub. +set_halt_option_from_nif_normal(_Op) -> ?nif_stub. +set_halt_option_from_nif_io_bound(_Op) -> ?nif_stub. +set_halt_option_from_nif_cpu_bound(_Op) -> ?nif_stub. nif_stub_error(Line) -> exit({nif_not_loaded,module,?MODULE,line,Line}). diff --git a/erts/emulator/test/dirty_nif_SUITE_data/Makefile.src b/erts/emulator/test/dirty_nif_SUITE_data/Makefile.src index 4462afd815..55ca552cb2 100644 --- a/erts/emulator/test/dirty_nif_SUITE_data/Makefile.src +++ b/erts/emulator/test/dirty_nif_SUITE_data/Makefile.src @@ -1,5 +1,5 @@ -NIF_LIBS = dirty_nif_SUITE@dll@ +NIF_LIBS = dirty_nif_SUITE@dll@ on_halt_a@dll@ on_halt_b@dll@ on_halt_c@dll@ on_halt_d@dll@ on_halt_e@dll@ on_halt_f@dll@ all: $(NIF_LIBS) echo_drv@dll@ diff --git a/erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c b/erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c index fb5146278b..7f0fc9ea46 100644 --- a/erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c +++ b/erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c @@ -19,11 +19,14 @@ */ #include <erl_nif.h> #include <assert.h> +#include <errno.h> #ifdef __WIN32__ #include <windows.h> #else #include <unistd.h> #endif +#include <stdio.h> +#include <string.h> /* * Hack to get around this function missing from the NIF API. @@ -43,8 +46,128 @@ static ERL_NIF_TERM atom_pid; static ERL_NIF_TERM atom_port; static ERL_NIF_TERM atom_send; +typedef struct { + int halting; + int on_halt_wait; + ErlNifMutex *mtx; + ErlNifCond *cnd; + char *filename; +} PrivData; + +static PrivData *make_priv_data(void) +{ + PrivData *pdata = enif_alloc(sizeof(PrivData)); + if (!pdata) + return NULL; + pdata->halting = 0; + pdata->on_halt_wait = 0; + pdata->mtx = NULL; + pdata->cnd = NULL; + pdata->filename = NULL; + return pdata; +} + +static void unload(ErlNifEnv *env, void *priv_data) +{ + if (priv_data) { + PrivData *pdata = priv_data; + if (pdata->mtx) + enif_mutex_destroy(pdata->mtx); + if (pdata->cnd) + enif_cond_destroy(pdata->cnd); + if (pdata->filename) + enif_free(pdata->filename); + enif_free(pdata); + } +} + +static void on_halt(void *priv_data); + static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { + int arity; + const ERL_NIF_TERM *array; + if (enif_get_tuple(env, load_info, &arity, &array)) { + char atom_text[32]; + int err; + unsigned filename_len; + PrivData *pdata = NULL; + + if (arity < 1 || 3 < arity) + return __LINE__; + if (!enif_get_atom(env, array[0], &atom_text[0], + sizeof(atom_text), ERL_NIF_LATIN1)) { + return __LINE__; + } + pdata = make_priv_data(); + if (!pdata) + return __LINE__; + if (strcmp(atom_text, "on_halt") == 0) { + if (0 != enif_set_option(env, ERL_NIF_OPT_ON_HALT, on_halt)) { + unload(env, pdata); + return __LINE__; + } + } + else if (strcmp(atom_text, "delay_halt") == 0) { + if (0 != enif_set_option(env, ERL_NIF_OPT_DELAY_HALT)) { + unload(env, pdata); + return __LINE__; + } + } + else if (strcmp(atom_text, "sync_halt") == 0) { + if (0 != enif_set_option(env, ERL_NIF_OPT_ON_HALT, on_halt)) { + unload(env, pdata); + return __LINE__; + } + if (0 != enif_set_option(env, ERL_NIF_OPT_DELAY_HALT)) { + unload(env, pdata); + return __LINE__; + } + pdata->mtx = enif_mutex_create("sync_halt_dirty_nif_SUITE"); + pdata->cnd = enif_cond_create("sync_halt_dirty_nif_SUITE"); + if (!pdata->mtx || !pdata->cnd) { + unload(env, pdata); + return __LINE__; + } + } + else { + unload(env, pdata); + return __LINE__; + } + + if (arity >= 2) { + if (!enif_get_list_length(env, array[1], &filename_len)) { + unload(env, pdata); + return __LINE__; + } + if (filename_len > 0) { + filename_len++; + pdata->filename = enif_alloc(filename_len); + if (!pdata->filename) { + unload(env, pdata); + return __LINE__; + } + if (filename_len != enif_get_string(env, + array[1], + pdata->filename, + filename_len, + ERL_NIF_LATIN1)) { + unload(env, pdata); + return __LINE__; + } + } + } + if (arity == 3) { + if (!enif_get_int(env, array[2], &pdata->on_halt_wait) + || pdata->on_halt_wait < 0 + || pdata->on_halt_wait*1000 < 0) { + unload(env, pdata); + return __LINE__; + } + } + *priv_data = (void *) pdata; + } + atom_badarg = enif_make_atom(env, "badarg"); atom_error = enif_make_atom(env, "error"); atom_false = enif_make_atom(env,"false"); @@ -57,6 +180,11 @@ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) return 0; } +static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) +{ + return load(env, priv_data, load_info); +} + static ERL_NIF_TERM lib_loaded(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { return enif_make_atom(env, "true"); @@ -468,6 +596,149 @@ static ERL_NIF_TERM dirty_terminating_literal_access(ErlNifEnv* env, int argc, c return self_term; } +static int fn_write_ok(char *filename) +{ + FILE *file = fopen(filename, "w"); + if (!file) + return EINVAL; + if (1 != fwrite("ok", 2, 1, file)) + return EINVAL; + fclose(file); + return 0; +} + +static int efn_write_ok(ErlNifEnv *env, const ERL_NIF_TERM arg) +{ + int res; + unsigned filename_len; + char *filename; + + if (!enif_get_list_length(env, arg, &filename_len) || filename_len < 2) { + res = EINVAL; + goto done; + } + filename_len++; + filename = enif_alloc(filename_len); + if (!filename) { + res = ENOMEM; + goto done; + } + if (filename_len != enif_get_string(env, + arg, + filename, + filename_len, + ERL_NIF_LATIN1)) { + res = EINVAL; + goto done; + } + res = fn_write_ok(filename); +done: + if (filename) + enif_free(filename); + switch (res) { + case 0: + return atom_ok; + case ENOMEM: + return enif_raise_exception(env, enif_make_atom(env, "enomem")); + default: + return enif_make_badarg(env); + } +} + +static ERL_NIF_TERM delay_halt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM msg; + ErlNifPid receiver, self; + int res, secs; + + if (argc != 3) + return enif_make_badarg(env); + if (!enif_get_int(env, argv[2], &secs)) + return enif_make_badarg(env); + if (secs < 0 || secs*1000 < 0) + return enif_make_badarg(env); + if (!enif_self(env, &self)) + return enif_make_badarg(env); + if (!enif_get_local_pid(env, argv[0], &receiver)) + return enif_make_badarg(env); + msg = enif_make_tuple2(env, enif_make_atom(env, "delay_halt"), enif_make_pid(env, &self)); + res = enif_send(env, &receiver, NULL, msg); + if (!res) + return enif_make_badarg(env); + +#ifdef __WIN32__ + Sleep(secs*1000); +#else + sleep(secs); +#endif + return efn_write_ok(env, argv[1]); +} + +static ERL_NIF_TERM sync_halt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + ERL_NIF_TERM msg; + ErlNifPid receiver, self; + int res; + PrivData *pdata = enif_priv_data(env); + if (!pdata) + return enif_raise_exception(env, enif_make_atom(env, "missing_priv_data")); + if (argc != 2) + return enif_make_badarg(env); + if (!enif_self(env, &self)) + return enif_make_badarg(env); + if (!enif_get_local_pid(env, argv[0], &receiver)) + return enif_make_badarg(env); + msg = enif_make_tuple2(env, enif_make_atom(env, "sync_halt"), enif_make_pid(env, &self)); + res = enif_send(env, &receiver, NULL, msg); + if (!res) + return enif_make_badarg(env); + enif_mutex_lock(pdata->mtx); + while (!pdata->halting) + enif_cond_wait(pdata->cnd, pdata->mtx); + enif_mutex_unlock(pdata->mtx); + return efn_write_ok(env, argv[1]); +} + +static ERL_NIF_TERM set_halt_option_from_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{ + unsigned len; + char atom_text[32]; + if (argc != 1) + return enif_make_badarg(env); + if (!enif_get_atom(env, argv[0], &atom_text[0], sizeof(atom_text), ERL_NIF_LATIN1)) + return enif_make_badarg(env); + if (strcmp(atom_text, "set_on_halt_handler") == 0) { + if (0 == enif_set_option(env, ERL_NIF_OPT_ON_HALT, on_halt)) + return atom_ok; + return atom_error; + } + else if (strcmp(atom_text, "delay_halt") == 0) { + if (0 == enif_set_option(env, ERL_NIF_OPT_DELAY_HALT)) + return atom_ok; + return atom_error; + } + return enif_make_badarg(env); +} + +static void on_halt(void *priv_data) +{ + PrivData *pdata = (PrivData *)priv_data; + int res; +#ifdef __WIN32__ + Sleep(pdata->on_halt_wait*1000); +#else + sleep(pdata->on_halt_wait); +#endif + if (pdata->mtx) { + enif_mutex_lock(pdata->mtx); + assert(!pdata->halting); + pdata->halting = !0; + enif_cond_broadcast(pdata->cnd); + enif_mutex_unlock(pdata->mtx); + } + res = fn_write_ok(pdata->filename); + assert(res == 0); +} static ErlNifFunc nif_funcs[] = { @@ -484,6 +755,14 @@ static ErlNifFunc nif_funcs[] = {"whereis_send", 3, whereis_send, ERL_NIF_DIRTY_JOB_IO_BOUND}, {"whereis_term", 2, whereis_term, ERL_NIF_DIRTY_JOB_CPU_BOUND}, {"dirty_terminating_literal_access", 2, dirty_terminating_literal_access, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"delay_halt_normal", 3, delay_halt, 0}, + {"delay_halt_io_bound", 3, delay_halt, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"delay_halt_cpu_bound", 3, delay_halt, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"sync_halt_io_bound", 2, sync_halt, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"sync_halt_cpu_bound", 2, sync_halt, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"set_halt_option_from_nif_normal", 1, set_halt_option_from_nif, 0}, + {"set_halt_option_from_nif_io_bound", 1, set_halt_option_from_nif, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"set_halt_option_from_nif_cpu_bound", 1, set_halt_option_from_nif, ERL_NIF_DIRTY_JOB_CPU_BOUND} }; -ERL_NIF_INIT(dirty_nif_SUITE,nif_funcs,load,NULL,NULL,NULL) +ERL_NIF_INIT(dirty_nif_SUITE,nif_funcs,load,NULL,upgrade,unload) diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_a.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_a.c new file mode 100644 index 0000000000..73d50a2bf1 --- /dev/null +++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_a.c @@ -0,0 +1,23 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2022. 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% + */ + +#include "on_halt_nif.c" + +ERL_NIF_INIT(on_halt_a,nif_funcs,load,NULL,NULL,unload) diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_b.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_b.c new file mode 100644 index 0000000000..b9e13a17fa --- /dev/null +++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_b.c @@ -0,0 +1,23 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2022. 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% + */ + +#include "on_halt_nif.c" + +ERL_NIF_INIT(on_halt_b,nif_funcs,load,NULL,NULL,unload) diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_c.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_c.c new file mode 100644 index 0000000000..db875e7f18 --- /dev/null +++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_c.c @@ -0,0 +1,23 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2022. 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% + */ + +#include "on_halt_nif.c" + +ERL_NIF_INIT(on_halt_c,nif_funcs,load,NULL,NULL,unload) diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_d.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_d.c new file mode 100644 index 0000000000..e3b64245ce --- /dev/null +++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_d.c @@ -0,0 +1,23 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2022. 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% + */ + +#include "on_halt_nif.c" + +ERL_NIF_INIT(on_halt_d,nif_funcs,load,NULL,NULL,unload) diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_e.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_e.c new file mode 100644 index 0000000000..73357c9b9d --- /dev/null +++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_e.c @@ -0,0 +1,23 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2022. 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% + */ + +#include "on_halt_nif.c" + +ERL_NIF_INIT(on_halt_e,nif_funcs,load,NULL,NULL,unload) diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_f.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_f.c new file mode 100644 index 0000000000..58b4955d4f --- /dev/null +++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_f.c @@ -0,0 +1,23 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2022. 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% + */ + +#include "on_halt_nif.c" + +ERL_NIF_INIT(on_halt_f,nif_funcs,load,NULL,NULL,unload) diff --git a/erts/emulator/test/dirty_nif_SUITE_data/on_halt_nif.c b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_nif.c new file mode 100644 index 0000000000..610b80d3f7 --- /dev/null +++ b/erts/emulator/test/dirty_nif_SUITE_data/on_halt_nif.c @@ -0,0 +1,96 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2022. 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% + */ +#include "erl_nif.h" +#include <errno.h> +#include <assert.h> +#ifdef __WIN32__ +#include <windows.h> +#else +#include <unistd.h> +#endif +#include <stdio.h> +#include <string.h> + + +static int fn_write_ok(char *filename) +{ + FILE *file = fopen(filename, "w"); + if (!file) + return EINVAL; + if (1 != fwrite("ok", 2, 1, file)) + return EINVAL; + fclose(file); + return 0; +} + +static void on_halt(void *priv_data) +{ + int res; +#ifdef __WIN32__ + Sleep(1000); +#else + sleep(1); +#endif + assert(priv_data); + res = fn_write_ok((char *) priv_data); + assert(res == 0); +} + +static void unload(ErlNifEnv *env, void *priv_data) +{ + if (priv_data) + enif_free(priv_data); +} + +static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) +{ + unsigned filename_len; + char *filename; + if (0 != enif_set_option(env, ERL_NIF_OPT_ON_HALT, on_halt)) + return __LINE__; + if (!enif_get_list_length(env, load_info, &filename_len)) + return __LINE__; + if (filename_len == 0) + return __LINE__; + filename_len++; + filename = enif_alloc(filename_len); + if (!filename) + return __LINE__; + if (filename_len != enif_get_string(env, + load_info, + filename, + filename_len, + ERL_NIF_LATIN1)) { + enif_free(filename); + return __LINE__; + } + *priv_data = (void *) filename; + return 0; +} + +static ERL_NIF_TERM lib_loaded(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + return enif_make_atom(env, "true"); +} + +static ErlNifFunc nif_funcs[] = +{ + {"lib_loaded", 0, lib_loaded} +}; diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 470f861e08..84125f139a 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -1204,8 +1204,13 @@ halt() -> %% halt/1 %% Shadowed by erl_bif_types: erlang:halt/1 --spec halt(Status) -> no_return() when - Status :: non_neg_integer() | 'abort' | string(). +-spec halt(Status :: non_neg_integer()) -> + no_return(); + (Abort :: abort) -> + no_return(); + (CrashDumpSlogan :: string()) -> + no_return(). + -dialyzer({no_return, halt/1}). halt(Status) -> try @@ -1216,11 +1221,18 @@ halt(Status) -> %% halt/2 %% Shadowed by erl_bif_types: erlang:halt/2 --spec halt(Status, Options) -> no_return() when - Status :: non_neg_integer() | 'abort' | string(), - Options :: [Option], - Option :: {flush, boolean()}. -halt(_Status, _Options) -> +-type halt_options() :: + [{flush, boolean()}]. + +-spec halt(Status :: non_neg_integer(), Options :: halt_options()) -> + no_return(); + (Abort :: abort, Options :: halt_options()) -> + no_return(); + (CrashDumpSlogan :: string(), Options :: halt_options()) -> + no_return(). + +-dialyzer({no_return, halt/2}). +halt(_, _) -> erlang:nif_error(undefined). %% has_prepared_code_on_load/1 -- 2.35.3
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor