From 3b513ac820ab529a565d8caf3972982f204e8b88 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?=
Date: Mon, 2 May 2016 14:53:33 +0200
Subject: code_SUITE: Make on_load_binary/1 clearer by using merl
---
lib/kernel/test/code_SUITE.erl | 25 ++++++++++---------------
1 file changed, 10 insertions(+), 15 deletions(-)
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index 383eab94fe..a0b72925f7 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -1184,22 +1184,17 @@ on_load_binary(_) ->
register(Master, self()),
%% Construct, compile and pretty-print.
- Mod = on_load_binary,
+ Mod = ?FUNCTION_NAME,
File = atom_to_list(Mod) ++ ".erl",
- Forms = [{attribute,1,file,{File,1}},
- {attribute,1,module,Mod},
- {attribute,2,export,[{ok,0}]},
- {attribute,3,on_load,{init,0}},
- {function,5,init,0,
- [{clause,5,[],[],
- [{op,6,'!',
- {atom,6,Master},
- {tuple,6,[{atom,6,Mod},{call,6,{atom,6,self},[]}]}},
- {'receive',7,[{clause,8,[{atom,8,go}],[],[{atom,8,ok}]}]}]}]},
- {function,11,ok,0,[{clause,11,[],[],[{atom,11,true}]}]}],
- Forms1 = erl_parse:new_anno(Forms),
- {ok,Mod,Bin} = compile:forms(Forms1, [report]),
- [io:put_chars(erl_pp:form(F)) || F <- Forms1],
+ Tree = ?Q(["-module('@Mod@').\n",
+ "-export([ok/0]).\n",
+ "-on_load({init,0}).\n",
+ "init() ->\n",
+ " '@Master@' ! {on_load_binary,self()},\n",
+ " receive go -> ok end.\n",
+ "ok() -> true.\n"]),
+ {ok,Mod,Bin} = merl:compile(Tree),
+ merl:print(Tree),
{Pid1,Ref1} = spawn_monitor(fun() ->
code:load_binary(Mod, File, Bin),
--
cgit v1.2.3
From 4050fc5665a4819b15f204543c28c59726b4aac9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?=
Date: Tue, 12 Apr 2016 07:07:17 +0200
Subject: Refactor erts_finish_loading() and insert_new_code()
As a preparation for fixing some issues with -on_load(), we will
integrate insert_new_code() into erts_finish_loading. Also rename
insert_new_code() to stub_insert_new_code() to make it clear that
it will only be used when making a stub module
---
erts/emulator/beam/beam_load.c | 53 +++++++++++++++++++++++++++---------------
1 file changed, 34 insertions(+), 19 deletions(-)
diff --git a/erts/emulator/beam/beam_load.c b/erts/emulator/beam/beam_load.c
index 2e21a553ed..9bd9b4bfc0 100644
--- a/erts/emulator/beam/beam_load.c
+++ b/erts/emulator/beam/beam_load.c
@@ -479,9 +479,9 @@ static void free_loader_state(Binary* magic);
static ErlHeapFragment* new_literal_fragment(Uint size);
static void free_literal_fragment(ErlHeapFragment*);
static void loader_state_dtor(Binary* magic);
-static Eterm insert_new_code(Process *c_p, ErtsProcLocks c_p_locks,
- Eterm group_leader, Eterm module,
- BeamCodeHeader* code, Uint size);
+static Eterm stub_insert_new_code(Process *c_p, ErtsProcLocks c_p_locks,
+ Eterm group_leader, Eterm module,
+ BeamCodeHeader* code, Uint size);
static int init_iff_file(LoaderState* stp, byte* code, Uint size);
static int scan_iff_file(LoaderState* stp, Uint* chunk_types,
Uint num_types, Uint num_mandatory);
@@ -513,7 +513,7 @@ static GenOp* gen_get_map_element(LoaderState* stp, GenOpArg Fail, GenOpArg Src,
static int freeze_code(LoaderState* stp);
-static void final_touch(LoaderState* stp);
+static void final_touch(LoaderState* stp, struct erl_module_instance* inst_p);
static void short_file(int line, LoaderState* stp, unsigned needed);
static void load_printf(int line, LoaderState* context, char *fmt, ...);
static int transform_engine(LoaderState* st);
@@ -768,6 +768,9 @@ erts_finish_loading(Binary* magic, Process* c_p,
{
Eterm retval;
LoaderState* stp = ERTS_MAGIC_BIN_DATA(magic);
+ Module* mod_tab_p;
+ struct erl_module_instance* inst_p;
+ Uint size;
/*
* No other process may run since we will update the export
@@ -784,11 +787,25 @@ erts_finish_loading(Binary* magic, Process* c_p,
*/
CHKBLK(ERTS_ALC_T_CODE,stp->code);
- retval = insert_new_code(c_p, c_p_locks, stp->group_leader, stp->module,
- stp->hdr, stp->loaded_size);
- if (retval != NIL) {
- goto load_error;
- }
+ retval = beam_make_current_old(c_p, c_p_locks, stp->module);
+ ASSERT(retval == NIL);
+
+ /*
+ * Update module table.
+ */
+
+ size = stp->loaded_size;
+ erts_total_code_size += size;
+ mod_tab_p = erts_put_module(stp->module);
+ inst_p = &mod_tab_p->curr;
+ inst_p->code_hdr = stp->hdr;
+ inst_p->code_length = size;
+
+ /*
+ * Update ranges (used for finding a function from a PC value).
+ */
+
+ erts_update_ranges((BeamInstr*)inst_p->code_hdr, size);
/*
* Ready for the final touch: fixing the export table entries for
@@ -796,7 +813,7 @@ erts_finish_loading(Binary* magic, Process* c_p,
*/
CHKBLK(ERTS_ALC_T_CODE,stp->code);
- final_touch(stp);
+ final_touch(stp, inst_p);
/*
* Loading succeded.
@@ -820,7 +837,6 @@ erts_finish_loading(Binary* magic, Process* c_p,
retval = am_on_load;
}
- load_error:
free_loader_state(magic);
return retval;
}
@@ -1026,9 +1042,9 @@ loader_state_dtor(Binary* magic)
}
static Eterm
-insert_new_code(Process *c_p, ErtsProcLocks c_p_locks,
- Eterm group_leader, Eterm module, BeamCodeHeader* code_hdr,
- Uint size)
+stub_insert_new_code(Process *c_p, ErtsProcLocks c_p_locks,
+ Eterm group_leader, Eterm module,
+ BeamCodeHeader* code_hdr, Uint size)
{
Module* modp;
Eterm retval;
@@ -4700,14 +4716,13 @@ freeze_code(LoaderState* stp)
}
static void
-final_touch(LoaderState* stp)
+final_touch(LoaderState* stp, struct erl_module_instance* inst_p)
{
int i;
int on_load = stp->on_load;
unsigned catches;
Uint index;
BeamInstr* codev = stp->codev;
- Module* modp;
/*
* Allocate catch indices and fix up all catch_yf instructions.
@@ -4722,8 +4737,7 @@ final_touch(LoaderState* stp)
codev[index+2] = make_catch(catches);
index = next;
}
- modp = erts_put_module(stp->module);
- modp->curr.catches = catches;
+ inst_p->catches = catches;
/*
* Export functions.
@@ -6421,7 +6435,8 @@ erts_make_stub_module(Process* p, Eterm Mod, Eterm Beam, Eterm Info)
* Insert the module in the module table.
*/
- rval = insert_new_code(p, 0, p->group_leader, Mod, code_hdr, code_size);
+ rval = stub_insert_new_code(p, 0, p->group_leader, Mod,
+ code_hdr, code_size);
if (rval != NIL) {
goto error;
}
--
cgit v1.2.3
From f77ea1c049c585db93618991eb02d9afbfa7ad21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?=
Date: Tue, 12 Apr 2016 07:14:57 +0200
Subject: Reimplement -on_load()
Load the module as old code; swap old and new code if the
-on_load function succeeds. That way, a failed update attempt
for a module that has an -on_load function will preserve the
previous version of the code.
---
erts/emulator/beam/beam_bif_load.c | 63 +++++++++++++++-----------
erts/emulator/beam/beam_load.c | 58 ++++++++++++++++++++----
erts/emulator/beam/erl_nif.c | 62 +++++++++++++++-----------
lib/kernel/src/code_server.erl | 1 +
lib/kernel/test/code_SUITE.erl | 91 +++++++++++++++++++++++++++++++++++++-
5 files changed, 212 insertions(+), 63 deletions(-)
diff --git a/erts/emulator/beam/beam_bif_load.c b/erts/emulator/beam/beam_bif_load.c
index 87508dcf5f..b6f5d66166 100644
--- a/erts/emulator/beam/beam_bif_load.c
+++ b/erts/emulator/beam/beam_bif_load.c
@@ -625,8 +625,8 @@ BIF_RETTYPE call_on_load_function_1(BIF_ALIST_1)
{
Module* modp = erts_get_module(BIF_ARG_1, erts_active_code_ix());
- if (modp && modp->curr.code_hdr) {
- BIF_TRAP_CODE_PTR_0(BIF_P, modp->curr.code_hdr->on_load_function_ptr);
+ if (modp && modp->old.code_hdr) {
+ BIF_TRAP_CODE_PTR_0(BIF_P, modp->old.code_hdr->on_load_function_ptr);
}
else {
BIF_ERROR(BIF_P, BADARG);
@@ -651,14 +651,14 @@ BIF_RETTYPE finish_after_on_load_2(BIF_ALIST_2)
code_ix = erts_active_code_ix();
modp = erts_get_module(BIF_ARG_1, code_ix);
- if (!modp || !modp->curr.code_hdr) {
+ if (!modp || !modp->old.code_hdr) {
error:
erts_smp_thr_progress_unblock();
erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
erts_release_code_write_permission();
BIF_ERROR(BIF_P, BADARG);
}
- if (modp->curr.code_hdr->on_load_function_ptr == NULL) {
+ if (modp->old.code_hdr->on_load_function_ptr == NULL) {
goto error;
}
if (BIF_ARG_2 != am_false && BIF_ARG_2 != am_true) {
@@ -667,44 +667,55 @@ BIF_RETTYPE finish_after_on_load_2(BIF_ALIST_2)
if (BIF_ARG_2 == am_true) {
int i;
+ struct erl_module_instance t;
+
+ /*
+ * Swap old and new code.
+ */
+ t = modp->curr;
+ modp->curr = modp->old;
+ modp->old = t;
/*
* The on_load function succeded. Fix up export entries.
*/
for (i = 0; i < export_list_size(code_ix); i++) {
Export *ep = export_list(i,code_ix);
- if (ep != NULL &&
- ep->code[0] == BIF_ARG_1 &&
- ep->code[4] != 0) {
+ if (ep == NULL || ep->code[0] != BIF_ARG_1) {
+ continue;
+ }
+ if (ep->code[4] != 0) {
ep->addressv[code_ix] = (void *) ep->code[4];
ep->code[4] = 0;
+ } else {
+ if (ep->addressv[code_ix] == ep->code+3 &&
+ ep->code[3] == (BeamInstr) em_apply_bif) {
+ continue;
+ }
+ ep->addressv[code_ix] = ep->code+3;
+ ep->code[3] = (BeamInstr) em_call_error_handler;
}
}
modp->curr.code_hdr->on_load_function_ptr = NULL;
set_default_trace_pattern(BIF_ARG_1);
} else if (BIF_ARG_2 == am_false) {
- BeamInstr* code;
- BeamInstr* end;
+ int i;
/*
- * The on_load function failed. Remove the loaded code.
- * This is an combination of delete and purge. We purge
- * the current code; the old code is not touched.
+ * The on_load function failed. Remove references to the
+ * code that is about to be purged from the export entries.
*/
- erts_total_code_size -= modp->curr.code_length;
- code = (BeamInstr*) modp->curr.code_hdr;
- end = (BeamInstr *) ((char *)code + modp->curr.code_length);
- erts_cleanup_funs_on_purge(code, end);
- beam_catches_delmod(modp->curr.catches, code, modp->curr.code_length,
- erts_active_code_ix());
- if (modp->curr.code_hdr->literals_start) {
- erts_free(ERTS_ALC_T_LITERAL, modp->curr.code_hdr->literals_start);
- }
- erts_free(ERTS_ALC_T_CODE, modp->curr.code_hdr);
- modp->curr.code_hdr = NULL;
- modp->curr.code_length = 0;
- modp->curr.catches = BEAM_CATCHES_NIL;
- erts_remove_from_ranges(code);
+
+ for (i = 0; i < export_list_size(code_ix); i++) {
+ Export *ep = export_list(i,code_ix);
+ if (ep == NULL || ep->code[0] != BIF_ARG_1) {
+ continue;
+ }
+ if (ep->code[3] == (BeamInstr) em_apply_bif) {
+ continue;
+ }
+ ep->code[4] = 0;
+ }
}
erts_smp_thr_progress_unblock();
erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
diff --git a/erts/emulator/beam/beam_load.c b/erts/emulator/beam/beam_load.c
index 9bd9b4bfc0..95489d9a63 100644
--- a/erts/emulator/beam/beam_load.c
+++ b/erts/emulator/beam/beam_load.c
@@ -32,6 +32,7 @@
#include "bif.h"
#include "external.h"
#include "beam_load.h"
+#include "beam_bp.h"
#include "big.h"
#include "erl_bits.h"
#include "beam_catches.h"
@@ -766,7 +767,7 @@ Eterm
erts_finish_loading(Binary* magic, Process* c_p,
ErtsProcLocks c_p_locks, Eterm* modp)
{
- Eterm retval;
+ Eterm retval = NIL;
LoaderState* stp = ERTS_MAGIC_BIN_DATA(magic);
Module* mod_tab_p;
struct erl_module_instance* inst_p;
@@ -779,16 +780,52 @@ erts_finish_loading(Binary* magic, Process* c_p,
ERTS_SMP_LC_ASSERT(erts_initialized == 0 || erts_has_code_write_permission() ||
erts_smp_thr_progress_is_blocking());
-
/*
* Make current code for the module old and insert the new code
* as current. This will fail if there already exists old code
* for the module.
*/
+ mod_tab_p = erts_put_module(stp->module);
CHKBLK(ERTS_ALC_T_CODE,stp->code);
- retval = beam_make_current_old(c_p, c_p_locks, stp->module);
- ASSERT(retval == NIL);
+ if (!stp->on_load) {
+ /*
+ * Normal case -- no -on_load() function.
+ */
+ retval = beam_make_current_old(c_p, c_p_locks, stp->module);
+ ASSERT(retval == NIL);
+ } else {
+ ErtsCodeIndex code_ix = erts_staging_code_ix();
+ Eterm module = stp->module;
+ int i;
+
+ /*
+ * There is an -on_load() function. We will keep the current
+ * code, but we must turn off any tracing.
+ */
+
+ for (i = 0; i < export_list_size(code_ix); i++) {
+ Export *ep = export_list(i, code_ix);
+ if (ep == NULL || ep->code[0] != module) {
+ continue;
+ }
+ if (ep->addressv[code_ix] == ep->code+3) {
+ if (ep->code[3] == (BeamInstr) em_apply_bif) {
+ continue;
+ } else if (ep->code[3] ==
+ (BeamInstr) BeamOp(op_i_generic_breakpoint)) {
+ ERTS_SMP_LC_ASSERT(erts_smp_thr_progress_is_blocking());
+ ASSERT(mod_tab_p->curr.num_traced_exports > 0);
+ erts_clear_export_break(mod_tab_p, ep->code+3);
+ ep->addressv[code_ix] = (BeamInstr *) ep->code[4];
+ ep->code[4] = 0;
+ }
+ ASSERT(ep->code[4] == 0);
+ }
+ }
+ ASSERT(mod_tab_p->curr.num_breakpoints == 0);
+ ASSERT(mod_tab_p->curr.num_traced_exports == 0);
+ }
/*
* Update module table.
@@ -796,8 +833,11 @@ erts_finish_loading(Binary* magic, Process* c_p,
size = stp->loaded_size;
erts_total_code_size += size;
- mod_tab_p = erts_put_module(stp->module);
- inst_p = &mod_tab_p->curr;
+ if (stp->on_load) {
+ inst_p = &mod_tab_p->old;
+ } else {
+ inst_p = &mod_tab_p->curr;
+ }
inst_p->code_hdr = stp->hdr;
inst_p->code_length = size;
@@ -4757,10 +4797,10 @@ final_touch(LoaderState* stp, struct erl_module_instance* inst_p)
ep->addressv[erts_staging_code_ix()] = address;
} else {
/*
- * Don't make any of the exported functions
- * callable yet.
+ * on_load: Don't make any of the exported functions
+ * callable yet. Keep any function in the current
+ * code callable.
*/
- ep->addressv[erts_staging_code_ix()] = ep->code+3;
ep->code[4] = (BeamInstr) address;
}
}
diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c
index 941f44b9ec..dc83a780a2 100644
--- a/erts/emulator/beam/erl_nif.c
+++ b/erts/emulator/beam/erl_nif.c
@@ -2780,7 +2780,7 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
ErlNifEntry* entry = NULL;
ErlNifEnv env;
int i, err, encoding;
- Module* mod;
+ Module* module_p;
Eterm mod_atom;
const Atom* mod_atomp;
Eterm f_atom;
@@ -2790,6 +2790,8 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
int veto;
struct erl_module_nif* lib = NULL;
int reload_warning = 0;
+ struct erl_module_instance* this_mi;
+ struct erl_module_instance* prev_mi;
encoding = erts_get_native_filename_encoding();
if (encoding == ERL_FILENAME_WIN_WCHAR) {
@@ -2823,21 +2825,29 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
ASSERT(caller != NULL);
mod_atom = caller[0];
ASSERT(is_atom(mod_atom));
- mod=erts_get_module(mod_atom, erts_active_code_ix());
- ASSERT(mod != NULL);
+ module_p = erts_get_module(mod_atom, erts_active_code_ix());
+ ASSERT(module_p != NULL);
mod_atomp = atom_tab(atom_val(mod_atom));
init_func = erts_static_nif_get_nif_init((char*)mod_atomp->name, mod_atomp->len);
if (init_func != NULL)
handle = init_func;
- if (!in_area(caller, mod->curr.code_hdr, mod->curr.code_length)) {
- ASSERT(in_area(caller, mod->old.code_hdr, mod->old.code_length));
+ if (in_area(caller, module_p->old.code_hdr, module_p->old.code_length)) {
+ if (module_p->old.code_hdr->on_load_function_ptr) {
+ this_mi = &module_p->old;
+ prev_mi = &module_p->curr;
+ } else {
+ ret = load_nif_error(BIF_P, "old_code", "Calling load_nif from old "
+ "module '%T' not allowed", mod_atom);
+ goto error;
+ }
+ } else {
+ this_mi = &module_p->curr;
+ prev_mi = &module_p->old;
+ }
- ret = load_nif_error(BIF_P, "old_code", "Calling load_nif from old "
- "module '%T' not allowed", mod_atom);
- }
- else if (init_func == NULL &&
+ if (init_func == NULL &&
(err=erts_sys_ddll_open(lib_name, &handle, &errdesc)) != ERL_DE_NO_ERROR) {
const char slogan[] = "Failed to load NIF library";
if (strstr(errdesc.str, lib_name) != NULL) {
@@ -2886,7 +2896,7 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
for (i=0; i < entry->num_of_funcs && ret==am_ok; i++) {
BeamInstr** code_pp;
if (!erts_atom_get(f->name, sys_strlen(f->name), &f_atom, ERTS_ATOM_ENC_LATIN1)
- || (code_pp = get_func_pp(mod->curr.code_hdr, f_atom, f->arity))==NULL) {
+ || (code_pp = get_func_pp(this_mi->code_hdr, f_atom, f->arity))==NULL) {
ret = load_nif_error(BIF_P,bad_lib,"Function not found %T:%s/%u",
mod_atom, f->name, f->arity);
}
@@ -2937,9 +2947,9 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
erts_refc_init(&lib->rt_cnt, 0);
erts_refc_init(&lib->rt_dtor_cnt, 0);
ASSERT(opened_rt_list == NULL);
- lib->mod = mod;
+ lib->mod = module_p;
env.mod_nif = lib;
- if (mod->curr.nif != NULL) { /*************** Reload ******************/
+ if (this_mi->nif != NULL) { /*************** Reload ******************/
/*
* Repeated load_nif calls from same Erlang module instance ("reload")
* is deprecated and was only ment as a development feature not to
@@ -2947,16 +2957,16 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
*/
int k, old_incr = 0;
ErlNifFunc* old_func;
- lib->priv_data = mod->curr.nif->priv_data;
+ lib->priv_data = this_mi->nif->priv_data;
- ASSERT(mod->curr.nif->entry != NULL);
+ ASSERT(this_mi->nif->entry != NULL);
if (entry->reload == NULL) {
ret = load_nif_error(BIF_P,reload,"Reload not supported by this NIF library.");
goto error;
}
/* Check that no NIF is removed */
- old_func = mod->curr.nif->entry->funcs;
- for (k=0; k < mod->curr.nif->entry->num_of_funcs; k++) {
+ old_func = this_mi->nif->entry->funcs;
+ for (k=0; k < this_mi->nif->entry->num_of_funcs; k++) {
int incr = 0;
ErlNifFunc* f = entry->funcs;
for (i=0; i < entry->num_of_funcs; i++) {
@@ -2972,7 +2982,7 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
old_func->name, old_func->arity);
goto error;
}
- old_func = next_func(mod->curr.nif->entry, &old_incr, old_func);
+ old_func = next_func(this_mi->nif->entry, &old_incr, old_func);
}
erts_pre_nif(&env, BIF_P, lib, NULL);
veto = entry->reload(&env, &lib->priv_data, BIF_ARG_2);
@@ -2982,24 +2992,24 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
}
else {
commit_opened_resource_types(lib);
- mod->curr.nif->entry = NULL; /* to prevent 'unload' callback */
- erts_unload_nif(mod->curr.nif);
+ this_mi->nif->entry = NULL; /* to prevent 'unload' callback */
+ erts_unload_nif(this_mi->nif);
reload_warning = 1;
}
}
else {
lib->priv_data = NULL;
- if (mod->old.nif != NULL) { /**************** Upgrade ***************/
- void* prev_old_data = mod->old.nif->priv_data;
+ if (prev_mi->nif != NULL) { /**************** Upgrade ***************/
+ void* prev_old_data = prev_mi->nif->priv_data;
if (entry->upgrade == NULL) {
ret = load_nif_error(BIF_P, upgrade, "Upgrade not supported by this NIF library.");
goto error;
}
- erts_pre_nif(&env, BIF_P, lib, NULL);
- veto = entry->upgrade(&env, &lib->priv_data, &mod->old.nif->priv_data, BIF_ARG_2);
+ erts_pre_nif(&env, BIF_P, lib, NULL);
+ veto = entry->upgrade(&env, &lib->priv_data, &prev_mi->nif->priv_data, BIF_ARG_2);
erts_post_nif(&env);
if (veto) {
- mod->old.nif->priv_data = prev_old_data;
+ prev_mi->nif->priv_data = prev_old_data;
ret = load_nif_error(BIF_P, upgrade, "Library upgrade-call unsuccessful.");
}
else
@@ -3024,12 +3034,12 @@ BIF_RETTYPE load_nif_2(BIF_ALIST_2)
int incr = 0;
ErlNifFunc* f = entry->funcs;
- mod->curr.nif = lib;
+ this_mi->nif = lib;
for (i=0; i < entry->num_of_funcs; i++)
{
BeamInstr* code_ptr;
erts_atom_get(f->name, sys_strlen(f->name), &f_atom, ERTS_ATOM_ENC_LATIN1);
- code_ptr = *get_func_pp(mod->curr.code_hdr, f_atom, f->arity);
+ code_ptr = *get_func_pp(this_mi->code_hdr, f_atom, f->arity);
if (code_ptr[1] == 0) {
code_ptr[5+0] = (BeamInstr) BeamOp(op_call_nif);
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl
index 90b2a06c46..56372f90ca 100644
--- a/lib/kernel/src/code_server.erl
+++ b/lib/kernel/src/code_server.erl
@@ -1351,6 +1351,7 @@ finish_on_load_1(Mod, File, OnLoadRes, WaitingPids, Db) ->
Res = case Keep of
false ->
_ = finish_on_load_report(Mod, OnLoadRes),
+ _ = erts_code_purger:purge(Mod),
{error,on_load_failure};
true ->
ets:insert(Db, {Mod,File}),
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index a0b72925f7..547881f3b2 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -20,6 +20,7 @@
-module(code_SUITE).
-include_lib("common_test/include/ct.hrl").
+-include_lib("syntax_tools/include/merl.hrl").
-export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2]).
-export([set_path/1, get_path/1, add_path/1, add_paths/1, del_path/1,
@@ -33,7 +34,9 @@
where_is_file/1,
purge_stacktrace/1, mult_lib_roots/1, bad_erl_libs/1,
code_archive/1, code_archive2/1, on_load/1, on_load_binary/1,
- on_load_embedded/1, on_load_errors/1, big_boot_embedded/1,
+ on_load_embedded/1, on_load_errors/1, on_load_update/1,
+ on_load_purge/1,
+ big_boot_embedded/1,
native_early_modules/1, get_mode/1,
normalized_paths/1]).
@@ -61,7 +64,8 @@ all() ->
ext_mod_dep, clash, where_is_file,
purge_stacktrace, mult_lib_roots,
bad_erl_libs, code_archive, code_archive2, on_load,
- on_load_binary, on_load_embedded, on_load_errors,
+ on_load_binary, on_load_embedded, on_load_errors, on_load_update,
+ on_load_purge,
big_boot_embedded, native_early_modules, get_mode, normalized_paths].
groups() ->
@@ -1436,6 +1440,89 @@ do_on_load_error(ReturnValue) ->
{undef,[{on_load_error,main,[],_}|_]} = Exit
end.
+on_load_update(_Config) ->
+ {Mod,Code1} = on_load_update_code(1),
+ {module,Mod} = code:load_binary(Mod, "", Code1),
+ 42 = Mod:a(),
+ 100 = Mod:b(99),
+ 4 = erlang:trace_pattern({Mod,'_','_'}, true),
+
+ {Mod,Code2} = on_load_update_code(2),
+ {error,on_load_failure} = code:load_binary(Mod, "", Code2),
+ 42 = Mod:a(),
+ 100 = Mod:b(99),
+ {'EXIT',{undef,_}} = (catch Mod:never()),
+ 4 = erlang:trace_pattern({Mod,'_','_'}, false),
+
+ {Mod,Code3} = on_load_update_code(3),
+ {module,Mod} = code:load_binary(Mod, "", Code3),
+ 100 = Mod:c(),
+ {'EXIT',{undef,_}} = (catch Mod:a()),
+ {'EXIT',{undef,_}} = (catch Mod:b(10)),
+ {'EXIT',{undef,_}} = (catch Mod:never()),
+
+ ok.
+
+on_load_update_code(Version) ->
+ Mod = ?FUNCTION_NAME,
+ Tree = on_load_update_code_1(Version, Mod),
+ io:format("Version ~p", [Version]),
+ {ok,Mod,Code} = merl:compile(Tree),
+ merl:print(Tree),
+ io:nl(),
+ {Mod,Code}.
+
+on_load_update_code_1(1, Mod) ->
+ ?Q(["-module('@Mod@').\n",
+ "-export([a/0,b/1]).\n"
+ "-on_load(f/0).\n",
+ "f() -> ok.\n",
+ "a() -> 42.\n"
+ "b(I) -> I+1.\n"]);
+on_load_update_code_1(2, Mod) ->
+ ?Q(["-module('@Mod@').\n",
+ "-export([never/0]).\n"
+ "-on_load(f/0).\n",
+ "f() -> 42 = '@Mod@':a(), 1 = '@Mod@':b(0), fail.\n",
+ "never() -> never.\n"]);
+on_load_update_code_1(3, Mod) ->
+ ?Q(["-module('@Mod@').\n",
+ "-export([c/0]).\n"
+ "-on_load(f/0).\n",
+ "f() -> ok.\n",
+ "c() -> 100.\n"]).
+
+on_load_purge(_Config) ->
+ Mod = ?FUNCTION_NAME,
+ register(Mod, self()),
+ Tree = ?Q(["-module('@Mod@').\n",
+ "-on_load(f/0).\n",
+ "loop() -> loop().\n",
+ "f() ->\n",
+ "'@Mod@' ! {self(),spawn(fun loop/0)},\n",
+ "receive Ack -> Ack end.\n"]),
+ merl:print(Tree),
+ {ok,Mod,Code} = merl:compile(Tree),
+ P = spawn(fun() ->
+ exit(code:load_binary(Mod, "", Code))
+ end),
+ monitor(process, P),
+ receive
+ {Pid1,Pid2} ->
+ monitor(process, Pid2),
+ Pid1 ! ack_and_failure,
+ receive
+ {'DOWN',_,process,P,Exit1} ->
+ {error,on_load_failure} = Exit1
+ end,
+ receive
+ {'DOWN',_,process,Pid2,Exit2} ->
+ io:format("~p\n", [Exit2])
+ after 10000 ->
+ ct:fail(no_down_message)
+ end
+ end.
+
%% Test that the native code of early loaded modules is loaded.
native_early_modules(Config) when is_list(Config) ->
case erlang:system_info(hipe_architecture) of
--
cgit v1.2.3
From eb7c5e3fa1f2d2bac21c4714b3a627a5517f797d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?=
Date: Tue, 3 May 2016 06:10:22 +0200
Subject: code_server: Eliminate unnecessary Tag in handle_call/3
For historical reasons, the second argument in handle_call/3 is
{Pid,Tag}. The tag is always 'call' and is never used.
---
lib/kernel/src/code_server.erl | 110 +++++++++++++++++++++--------------------
1 file changed, 56 insertions(+), 54 deletions(-)
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl
index 56372f90ca..356c6630fa 100644
--- a/lib/kernel/src/code_server.erl
+++ b/lib/kernel/src/code_server.erl
@@ -142,7 +142,7 @@ reply(Pid, Res) ->
loop(#state{supervisor=Supervisor}=State0) ->
receive
{code_call, Pid, Req} ->
- case handle_call(Req, {Pid, call}, State0) of
+ case handle_call(Req, Pid, State0) of
{reply, Res, State} ->
_ = reply(Pid, Res),
loop(State);
@@ -225,90 +225,90 @@ system_code_change(State, _Module, _OldVsn, _Extra) ->
%% The gen_server call back functions.
%%
-handle_call({stick_dir,Dir}, {_From,_Tag}, S) ->
+handle_call({stick_dir,Dir}, _From, S) ->
{reply,stick_dir(Dir, true, S),S};
-handle_call({unstick_dir,Dir}, {_From,_Tag}, S) ->
+handle_call({unstick_dir,Dir}, _From, S) ->
{reply,stick_dir(Dir, false, S),S};
-handle_call({stick_mod,Mod}, {_From,_Tag}, S) ->
+handle_call({stick_mod,Mod}, _From, S) ->
{reply,stick_mod(Mod, true, S),S};
-handle_call({unstick_mod,Mod}, {_From,_Tag}, S) ->
+handle_call({unstick_mod,Mod}, _From, S) ->
{reply,stick_mod(Mod, false, S),S};
-handle_call({dir,Dir}, {_From,_Tag}, S) ->
+handle_call({dir,Dir}, _From, S) ->
Root = S#state.root,
Resp = do_dir(Root,Dir,S#state.namedb),
{reply,Resp,S};
-handle_call({load_file,Mod}, Caller, St) when is_atom(Mod) ->
- load_file(Mod, Caller, St);
+handle_call({load_file,Mod}, From, St) when is_atom(Mod) ->
+ load_file(Mod, From, St);
-handle_call({add_path,Where,Dir0}, {_From,_Tag},
+handle_call({add_path,Where,Dir0}, _From,
#state{namedb=Namedb,path=Path0}=S) ->
{Resp,Path} = add_path(Where, Dir0, Path0, Namedb),
{reply,Resp,S#state{path=Path}};
-handle_call({add_paths,Where,Dirs0}, {_From,_Tag},
+handle_call({add_paths,Where,Dirs0}, _From,
#state{namedb=Namedb,path=Path0}=S) ->
{Resp,Path} = add_paths(Where, Dirs0, Path0, Namedb),
{reply,Resp,S#state{path=Path}};
-handle_call({set_path,PathList}, {_From,_Tag},
+handle_call({set_path,PathList}, _From,
#state{path=Path0,namedb=Namedb}=S) ->
{Resp,Path,NewDb} = set_path(PathList, Path0, Namedb),
{reply,Resp,S#state{path=Path,namedb=NewDb}};
-handle_call({del_path,Name}, {_From,_Tag},
+handle_call({del_path,Name}, _From,
#state{path=Path0,namedb=Namedb}=S) ->
{Resp,Path} = del_path(Name, Path0, Namedb),
{reply,Resp,S#state{path=Path}};
-handle_call({replace_path,Name,Dir}, {_From,_Tag},
+handle_call({replace_path,Name,Dir}, _From,
#state{path=Path0,namedb=Namedb}=S) ->
{Resp,Path} = replace_path(Name, Dir, Path0, Namedb),
{reply,Resp,S#state{path=Path}};
-handle_call(get_path, {_From,_Tag}, S) ->
+handle_call(get_path, _From, S) ->
{reply,S#state.path,S};
%% Messages to load, delete and purge modules/files.
-handle_call({load_abs,File,Mod}, Caller, S) when is_atom(Mod) ->
+handle_call({load_abs,File,Mod}, From, S) when is_atom(Mod) ->
case modp(File) of
false ->
{reply,{error,badarg},S};
true ->
- load_abs(File, Mod, Caller, S)
+ load_abs(File, Mod, From, S)
end;
-handle_call({load_binary,Mod,File,Bin}, Caller, S) when is_atom(Mod) ->
- do_load_binary(Mod, File, Bin, Caller, S);
+handle_call({load_binary,Mod,File,Bin}, From, S) when is_atom(Mod) ->
+ do_load_binary(Mod, File, Bin, From, S);
-handle_call({load_native_partial,Mod,Bin}, {_From,_Tag}, S) ->
+handle_call({load_native_partial,Mod,Bin}, _From, S) ->
Architecture = erlang:system_info(hipe_architecture),
Result = (catch hipe_unified_loader:load(Mod, Bin, Architecture)),
Status = hipe_result_to_status(Result, S),
{reply,Status,S};
-handle_call({load_native_sticky,Mod,Bin,WholeModule}, {_From,_Tag}, S) ->
+handle_call({load_native_sticky,Mod,Bin,WholeModule}, _From, S) ->
Architecture = erlang:system_info(hipe_architecture),
Result = (catch hipe_unified_loader:load_module(Mod, Bin, WholeModule,
Architecture)),
Status = hipe_result_to_status(Result, S),
{reply,Status,S};
-handle_call({ensure_loaded,Mod}, Caller, St) when is_atom(Mod) ->
+handle_call({ensure_loaded,Mod}, From, St) when is_atom(Mod) ->
case erlang:module_loaded(Mod) of
true ->
{reply,{module,Mod},St};
false when St#state.mode =:= interactive ->
- load_file(Mod, Caller, St);
+ load_file(Mod, From, St);
false ->
{reply,{error,embedded},St}
end;
-handle_call({delete,Mod}, {_From,_Tag}, St) when is_atom(Mod) ->
+handle_call({delete,Mod}, _From, St) when is_atom(Mod) ->
case catch erlang:delete_module(Mod) of
true ->
ets:delete(St#state.moddb, Mod),
@@ -317,48 +317,50 @@ handle_call({delete,Mod}, {_From,_Tag}, St) when is_atom(Mod) ->
{reply,false,St}
end;
-handle_call({purge,Mod}, {_From,_Tag}, St) when is_atom(Mod) ->
+handle_call({purge,Mod}, _From, St) when is_atom(Mod) ->
{reply,do_purge(Mod),St};
-handle_call({soft_purge,Mod}, {_From,_Tag}, St) when is_atom(Mod) ->
+handle_call({soft_purge,Mod}, _From, St) when is_atom(Mod) ->
{reply,do_soft_purge(Mod),St};
-handle_call({is_loaded,Mod}, {_From,_Tag}, St) when is_atom(Mod) ->
+handle_call({is_loaded,Mod}, _From, St) when is_atom(Mod) ->
{reply,is_loaded(Mod, St#state.moddb),St};
-handle_call(all_loaded, {_From,_Tag}, S) ->
+handle_call(all_loaded, _From, S) ->
Db = S#state.moddb,
{reply,all_loaded(Db),S};
-handle_call({get_object_code,Mod}, {_From,_Tag}, St) when is_atom(Mod) ->
+handle_call({get_object_code,Mod}, _From, St) when is_atom(Mod) ->
Path = St#state.path,
case mod_to_bin(Path, Mod) of
{_,Bin,FName} -> {reply,{Mod,Bin,FName},St};
Error -> {reply,Error,St}
end;
-handle_call({is_sticky, Mod}, {_From,_Tag}, S) ->
+handle_call({is_sticky, Mod}, _From, S) ->
Db = S#state.moddb,
{reply, is_sticky(Mod,Db), S};
-handle_call(stop,{_From,_Tag}, S) ->
+handle_call(stop,_From, S) ->
{stop,normal,stopped,S};
-handle_call({set_primary_archive, File, ArchiveBin, FileInfo, ParserFun}, {_From,_Tag}, S=#state{mode=Mode}) ->
- case erl_prim_loader:set_primary_archive(File, ArchiveBin, FileInfo, ParserFun) of
+handle_call({set_primary_archive, File, ArchiveBin, FileInfo, ParserFun},
+ _From, S=#state{mode=Mode}) ->
+ case erl_prim_loader:set_primary_archive(File, ArchiveBin, FileInfo,
+ ParserFun) of
{ok, Files} ->
{reply, {ok, Mode, Files}, S};
{error, _Reason} = Error ->
{reply, Error, S}
end;
-handle_call(get_mode, {_From,_Tag}, S=#state{mode=Mode}) ->
+handle_call(get_mode, _From, S=#state{mode=Mode}) ->
{reply, Mode, S};
-handle_call({finish_loading,Prepared,EnsureLoaded}, {_,_}, S) ->
+handle_call({finish_loading,Prepared,EnsureLoaded}, _From, S) ->
{reply,finish_loading(Prepared, EnsureLoaded, S),S};
-handle_call(Other,{_From,_Tag}, S) ->
+handle_call(Other,_From, S) ->
error_msg(" ** Codeserver*** ignoring ~w~n ",[Other]),
{noreply,S}.
@@ -1054,14 +1056,14 @@ add_paths(Where,[Dir|Tail],Path,NameDb) ->
add_paths(_,_,Path,_) ->
{ok,Path}.
-do_load_binary(Module, File, Binary, Caller, St) ->
+do_load_binary(Module, File, Binary, From, St) ->
case modp(File) andalso is_binary(Binary) of
true ->
case erlang:module_loaded(Module) of
true -> do_purge(Module);
false -> ok
end,
- try_load_module(File, Module, Binary, Caller, St);
+ try_load_module(File, Module, Binary, From, St);
false ->
{reply,{error,badarg},St}
end.
@@ -1070,51 +1072,51 @@ modp(Atom) when is_atom(Atom) -> true;
modp(List) when is_list(List) -> int_list(List);
modp(_) -> false.
-load_abs(File, Mod, Caller, St) ->
+load_abs(File, Mod, From, St) ->
Ext = objfile_extension(),
FileName0 = lists:concat([File, Ext]),
FileName = absname(FileName0),
case erl_prim_loader:get_file(FileName) of
{ok,Bin,_} ->
- try_load_module(FileName, Mod, Bin, Caller, St);
+ try_load_module(FileName, Mod, Bin, From, St);
error ->
{reply,{error,nofile},St}
end.
-try_load_module(File, Mod, Bin, {From,_}=Caller, St0) ->
+try_load_module(File, Mod, Bin, From, St0) ->
case pending_on_load(Mod, From, St0) of
no ->
- try_load_module_1(File, Mod, Bin, Caller, St0);
+ try_load_module_1(File, Mod, Bin, From, St0);
{yes,St} ->
{noreply,St}
end.
-try_load_module_1(File, Mod, Bin, Caller, #state{moddb=Db}=St) ->
+try_load_module_1(File, Mod, Bin, From, #state{moddb=Db}=St) ->
case is_sticky(Mod, Db) of
true -> %% Sticky file reject the load
error_msg("Can't load module '~w' that resides in sticky dir\n",[Mod]),
{reply,{error,sticky_directory},St};
false ->
Architecture = erlang:system_info(hipe_architecture),
- try_load_module_2(File, Mod, Bin, Caller, Architecture, St)
+ try_load_module_2(File, Mod, Bin, From, Architecture, St)
end.
-try_load_module_2(File, Mod, Bin, Caller, undefined, St) ->
- try_load_module_3(File, Mod, Bin, Caller, undefined, St);
-try_load_module_2(File, Mod, Bin, Caller, Architecture,
+try_load_module_2(File, Mod, Bin, From, undefined, St) ->
+ try_load_module_3(File, Mod, Bin, From, undefined, St);
+try_load_module_2(File, Mod, Bin, From, Architecture,
#state{moddb=Db}=St) ->
case catch hipe_unified_loader:load_native_code(Mod, Bin, Architecture) of
{module,Mod} = Module ->
ets:insert(Db, [{{native,Mod},true},{Mod,File}]),
{reply,Module,St};
no_native ->
- try_load_module_3(File, Mod, Bin, Caller, Architecture, St);
+ try_load_module_3(File, Mod, Bin, From, Architecture, St);
Error ->
error_msg("Native loading of ~ts failed: ~p\n", [File,Error]),
{reply,ok,St}
end.
-try_load_module_3(File, Mod, Bin, Caller, Architecture,
+try_load_module_3(File, Mod, Bin, From, Architecture,
#state{moddb=Db}=St) ->
case erlang:load_module(Mod, Bin) of
{module,Mod} = Module ->
@@ -1122,7 +1124,7 @@ try_load_module_3(File, Mod, Bin, Caller, Architecture,
post_beam_load([Mod], Architecture, St),
{reply,Module,St};
{error,on_load} ->
- handle_on_load(Mod, File, Caller, St);
+ handle_on_load(Mod, File, From, St);
{error,What} = Error ->
error_msg("Loading of ~ts failed: ~p\n", [File, What]),
{reply,Error,St}
@@ -1151,18 +1153,18 @@ int_list([H|T]) when is_integer(H) -> int_list(T);
int_list([_|_]) -> false;
int_list([]) -> true.
-load_file(Mod, {From,_}=Caller, St0) ->
+load_file(Mod, From, St0) ->
case pending_on_load(Mod, From, St0) of
- no -> load_file_1(Mod, Caller, St0);
+ no -> load_file_1(Mod, From, St0);
{yes,St} -> {noreply,St}
end.
-load_file_1(Mod, Caller, #state{path=Path}=St) ->
+load_file_1(Mod, From, #state{path=Path}=St) ->
case mod_to_bin(Path, Mod) of
error ->
{reply,{error,nofile},St};
{Mod,Binary,File} ->
- try_load_module_1(File, Mod, Binary, Caller, St)
+ try_load_module_1(File, Mod, Binary, From, St)
end.
mod_to_bin([Dir|Tail], Mod) ->
@@ -1305,7 +1307,7 @@ run([F|Fs], Data0) ->
%% The on_load functionality.
%% -------------------------------------------------------
-handle_on_load(Mod, File, {From,_}, #state{on_load=OnLoad0}=St0) ->
+handle_on_load(Mod, File, From, #state{on_load=OnLoad0}=St0) ->
Fun = fun() ->
Res = erlang:call_on_load_function(Mod),
exit(Res)
--
cgit v1.2.3
From f8e4ac533d388b39d5980092e5dd7a9d4ffee60b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?=
Date: Fri, 29 Apr 2016 15:50:19 +0200
Subject: Avoid deadlock when an on_load function makes an external call to the
module itself
---
lib/kernel/src/code_server.erl | 35 ++++++++++++++++++++++-------------
lib/kernel/test/code_SUITE.erl | 30 ++++++++++++++++++++++++++++--
2 files changed, 50 insertions(+), 15 deletions(-)
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl
index 356c6630fa..10264382eb 100644
--- a/lib/kernel/src/code_server.erl
+++ b/lib/kernel/src/code_server.erl
@@ -32,7 +32,8 @@
-import(lists, [foreach/2]).
--type on_load_item() :: {reference(),module(),file:name_all(),[pid()]}.
+-type on_load_item() :: {{pid(),reference()},module(),
+ file:name_all(),[pid()]}.
-record(state, {supervisor :: pid(),
root :: file:name_all(),
@@ -155,8 +156,8 @@ loop(#state{supervisor=Supervisor}=State0) ->
system_terminate(Reason, Supervisor, [], State0);
{system, From, Msg} ->
handle_system_msg(running,Msg, From, Supervisor, State0);
- {'DOWN',Ref,process,_,Res} ->
- State = finish_on_load(Ref, Res, State0),
+ {'DOWN',Ref,process,Pid,Res} ->
+ State = finish_on_load({Pid,Ref}, Res, State0),
loop(State);
_Msg ->
loop(State0)
@@ -1312,38 +1313,46 @@ handle_on_load(Mod, File, From, #state{on_load=OnLoad0}=St0) ->
Res = erlang:call_on_load_function(Mod),
exit(Res)
end,
- {_,Ref} = spawn_monitor(Fun),
- OnLoad = [{Ref,Mod,File,[From]}|OnLoad0],
+ PidRef = spawn_monitor(Fun),
+ OnLoad = [{PidRef,Mod,File,[From]}|OnLoad0],
St = St0#state{on_load=OnLoad},
{noreply,St}.
pending_on_load(_, _, #state{on_load=[]}) ->
no;
pending_on_load(Mod, From, #state{on_load=OnLoad0}=St) ->
- case lists:keymember(Mod, 2, OnLoad0) of
+ case lists:keyfind(Mod, 2, OnLoad0) of
false ->
no;
- true ->
+ {{From,_Ref},Mod,_File,_Pids} ->
+ %% The on_load function tried to make an external
+ %% call to its own module. That would be a deadlock.
+ %% Fail the call. (The call is probably from error_handler,
+ %% and it will ignore the actual error reason and cause
+ %% an undef execption.)
+ _ = reply(From, {error,deadlock}),
+ {yes,St};
+ {_,_,_,_} ->
OnLoad = pending_on_load_1(Mod, From, OnLoad0),
{yes,St#state{on_load=OnLoad}}
end.
-pending_on_load_1(Mod, From, [{Ref,Mod,File,Pids}|T]) ->
- [{Ref,Mod,File,[From|Pids]}|T];
+pending_on_load_1(Mod, From, [{PidRef,Mod,File,Pids}|T]) ->
+ [{PidRef,Mod,File,[From|Pids]}|T];
pending_on_load_1(Mod, From, [H|T]) ->
[H|pending_on_load_1(Mod, From, T)];
pending_on_load_1(_, _, []) -> [].
-finish_on_load(Ref, OnLoadRes, #state{on_load=OnLoad0,moddb=Db}=State) ->
- case lists:keyfind(Ref, 1, OnLoad0) of
+finish_on_load(PidRef, OnLoadRes, #state{on_load=OnLoad0,moddb=Db}=State) ->
+ case lists:keyfind(PidRef, 1, OnLoad0) of
false ->
%% Since this process in general silently ignores messages
%% it doesn't understand, it should also ignore a 'DOWN'
%% message with an unknown reference.
State;
- {Ref,Mod,File,WaitingPids} ->
+ {PidRef,Mod,File,WaitingPids} ->
finish_on_load_1(Mod, File, OnLoadRes, WaitingPids, Db),
- OnLoad = [E || {R,_,_,_}=E <- OnLoad0, R =/= Ref],
+ OnLoad = [E || {R,_,_,_}=E <- OnLoad0, R =/= PidRef],
State#state{on_load=OnLoad}
end.
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index 547881f3b2..e238eea2de 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -35,7 +35,7 @@
purge_stacktrace/1, mult_lib_roots/1, bad_erl_libs/1,
code_archive/1, code_archive2/1, on_load/1, on_load_binary/1,
on_load_embedded/1, on_load_errors/1, on_load_update/1,
- on_load_purge/1,
+ on_load_purge/1, on_load_self_call/1,
big_boot_embedded/1,
native_early_modules/1, get_mode/1,
normalized_paths/1]).
@@ -65,7 +65,7 @@ all() ->
purge_stacktrace, mult_lib_roots,
bad_erl_libs, code_archive, code_archive2, on_load,
on_load_binary, on_load_embedded, on_load_errors, on_load_update,
- on_load_purge,
+ on_load_purge, on_load_self_call,
big_boot_embedded, native_early_modules, get_mode, normalized_paths].
groups() ->
@@ -1523,6 +1523,32 @@ on_load_purge(_Config) ->
end
end.
+on_load_self_call(_Config) ->
+ Mod = ?FUNCTION_NAME,
+ register(Mod, self()),
+ Tree = ?Q(["-module('@Mod@').\n",
+ "-export([ext/0]).\n",
+ "-on_load(f/0).\n",
+ "f() ->\n",
+ " '@Mod@' ! (catch '@Mod@':ext()),\n",
+ " ok.\n",
+ "ext() -> good_work.\n"]),
+ merl:print(Tree),
+ {ok,Mod,Code} = merl:compile(Tree),
+
+ {'EXIT',{undef,_}} = on_load_do_load(Mod, Code),
+ good_work = on_load_do_load(Mod, Code),
+
+ ok.
+
+on_load_do_load(Mod, Code) ->
+ spawn(fun() ->
+ {module,Mod} = code:load_binary(Mod, "", Code)
+ end),
+ receive
+ Any -> Any
+ end.
+
%% Test that the native code of early loaded modules is loaded.
native_early_modules(Config) when is_list(Config) ->
case erlang:system_info(hipe_architecture) of
--
cgit v1.2.3
From 7a1ff887383674e786a7c22aceefb0365ed91818 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?=
Date: Tue, 3 May 2016 06:51:25 +0200
Subject: Correctly handle multiple load attempts when on_load is pending
If an on_load function had not yet returned, the code server
would only correctly handle calls to code:ensure_loaded/1. That
is, each process that called code:ensured_loaded/1 for the
module in question would be suspended until the on_load function
had returned.
The code server handled calls to code:load_binary/1, code:load_file/1,
and code:load_abs/1 in the same way as for code:ensure_loaded/1.
That means that if call to one those functions attempted to load
*different* code for the module, that code would not get loaded.
Note that code:finish_loading/1 (code:atomic_load/1) will still return
{error,pending_on_load} if there is a pending on_load function for one
of the modules that are about to be loaded. The reason is that
code:finish_loading/1 is meant to either succeed directly, or fail
quickly if there is any problem.
---
lib/kernel/src/code_server.erl | 131 ++++++++++++++++++++++++-----------------
lib/kernel/test/code_SUITE.erl | 58 +++++++++++++++++-
2 files changed, 133 insertions(+), 56 deletions(-)
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl
index 10264382eb..6174136507 100644
--- a/lib/kernel/src/code_server.erl
+++ b/lib/kernel/src/code_server.erl
@@ -32,8 +32,12 @@
-import(lists, [foreach/2]).
+-type on_load_action() ::
+ fun((term(), state()) -> {'reply',term(),state()} |
+ {'noreply',state()}).
+
-type on_load_item() :: {{pid(),reference()},module(),
- file:name_all(),[pid()]}.
+ [{pid(),on_load_action()}]}.
-record(state, {supervisor :: pid(),
root :: file:name_all(),
@@ -304,7 +308,7 @@ handle_call({ensure_loaded,Mod}, From, St) when is_atom(Mod) ->
true ->
{reply,{module,Mod},St};
false when St#state.mode =:= interactive ->
- load_file(Mod, From, St);
+ ensure_loaded(Mod, From, St);
false ->
{reply,{error,embedded},St}
end;
@@ -1084,13 +1088,11 @@ load_abs(File, Mod, From, St) ->
{reply,{error,nofile},St}
end.
-try_load_module(File, Mod, Bin, From, St0) ->
- case pending_on_load(Mod, From, St0) of
- no ->
- try_load_module_1(File, Mod, Bin, From, St0);
- {yes,St} ->
- {noreply,St}
- end.
+try_load_module(File, Mod, Bin, From, St) ->
+ Action = fun(_, S) ->
+ try_load_module_1(File, Mod, Bin, From, S)
+ end,
+ handle_pending_on_load(Action, Mod, From, St).
try_load_module_1(File, Mod, Bin, From, #state{moddb=Db}=St) ->
case is_sticky(Mod, Db) of
@@ -1117,19 +1119,19 @@ try_load_module_2(File, Mod, Bin, From, Architecture,
{reply,ok,St}
end.
-try_load_module_3(File, Mod, Bin, From, Architecture,
- #state{moddb=Db}=St) ->
- case erlang:load_module(Mod, Bin) of
- {module,Mod} = Module ->
- ets:insert(Db, {Mod,File}),
- post_beam_load([Mod], Architecture, St),
- {reply,Module,St};
- {error,on_load} ->
- handle_on_load(Mod, File, From, St);
- {error,What} = Error ->
- error_msg("Loading of ~ts failed: ~p\n", [File, What]),
- {reply,Error,St}
- end.
+try_load_module_3(File, Mod, Bin, From, Architecture, St0) ->
+ Action = fun({module,_}=Module, #state{moddb=Db}=S) ->
+ ets:insert(Db, {Mod,File}),
+ post_beam_load([Mod], Architecture, S),
+ {reply,Module,S};
+ ({error,on_load_failure}=Error, S) ->
+ {reply,Error,S};
+ ({error,What}=Error, S) ->
+ error_msg("Loading of ~ts failed: ~p\n", [File, What]),
+ {reply,Error,S}
+ end,
+ Res = erlang:load_module(Mod, Bin),
+ handle_on_load(Res, Action, Mod, From, St0).
hipe_result_to_status(Result, #state{moddb=Db}) ->
case Result of
@@ -1154,11 +1156,22 @@ int_list([H|T]) when is_integer(H) -> int_list(T);
int_list([_|_]) -> false;
int_list([]) -> true.
+ensure_loaded(Mod, From, St0) ->
+ Action = fun(_, S) ->
+ case erlang:module_loaded(Mod) of
+ true ->
+ {reply,{module,Mod},S};
+ false ->
+ load_file_1(Mod, From, S)
+ end
+ end,
+ handle_pending_on_load(Action, Mod, From, St0).
+
load_file(Mod, From, St0) ->
- case pending_on_load(Mod, From, St0) of
- no -> load_file_1(Mod, From, St0);
- {yes,St} -> {noreply,St}
- end.
+ Action = fun(_, S) ->
+ load_file_1(Mod, From, S)
+ end,
+ handle_pending_on_load(Action, Mod, From, St0).
load_file_1(Mod, From, #state{path=Path}=St) ->
case mod_to_bin(Path, Mod) of
@@ -1308,55 +1321,56 @@ run([F|Fs], Data0) ->
%% The on_load functionality.
%% -------------------------------------------------------
-handle_on_load(Mod, File, From, #state{on_load=OnLoad0}=St0) ->
+handle_on_load({error,on_load}, Action, Mod, From, St0) ->
+ #state{on_load=OnLoad0} = St0,
Fun = fun() ->
Res = erlang:call_on_load_function(Mod),
exit(Res)
end,
PidRef = spawn_monitor(Fun),
- OnLoad = [{PidRef,Mod,File,[From]}|OnLoad0],
+ PidAction = {From,Action},
+ OnLoad = [{PidRef,Mod,[PidAction]}|OnLoad0],
St = St0#state{on_load=OnLoad},
- {noreply,St}.
+ {noreply,St};
+handle_on_load(Res, Action, _, _, St) ->
+ Action(Res, St).
-pending_on_load(_, _, #state{on_load=[]}) ->
- no;
-pending_on_load(Mod, From, #state{on_load=OnLoad0}=St) ->
+handle_pending_on_load(Action, Mod, From, #state{on_load=OnLoad0}=St) ->
case lists:keyfind(Mod, 2, OnLoad0) of
false ->
- no;
- {{From,_Ref},Mod,_File,_Pids} ->
+ Action(ok, St);
+ {{From,_Ref},Mod,_Pids} ->
%% The on_load function tried to make an external
%% call to its own module. That would be a deadlock.
%% Fail the call. (The call is probably from error_handler,
%% and it will ignore the actual error reason and cause
%% an undef execption.)
- _ = reply(From, {error,deadlock}),
- {yes,St};
- {_,_,_,_} ->
- OnLoad = pending_on_load_1(Mod, From, OnLoad0),
- {yes,St#state{on_load=OnLoad}}
+ {reply,{error,deadlock},St};
+ {_,_,_} ->
+ OnLoad = handle_pending_on_load_1(Mod, {From,Action}, OnLoad0),
+ {noreply,St#state{on_load=OnLoad}}
end.
-pending_on_load_1(Mod, From, [{PidRef,Mod,File,Pids}|T]) ->
- [{PidRef,Mod,File,[From|Pids]}|T];
-pending_on_load_1(Mod, From, [H|T]) ->
- [H|pending_on_load_1(Mod, From, T)];
-pending_on_load_1(_, _, []) -> [].
+handle_pending_on_load_1(Mod, From, [{PidRef,Mod,Pids}|T]) ->
+ [{PidRef,Mod,[From|Pids]}|T];
+handle_pending_on_load_1(Mod, From, [H|T]) ->
+ [H|handle_pending_on_load_1(Mod, From, T)];
+handle_pending_on_load_1(_, _, []) -> [].
-finish_on_load(PidRef, OnLoadRes, #state{on_load=OnLoad0,moddb=Db}=State) ->
+finish_on_load(PidRef, OnLoadRes, #state{on_load=OnLoad0}=St0) ->
case lists:keyfind(PidRef, 1, OnLoad0) of
false ->
%% Since this process in general silently ignores messages
%% it doesn't understand, it should also ignore a 'DOWN'
%% message with an unknown reference.
- State;
- {PidRef,Mod,File,WaitingPids} ->
- finish_on_load_1(Mod, File, OnLoadRes, WaitingPids, Db),
- OnLoad = [E || {R,_,_,_}=E <- OnLoad0, R =/= PidRef],
- State#state{on_load=OnLoad}
+ St0;
+ {PidRef,Mod,Waiting} ->
+ St = finish_on_load_1(Mod, OnLoadRes, Waiting, St0),
+ OnLoad = [E || {R,_,_}=E <- OnLoad0, R =/= PidRef],
+ St#state{on_load=OnLoad}
end.
-finish_on_load_1(Mod, File, OnLoadRes, WaitingPids, Db) ->
+finish_on_load_1(Mod, OnLoadRes, Waiting, St) ->
Keep = OnLoadRes =:= ok,
erlang:finish_after_on_load(Mod, Keep),
Res = case Keep of
@@ -1365,11 +1379,20 @@ finish_on_load_1(Mod, File, OnLoadRes, WaitingPids, Db) ->
_ = erts_code_purger:purge(Mod),
{error,on_load_failure};
true ->
- ets:insert(Db, {Mod,File}),
{module,Mod}
end,
- _ = [reply(Pid, Res) || Pid <- WaitingPids],
- ok.
+ finish_on_load_2(Waiting, Res, St).
+
+finish_on_load_2([{Pid,Action}|T], Res, St0) ->
+ case Action(Res, St0) of
+ {reply,Rep,St} ->
+ _ = reply(Pid, Rep),
+ finish_on_load_2(T, Res, St);
+ {noreply,St} ->
+ finish_on_load_2(T, Res, St)
+ end;
+finish_on_load_2([], _, St) ->
+ St.
finish_on_load_report(_Mod, Atom) when is_atom(Atom) ->
%% No error reports for atoms.
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index e238eea2de..8da67c89f8 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -35,7 +35,7 @@
purge_stacktrace/1, mult_lib_roots/1, bad_erl_libs/1,
code_archive/1, code_archive2/1, on_load/1, on_load_binary/1,
on_load_embedded/1, on_load_errors/1, on_load_update/1,
- on_load_purge/1, on_load_self_call/1,
+ on_load_purge/1, on_load_self_call/1, on_load_pending/1,
big_boot_embedded/1,
native_early_modules/1, get_mode/1,
normalized_paths/1]).
@@ -65,7 +65,7 @@ all() ->
purge_stacktrace, mult_lib_roots,
bad_erl_libs, code_archive, code_archive2, on_load,
on_load_binary, on_load_embedded, on_load_errors, on_load_update,
- on_load_purge, on_load_self_call,
+ on_load_purge, on_load_self_call, on_load_pending,
big_boot_embedded, native_early_modules, get_mode, normalized_paths].
groups() ->
@@ -830,6 +830,12 @@ check_funs({'$M_EXPR',warning_msg,2},
[{code_server,finish_on_load_report,2} | _]) -> 0;
check_funs({'$M_EXPR','$F_EXPR',1},
[{code_server,run,2}|_]) -> 0;
+check_funs({'$M_EXPR','$F_EXPR',2},
+ [{code_server,handle_on_load,5}|_]) -> 0;
+check_funs({'$M_EXPR','$F_EXPR',2},
+ [{code_server,handle_pending_on_load,4}|_]) -> 0;
+check_funs({'$M_EXPR','$F_EXPR',2},
+ [{code_server,finish_on_load_2,3}|_]) -> 0;
%% This is cheating! /raimo
%%
%% check_funs(This = {M,_,_}, Path) ->
@@ -1549,6 +1555,54 @@ on_load_do_load(Mod, Code) ->
Any -> Any
end.
+on_load_pending(_Config) ->
+ Mod = ?FUNCTION_NAME,
+ Tree1 = ?Q(["-module('@Mod@').\n",
+ "-on_load(f/0).\n",
+ "f() ->\n",
+ " register('@Mod@', self()),\n",
+ " receive _ -> ok end.\n"]),
+ merl:print(Tree1),
+ {ok,Mod,Code1} = merl:compile(Tree1),
+
+ Tree2 = ?Q(["-module('@Mod@').\n",
+ "-export([t/0]).\n",
+ "t() -> ok.\n"]),
+ merl:print(Tree2),
+ {ok,Mod,Code2} = merl:compile(Tree2),
+
+ Self = self(),
+ {_,Ref1} =
+ spawn_monitor(fun() ->
+ Self ! started1,
+ {module,Mod} = code:load_binary(Mod, "", Code1)
+ end),
+ receive started1 -> ok end,
+ timer:sleep(10),
+ {_,Ref2} =
+ spawn_monitor(fun() ->
+ Self ! started2,
+ {module,Mod} = code:load_binary(Mod, "", Code2),
+ ok = Mod:t()
+ end),
+ receive started2 -> ok end,
+ receive
+ Unexpected ->
+ ct:fail({unexpected,Unexpected})
+ after 100 ->
+ ok
+ end,
+ Mod ! go,
+ receive
+ {'DOWN',Ref1,process,_,normal} -> ok
+ end,
+ receive
+ {'DOWN',Ref2,process,_,normal} -> ok
+ end,
+ ok = Mod:t(),
+ ok.
+
+
%% Test that the native code of early loaded modules is loaded.
native_early_modules(Config) when is_list(Config) ->
case erlang:system_info(hipe_architecture) of
--
cgit v1.2.3
From 72fc3ab8b5bd19cbd7c9d0b507d97a942d4177de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Gustavsson?=
Date: Wed, 4 May 2016 09:45:24 +0200
Subject: Update documentation regarding improvements
---
system/doc/reference_manual/code_loading.xml | 55 +++++++++++++---------------
1 file changed, 25 insertions(+), 30 deletions(-)
diff --git a/system/doc/reference_manual/code_loading.xml b/system/doc/reference_manual/code_loading.xml
index e9da40e02f..f6fd2911fa 100644
--- a/system/doc/reference_manual/code_loading.xml
+++ b/system/doc/reference_manual/code_loading.xml
@@ -130,26 +130,6 @@ loop() ->
Running a Function When a Module is Loaded
-
- The on_load feature is to be considered experimental
- as there are a number of known weak points in current semantics,
- which therefore might change in future Erlang/OTP releases:
-
- Doing external call in on_load to the module itself
- leads to deadlock.
- At module upgrade, other processes calling the module
- get suspended waiting for on_load to finish. This can be very bad
- for applications with demands on realtime characteristics.
- At module upgrade, no rollback is done if the
- on_load function fails.
- The system is left in a bad limbo state without any working
- and reachable instance of the module.
-
- The problems with module upgrade described above can be fixed in future
- Erlang/OTP releases by changing the behaviour to not make the module reachable until
- after the on_load function has successfully returned.
-
-
The -on_load() directive names a function that is to
be run automatically when a module is loaded.
Its syntax is as follows:
@@ -159,20 +139,35 @@ loop() ->
It is not necessary to export the function. It is called in a
freshly spawned process (which terminates as soon as the function
- returns). The function must return ok if the module is to
- remain loaded and become callable, or any other value if the module
- is to be unloaded. Generating an exception also causes the
- module to be unloaded. If the return value is not an atom,
- a warning error report is sent to the error logger.
-
- A process that calls any function in a module whose on_load
- function has not yet returned, is suspended until the on_load
- function has returned.
+ returns).
+
+ The function must return ok if the module is to
+ become the new current code for the module and become
+ callable.
+
+ Returning any other value or generating an exception
+ causes the new code to be unloaded. If the return value is not an
+ atom, a warning error report is sent to the error logger.
+
+ If there already is current code for the module, that code will
+ remain current and can be called until the on_load function
+ has returned. If the on_load function fails, the current
+ code (if any) will remain current. If there is no current code for
+ a module, any process that makes an external call to the module
+ before the on_load function has finished will be suspended
+ until the on_load function have finished.
+
+
+ Before OTP 19, if the on_load function failed, any
+ previously current code would become old, essentially leaving
+ the system without any working and reachable instance of the
+ module. That problem has been eliminated in OTP 19.
+
In embedded mode, first all modules are loaded.
Then all on_load functions are called. The system is
terminated unless all of the on_load functions return
- ok
.
+ ok.
Example:
--
cgit v1.2.3