diff options
author | Rickard Green <rickard@erlang.org> | 2019-10-17 16:23:56 +0200 |
---|---|---|
committer | Rickard Green <rickard@erlang.org> | 2019-10-29 19:53:35 +0100 |
commit | 4a59ebeb43184fb9dcdb3d2b4aeb454a2caed700 (patch) | |
tree | e4a515c0082f5d0bbb5df5396264115004b01190 /erts | |
parent | db6059a9217767a6e42e93cec05089c0ec977d20 (diff) | |
download | erlang-4a59ebeb43184fb9dcdb3d2b4aeb454a2caed700.tar.gz |
Fix release of literal areas
Literal areas could get prematurely removed and accessed from the
exit reason of a terminated process, or accessed from the heap of
a process executing dirty while terminating.
Diffstat (limited to 'erts')
-rw-r--r-- | erts/emulator/beam/atom.names | 1 | ||||
-rw-r--r-- | erts/emulator/beam/beam_bif_load.c | 493 | ||||
-rw-r--r-- | erts/emulator/beam/erl_alloc.types | 1 | ||||
-rw-r--r-- | erts/emulator/beam/erl_process.c | 64 | ||||
-rw-r--r-- | erts/emulator/beam/global.h | 3 | ||||
-rw-r--r-- | erts/emulator/test/Makefile | 3 | ||||
-rw-r--r-- | erts/emulator/test/code_SUITE.erl | 1 | ||||
-rw-r--r-- | erts/emulator/test/dirty_nif_SUITE.erl | 50 | ||||
-rw-r--r-- | erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c | 59 | ||||
-rw-r--r-- | erts/emulator/test/literal_area_collector_test.erl | 80 | ||||
-rw-r--r-- | erts/emulator/test/z_SUITE.erl | 10 | ||||
-rw-r--r-- | erts/preloaded/ebin/erts_internal.beam | bin | 11060 -> 11296 bytes | |||
-rw-r--r-- | erts/preloaded/src/erts_internal.erl | 11 |
13 files changed, 688 insertions, 88 deletions
diff --git a/erts/emulator/beam/atom.names b/erts/emulator/beam/atom.names index 7b12e5432d..348d75303d 100644 --- a/erts/emulator/beam/atom.names +++ b/erts/emulator/beam/atom.names @@ -698,6 +698,7 @@ atom values atom version atom visible atom wait +atom wait_release_literal_area_switch atom waiting atom wall_clock atom warning diff --git a/erts/emulator/beam/beam_bif_load.c b/erts/emulator/beam/beam_bif_load.c index 023ee3ef4b..d7e74cd622 100644 --- a/erts/emulator/beam/beam_bif_load.c +++ b/erts/emulator/beam/beam_bif_load.c @@ -68,23 +68,6 @@ Process *erts_code_purger = NULL; #ifdef ERTS_DIRTY_SCHEDULERS Process *erts_dirty_process_code_checker; #endif -erts_smp_atomic_t erts_copy_literal_area__; -#define ERTS_SET_COPY_LITERAL_AREA(LA) \ - erts_smp_atomic_set_nob(&erts_copy_literal_area__, \ - (erts_aint_t) (LA)) -Process *erts_literal_area_collector = NULL; - -typedef struct ErtsLiteralAreaRef_ ErtsLiteralAreaRef; -struct ErtsLiteralAreaRef_ { - ErtsLiteralAreaRef *next; - ErtsLiteralArea *literal_area; -}; - -struct { - erts_smp_mtx_t mtx; - ErtsLiteralAreaRef *first; - ErtsLiteralAreaRef *last; -} release_literal_areas; static void set_default_trace_pattern(Eterm module); static Eterm check_process_code(Process* rp, Module* modp, int *redsp, int fcalls); @@ -116,17 +99,13 @@ init_purge_state(void) purge_state.saved_old.code_hdr = 0; } +static void +init_release_literal_areas(void); + void erts_beam_bif_load_init(void) { - erts_smp_mtx_init(&release_literal_areas.mtx, "release_literal_areas", NIL, - ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC); - - release_literal_areas.first = NULL; - release_literal_areas.last = NULL; - erts_smp_atomic_init_nob(&erts_copy_literal_area__, - (erts_aint_t) NULL); - + init_release_literal_areas(); init_purge_state(); } @@ -1314,6 +1293,68 @@ hfrag_literal_copy(Eterm **hpp, ErlOffHeap *ohp, } } +/* + * Release of literal areas... + * + * Overview over how literal areas are released. + * + * - A literal area to remove is placed in the release_literal_areas.first + * queue. + * - The erts_literal_area_collector process is woken and calls + * erts_internal:release_literal_area_switch() which publishes the + * area to release available to the emulator + * (ERTS_COPY_LITERAL_AREA()). + * - The literal area collector process gets suspended waiting thread + * progress in order to ensure all schedulers see the newly published + * area to release. + * - When the literal area collector process is resumed after thread + * progress has completed, erts_internal:release_literal_area_switch() + * returns 'true'. + * - The literal area collector process sends copy-literals requests + * to all processes in the system. + * - Processes inspects their heap for literals in the area, if + * such are found do a literal-gc to make copies on the heap + * of all those literals, and then send replies to the + * literal area collector process. + * - Processes that terminates replies even though they might need to + * access literal areas. When a process that might need to access a + * literal area terminates, it blocks release of literal areas + * by incrementing a counter, and later when termination has + * completed decrements that counter. The increment is performed + * before replying to the copy-literals request. + * - When all processes has responded, the literal area collector + * process calls erts_internal:release_literal_area_switch() again + * in order to switch to the next area. + * - erts_internal:release_literal_area_switch() changes the set of + * counters that blocks release of literal areas + * - The literal area collector process gets suspended waiting thread + * progress in order to ensure that the change of counters is visable + * by all schedulers. + * - When the literal area collector process is resumed after thread + * progress has completed, erts_internal:release_literal_area_switch() + * inspects all counters in previously used set ensuring that no + * terminating processes (which began termination before the change + * of counters) are lingering. If needed the literal area collector + * process will be blocked in + * erts_internal:release_literal_area_switch() waiting for all + * terminating processes to complete. + * - When counter inspection is complete + * erts_internal:release_literal_area_switch() returns 'true' if + * a new area was set for release and 'false' if no more areas have + * been scheduled for release. + * + * When multiple literal areas have been queued for release, + * erts_internal:release_literal_area_switch() will time the thread + * progress waits so each wait period will be utilized both for + * ensuring that a new area is seen by all schedulers, and ensuring + * that a change of counters is seen by all schedulers. By this only + * one thread progress wait will be done per literal area collected + * until the last literal area which will need two thread progress + * wait periods. + */ + +static Export *wait_release_literal_area_switch; + #ifdef ERTS_SMP ErtsThrPrgrLaterOp later_literal_area_switch; @@ -1323,87 +1364,385 @@ typedef struct { ErtsLiteralArea *la; } ErtsLaterReleasLiteralArea; +#endif + +erts_smp_atomic_t erts_copy_literal_area__; +#define ERTS_SET_COPY_LITERAL_AREA(LA) \ + erts_smp_atomic_set_nob(&erts_copy_literal_area__, \ + (erts_aint_t) (LA)) +Process *erts_literal_area_collector = NULL; + +typedef struct ErtsLiteralAreaRef_ ErtsLiteralAreaRef; +struct ErtsLiteralAreaRef_ { + ErtsLiteralAreaRef *next; + ErtsLiteralArea *literal_area; +}; + +typedef struct { + erts_smp_atomic_t counter[2]; +} ErtsReleaseLiteralAreaBlockCounters; + +typedef struct { + union { + ErtsReleaseLiteralAreaBlockCounters block; + char align__[ERTS_ALC_CACHE_LINE_ALIGN_SIZE(sizeof(ErtsReleaseLiteralAreaBlockCounters))]; + } u; +} ErtsAlignedReleaseLiteralAreaBlockCounters; + +typedef enum { + ERTS_RLA_BLOCK_STATE_NONE, + ERTS_RLA_BLOCK_STATE_SWITCHED_IX, + ERTS_RLA_BLOCK_STATE_WAITING +} ErtsReleaseLiteralAreaBlockState; + +static struct { + erts_smp_mtx_t mtx; + ErtsLiteralAreaRef *first; + ErtsLiteralAreaRef *last; + ErtsAlignedReleaseLiteralAreaBlockCounters *bc; + erts_smp_atomic32_t block_ix; + int wait_sched_ix; + ErtsReleaseLiteralAreaBlockState block_state; + ErtsLiteralArea *block_area; +} release_literal_areas; + static void -later_release_literal_area(void *vlrlap) +init_release_literal_areas(void) { - ErtsLaterReleasLiteralArea *lrlap; - lrlap = (ErtsLaterReleasLiteralArea *) vlrlap; - erts_release_literal_area(lrlap->la); - erts_free(ERTS_ALC_T_RELEASE_LAREA, vlrlap); + int i; + erts_smp_mtx_init(&release_literal_areas.mtx, "release_literal_areas", NIL, + ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC); + + release_literal_areas.first = NULL; + release_literal_areas.last = NULL; + erts_smp_atomic_init_nob(&erts_copy_literal_area__, + (erts_aint_t) NULL); + + erts_smp_atomic32_init_nob(&release_literal_areas.block_ix, 0); + release_literal_areas.wait_sched_ix = 0; + release_literal_areas.block_state = ERTS_RLA_BLOCK_STATE_NONE; + release_literal_areas.block_area = NULL; + + release_literal_areas.bc = + erts_alloc_permanent_cache_aligned(ERTS_ALC_T_RLA_BLOCK_CNTRS, + sizeof(ErtsAlignedReleaseLiteralAreaBlockCounters) + * erts_no_schedulers); + /* + * The literal-area-collector has an increment in all block counters + * which it only removes when waiting for other increments to disappear. + */ + for (i = 0; i < erts_no_schedulers; i++) { + erts_smp_atomic_init_nob(&release_literal_areas.bc[i].u.block.counter[0], 1); + erts_smp_atomic_init_nob(&release_literal_areas.bc[i].u.block.counter[1], 1); + } + + wait_release_literal_area_switch = erts_export_put(am_erts_internal, + am_wait_release_literal_area_switch, + 1); } +#ifdef ERTS_SMP static void -complete_literal_area_switch(void *literal_area) +rla_resume(void *literal_area) { - Process *p = erts_literal_area_collector; - erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS); - erts_resume(p, ERTS_PROC_LOCK_STATUS); - erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS); - if (literal_area) - erts_release_literal_area((ErtsLiteralArea *) literal_area); + erts_resume(erts_literal_area_collector, 0); } #endif -BIF_RETTYPE erts_internal_release_literal_area_switch_0(BIF_ALIST_0) + +static ERTS_INLINE Sint +rla_bc_read(int sched_ix, int block_ix) { - ErtsLiteralArea *unused_la; - ErtsLiteralAreaRef *la_ref; + return (Sint) erts_smp_atomic_read_nob( + &release_literal_areas.bc[sched_ix].u.block.counter[block_ix]); +} - if (BIF_P != erts_literal_area_collector) - BIF_ERROR(BIF_P, EXC_NOTSUP); +static ERTS_INLINE Sint +rla_bc_read_acqb(int sched_ix, int block_ix) +{ + return (Sint) erts_smp_atomic_read_acqb( + &release_literal_areas.bc[sched_ix].u.block.counter[block_ix]); +} - erts_smp_mtx_lock(&release_literal_areas.mtx); +static ERTS_INLINE Sint +rla_bc_dec_read_acqb(int sched_ix, int block_ix) +{ + return (Sint) erts_smp_atomic_dec_read_acqb( + &release_literal_areas.bc[sched_ix].u.block.counter[block_ix]); +} + +static ERTS_INLINE Sint +rla_bc_dec_read_relb(int sched_ix, int block_ix) +{ + return (Sint) erts_smp_atomic_dec_read_relb( + &release_literal_areas.bc[sched_ix].u.block.counter[block_ix]); +} + +static ERTS_INLINE void +rla_bc_inc(int sched_ix, int block_ix) +{ + erts_smp_atomic_inc_nob( + &release_literal_areas.bc[sched_ix].u.block.counter[block_ix]); +} + + +Uint32 +erts_block_release_literal_area(void) +{ + ErtsSchedulerData *esdp = erts_get_scheduler_data(); + int sched_ix; + int block_ix; + + ASSERT(esdp->type == ERTS_SCHED_NORMAL); + + sched_ix = ((int) esdp->no) - 1; + ASSERT((sched_ix & ~0xffff) == 0); + + ASSERT(0 <= sched_ix && sched_ix <= erts_no_schedulers); + + block_ix = (int) erts_smp_atomic32_read_nob(&release_literal_areas.block_ix); + ASSERT(block_ix == 0 || block_ix == 1); + + rla_bc_inc(sched_ix, block_ix); + /* + * The returned value needs to be non-zero, so the user can + * use zero as a marker for not having blocked. + * + * Both block_ix and sched_ix can be zero so we set + * the highest (unused) bits to 0xfed00000 + */ + return (Uint32) 0xfed00000 | ((block_ix << 16) | sched_ix); +} + +static void +wakeup_literal_area_collector(void *unused) +{ + erts_queue_message(erts_literal_area_collector, + 0, + erts_alloc_message(0, NULL), + am_copy_literals, + am_system); +} + +void +erts_unblock_release_literal_area(Uint32 sched_block_ix) +{ + Sint block_count; + int block_ix = (int) ((sched_block_ix >> 16) & 0xf); + int sched_ix = (int) (sched_block_ix & 0xffff); + + ASSERT((sched_block_ix & ((Uint32) 0xfff00000)) + == (Uint32) 0xfed00000); + + ASSERT(block_ix == 0 || block_ix == 1); + + block_count = rla_bc_dec_read_relb(sched_ix, block_ix); + + ASSERT(block_count >= 0); + + if (!block_count) { + /* + * Wakeup literal collector so it can continue... + * + * We don't know what locks we have here, so schedule + * the operation... + */ + int sid = 1; + ErtsSchedulerData *esdp = erts_get_scheduler_data(); + if (esdp && esdp->type == ERTS_SCHED_NORMAL) + sid = (int) esdp->no; + erts_schedule_misc_aux_work(sid, + wakeup_literal_area_collector, + NULL); + } +} + +static void +rla_switch_area(void) +{ + ErtsLiteralAreaRef *la_ref; + + erts_smp_mtx_lock(&release_literal_areas.mtx); la_ref = release_literal_areas.first; if (la_ref) { release_literal_areas.first = la_ref->next; if (!release_literal_areas.first) release_literal_areas.last = NULL; } - erts_smp_mtx_unlock(&release_literal_areas.mtx); - unused_la = ERTS_COPY_LITERAL_AREA(); + if (!la_ref) + ERTS_SET_COPY_LITERAL_AREA(NULL); + else { + ERTS_SET_COPY_LITERAL_AREA(la_ref->literal_area); + erts_free(ERTS_ALC_T_LITERAL_REF, la_ref); + } +} + +BIF_RETTYPE erts_internal_release_literal_area_switch_0(BIF_ALIST_0) +{ + ErtsLiteralArea *new_area, *old_area; + int wait_ix = 0; + int sched_ix = 0; + + if (BIF_P != erts_literal_area_collector) + BIF_ERROR(BIF_P, EXC_NOTSUP); + + while (1) { + int six; + + switch (release_literal_areas.block_state) { + case ERTS_RLA_BLOCK_STATE_NONE: { + + old_area = ERTS_COPY_LITERAL_AREA(); + + rla_switch_area(); + + if (old_area) { + int block_ix; + /* + * Switch block index. + */ + block_ix = (int) erts_smp_atomic32_read_nob(&release_literal_areas.block_ix); + erts_smp_atomic32_set_nob(&release_literal_areas.block_ix, + (erts_aint32_t) !block_ix); + release_literal_areas.block_state = ERTS_RLA_BLOCK_STATE_SWITCHED_IX; + ASSERT(!release_literal_areas.block_area); + release_literal_areas.block_area = old_area; + } + + new_area = ERTS_COPY_LITERAL_AREA(); - if (!la_ref) { - ERTS_SET_COPY_LITERAL_AREA(NULL); - if (unused_la) { -#ifdef ERTS_SMP - ErtsLaterReleasLiteralArea *lrlap; - lrlap = erts_alloc(ERTS_ALC_T_RELEASE_LAREA, - sizeof(ErtsLaterReleasLiteralArea)); - lrlap->la = unused_la; - erts_schedule_thr_prgr_later_cleanup_op( - later_release_literal_area, - (void *) lrlap, - &lrlap->lop, - (sizeof(ErtsLaterReleasLiteralArea) - + sizeof(ErtsLiteralArea) - + ((unused_la->end - - &unused_la->start[0]) - - 1)*(sizeof(Eterm)))); + if (!old_area && !new_area) + BIF_RET(am_false); + + publish_new_info: + +#ifndef ERTS_SMP + if (new_area) + BIF_RET(am_true); + /* fall through... */ #else - erts_release_literal_area(unused_la); + /* + * Waiting 'thread progress' will ensure that all schedulers are + * guaranteed to see the new block index and the new area before + * we continue... + */ + erts_schedule_thr_prgr_later_op(rla_resume, + NULL, + &later_literal_area_switch); + erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL); + if (new_area) { + /* + * If we also got a new block_area, we will + * take care of that the next time we come back + * after all processes has responded on + * copy-literals requests... + */ + ERTS_BIF_YIELD_RETURN(BIF_P, + am_true); + } + + ASSERT(old_area); + ERTS_VBUMP_ALL_REDS(BIF_P); + BIF_TRAP0(BIF_P, + bif_export[BIF_erts_internal_release_literal_area_switch_0]); #endif - } - BIF_RET(am_false); - } + } - ERTS_SET_COPY_LITERAL_AREA(la_ref->literal_area); + case ERTS_RLA_BLOCK_STATE_SWITCHED_IX: + wait_ix = !erts_smp_atomic32_read_nob(&release_literal_areas.block_ix); + /* + * Now all counters in the old index will monotonically + * decrease towards 1 (our own increment). Check that we + * have no other increments, than our own, in all counters + * of the old block index. Wait for other increments to + * be decremented if necessary... + */ + sched_ix = 0; + break; + + case ERTS_RLA_BLOCK_STATE_WAITING: + wait_ix = !erts_smp_atomic32_read_nob(&release_literal_areas.block_ix); + /* + * Woken after being waiting for a counter to reach zero... + */ + sched_ix = release_literal_areas.wait_sched_ix; + /* restore "our own increment" */ + rla_bc_inc(sched_ix, wait_ix); + break; + } - erts_free(ERTS_ALC_T_LITERAL_REF, la_ref); + ASSERT(0 <= sched_ix && sched_ix < erts_no_schedulers); -#ifdef ERTS_SMP - erts_schedule_thr_prgr_later_op(complete_literal_area_switch, - unused_la, - &later_literal_area_switch); - erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, NULL); - ERTS_BIF_YIELD_RETURN(BIF_P, am_true); -#else - erts_release_literal_area(unused_la); - BIF_RET(am_true); +#ifdef DEBUG + for (six = 0; six < sched_ix; six++) { + ASSERT(1 == rla_bc_read(six, wait_ix)); + } #endif + for (six = sched_ix; six < erts_no_schedulers; six++) { + Sint block_count = rla_bc_read_acqb(six, wait_ix); + ASSERT(block_count >= 1); + if (block_count == 1) + continue; + + block_count = rla_bc_dec_read_acqb(six, wait_ix); + if (!block_count) { + /* + * We brought it down to zero ourselves, so no need to wait. + * Since the counter is guaranteed to be monotonically + * decreasing (disregarding our own operations) it is safe + * to continue. Restore "our increment" in preparation for + * next switch. + */ + rla_bc_inc(six, wait_ix); + continue; + } + + /* + * Wait for counter to be brought down to zero. The one bringing + * the counter down to zero will wake us up. We might also be + * woken later in erts_internal:wait_release_literal_area_switch() + * if a new area appears (handled here below). + */ + release_literal_areas.wait_sched_ix = six; + release_literal_areas.block_state = ERTS_RLA_BLOCK_STATE_WAITING; + if (!ERTS_COPY_LITERAL_AREA()) { + rla_switch_area(); + new_area = ERTS_COPY_LITERAL_AREA(); + if (new_area) { + /* + * A new area showed up. Start the work with that area + * and come back and check block counters when that has + * been handled. + */ + old_area = release_literal_areas.block_area; + goto publish_new_info; + } + } + + /* + * Wait for block_counter to reach zero or a new literal area + * to handle... + */ + BIF_TRAP1(wait_release_literal_area_switch, BIF_P, am_copy_literals); + } + + /* Done checking all block counters, release the literal area... */ + + release_literal_areas.block_state = ERTS_RLA_BLOCK_STATE_NONE; + erts_release_literal_area(release_literal_areas.block_area); + release_literal_areas.block_area = NULL; + +#ifdef DEBUG + /* All counters should be at 1; ready for next switch... */ + for (six = 0; six < erts_no_schedulers; six++) { + ASSERT(1 == rla_bc_read(six, wait_ix)); + } +#endif + } } void diff --git a/erts/emulator/beam/erl_alloc.types b/erts/emulator/beam/erl_alloc.types index 252bf1cc7e..9c6cdd8f8c 100644 --- a/erts/emulator/beam/erl_alloc.types +++ b/erts/emulator/beam/erl_alloc.types @@ -286,6 +286,7 @@ type MREF_TAB_BKTS STANDARD SYSTEM magic_ref_table_buckets type MREF_TAB LONG_LIVED SYSTEM magic_ref_table type MINDIRECTION FIXED_SIZE SYSTEM magic_indirection type CRASH_DUMP STANDARD SYSTEM crash_dump +type RLA_BLOCK_CNTRS LONG_LIVED SYSTEM release_literal_area_block_counters +if threads_no_smp # Need thread safe allocs, but std_alloc and fix_alloc are not; diff --git a/erts/emulator/beam/erl_process.c b/erts/emulator/beam/erl_process.c index 9f6adb03d0..012efe0a51 100644 --- a/erts/emulator/beam/erl_process.c +++ b/erts/emulator/beam/erl_process.c @@ -11680,6 +11680,7 @@ request_system_task(Process *c_p, Eterm requester, Eterm target, goto badarg; st->type = ERTS_PSTT_CLA; noproc_res = am_ok; + fail_state = ERTS_PSFLG_FREE; if (!rp) goto noproc; break; @@ -11690,7 +11691,7 @@ request_system_task(Process *c_p, Eterm requester, Eterm target, if (!schedule_process_sys_task(rp, prio, st, &fail_state)) { Eterm failure; - if (fail_state & ERTS_PSFLG_EXITING) { + if (fail_state & (ERTS_PSFLG_EXITING|ERTS_PSFLG_FREE)) { noproc: failure = noproc_res; } @@ -12892,6 +12893,7 @@ delete_process(Process* p) ErtsPSD *psd; struct saved_calls *scb; process_breakpoint_time_t *pbt; + Uint32 block_rla_ref = (Uint32) (Uint) p->u.terminate; VERBOSE(DEBUG_PROCESSES, ("Removing process: %T\n",p->common.id)); VERBOSE(DEBUG_SHCOPY, ("[pid=%T] delete process: %p %p %p %p\n", p->common.id, @@ -12963,6 +12965,9 @@ delete_process(Process* p) ASSERT(!p->suspend_monitors); p->fvalue = NIL; + + if (block_rla_ref) + erts_unblock_release_literal_area(block_rla_ref); } static ERTS_INLINE void @@ -13909,6 +13914,7 @@ erts_continue_exit_process(Process *p) } ASSERT(erts_proc_read_refc(p) > 0); p->bif_timers = NULL; + ASSERT(!p->u.terminate); } #ifdef ERTS_SMP @@ -13934,7 +13940,9 @@ erts_continue_exit_process(Process *p) erts_exit(ERTS_ABORT_EXIT, "%s:%d: Internal error: %d\n", __FILE__, __LINE__, (int) ssr); } + ASSERT(!p->u.terminate); } + if (p->flags & F_HAVE_BLCKD_NMSCHED) { ErtsSchedSuspendResult ssr; ssr = erts_block_multi_scheduling(p, ERTS_PROC_LOCK_MAIN, 0, 1, 1); @@ -13954,6 +13962,7 @@ erts_continue_exit_process(Process *p) erts_exit(ERTS_ABORT_EXIT, "%s:%d: Internal error: %d\n", __FILE__, __LINE__, (int) ssr); } + ASSERT(!p->u.terminate); } #endif @@ -13961,10 +13970,33 @@ erts_continue_exit_process(Process *p) if (erts_db_process_exiting(p, ERTS_PROC_LOCK_MAIN)) goto yield; p->flags &= ~F_USING_DB; + ASSERT(!p->u.terminate); } - erts_set_gc_state(p, 1); state = erts_smp_atomic32_read_acqb(&p->state); + /* + * If we might access any literals on the heap after this point, + * we need to block release of literal areas. After this point, + * since cleanup of sys-tasks reply to copy-literals requests. + * Note that we do not only have to prevent release of + * currently processed literal area, but also future processed + * literal areas, until we are guaranteed not to access any + * literal areas at all. + * + * - A non-immediate exit reason may refer to literals. + * - A process executing dirty while terminated, might access + * any term on the heap, and therfore literals, until it has + * stopped executing dirty. + */ + if (!p->u.terminate + && (is_not_immed(reason) + || (state & (ERTS_PSFLG_DIRTY_RUNNING + | ERTS_PSFLG_DIRTY_RUNNING_SYS)))) { + Uint32 block_rla_ref = erts_block_release_literal_area(); + p->u.terminate = (void *) (Uint) block_rla_ref; + } + + erts_set_gc_state(p, 1); if (state & ERTS_PSFLG_ACTIVE_SYS #ifdef ERTS_DIRTY_SCHEDULERS || p->dirty_sys_tasks @@ -13976,7 +14008,6 @@ erts_continue_exit_process(Process *p) #ifdef DEBUG erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS); - ASSERT(p->sys_task_qs == NULL); ASSERT(ERTS_PROC_GET_DELAYED_GC_TASK_QS(p) == NULL); #ifdef ERTS_DIRTY_SCHEDULERS ASSERT(p->dirty_sys_tasks == NULL); @@ -14077,8 +14108,10 @@ erts_continue_exit_process(Process *p) refc_inced = 1; } a = erts_smp_atomic32_cmpxchg_mb(&p->state, n, e); - if (a == e) + if (a == e) { + state = n; break; + } } #ifdef ERTS_DIRTY_SCHEDULERS @@ -14099,7 +14132,28 @@ erts_continue_exit_process(Process *p) dep = (p->flags & F_DISTRIBUTION) ? erts_this_dist_entry : NULL; - erts_smp_proc_unlock(p, ERTS_PROC_LOCKS_ALL); + /* + * It might show up copy-literals tasks until we + * have entered free state. Cleanup such tasks now. + */ + if (!(state & ERTS_PSFLG_ACTIVE_SYS)) + erts_smp_proc_unlock(p, ERTS_PROC_LOCKS_ALL); + else { + erts_smp_proc_unlock(p, ERTS_PROC_LOCKS_ALL_MINOR); + + do { + (void) cleanup_sys_tasks(p, state, CONTEXT_REDS); + state = erts_atomic32_read_acqb(&p->state); + } while (state & ERTS_PSFLG_ACTIVE_SYS); + + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_MAIN); + } + +#ifdef DEBUG + erts_smp_proc_lock(p, ERTS_PROC_LOCK_STATUS); + ASSERT(p->sys_task_qs == NULL); + erts_smp_proc_unlock(p, ERTS_PROC_LOCK_STATUS); +#endif if (dep) { erts_do_net_exits(dep, reason); diff --git a/erts/emulator/beam/global.h b/erts/emulator/beam/global.h index 87777d14e9..c5fdb46590 100644 --- a/erts/emulator/beam/global.h +++ b/erts/emulator/beam/global.h @@ -901,6 +901,9 @@ Eterm erl_is_function(Process* p, Eterm arg1, Eterm arg2); Eterm erts_check_process_code(Process *c_p, Eterm module, int *redsp, int fcalls); Eterm erts_proc_copy_literal_area(Process *c_p, int *redsp, int fcalls, int gc_allowed); +Uint32 erts_block_release_literal_area(void); +void erts_unblock_release_literal_area(Uint32); + typedef struct ErtsLiteralArea_ { struct erl_off_heap_header *off_heap; Eterm *end; diff --git a/erts/emulator/test/Makefile b/erts/emulator/test/Makefile index b17170c8b8..e4f1631d11 100644 --- a/erts/emulator/test/Makefile +++ b/erts/emulator/test/Makefile @@ -126,7 +126,8 @@ MODULES= \ ignore_cores \ dgawd_handler \ random_iolist \ - crypto_reference + crypto_reference \ + literal_area_collector_test NO_OPT= bs_bincomp \ bs_bit_binaries \ diff --git a/erts/emulator/test/code_SUITE.erl b/erts/emulator/test/code_SUITE.erl index 661a2ee6c9..030dbda369 100644 --- a/erts/emulator/test/code_SUITE.erl +++ b/erts/emulator/test/code_SUITE.erl @@ -395,6 +395,7 @@ constant_pools(Config) when is_list(Config) -> end, HeapSz = TotHeapSz, %% Ensure restored to hibernated state... true = HeapSz > OldHeapSz, + literal_area_collector_test:check_idle(5000), ok. no_old_heap(Parent) -> diff --git a/erts/emulator/test/dirty_nif_SUITE.erl b/erts/emulator/test/dirty_nif_SUITE.erl index 13806fd5c4..f9e45be718 100644 --- a/erts/emulator/test/dirty_nif_SUITE.erl +++ b/erts/emulator/test/dirty_nif_SUITE.erl @@ -34,7 +34,7 @@ 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, dirty_nif_send_traced/1, + code_purge/1, literal_area/1, dirty_nif_send_traced/1, nif_whereis/1, nif_whereis_parallel/1, nif_whereis_proxy/1]). -define(nif_stub,nif_stub_error(?LINE)). @@ -52,6 +52,7 @@ all() -> dirty_process_register, dirty_process_trace, code_purge, + literal_area, dirty_nif_send_traced, nif_whereis, nif_whereis_parallel]. @@ -426,6 +427,7 @@ code_purge(Config) when is_list(Config) -> Time = erlang:convert_time_unit(End-Start, native, milli_seconds), io:format("Time=~p~n", [Time]), true = Time =< 1000, + literal_area_collector_test:check_idle(5000), ok. dirty_nif_send_traced(Config) when is_list(Config) -> @@ -456,6 +458,51 @@ dirty_nif_send_traced(Config) when is_list(Config) -> true = Time2 >= 1900, ok. +dirty_literal_test_code() -> + " +-module(dirty_literal_code_test). + +-export([get_literal/0]). + +get_literal() -> + {0,1,2,3,4,5,6,7,8,9}. + +". + +literal_area(Config) when is_list(Config) -> + NifTMO = 3000, + ExtraTMO = 1000, + TotTMO = NifTMO+ExtraTMO, + Path = ?config(data_dir, Config), + File = filename:join(Path, "dirty_literal_code_test.erl"), + ok = file:write_file(File, dirty_literal_test_code()), + {ok, dirty_literal_code_test, Bin} = compile:file(File, [binary]), + {module, dirty_literal_code_test} = erlang:load_module(dirty_literal_code_test, Bin), + Me = self(), + Fun = fun () -> + dirty_terminating_literal_access( + Me, + dirty_literal_code_test:get_literal()) + end, + {Pid, Mon} = spawn_monitor(Fun), + receive {dirty_alive, Pid} -> ok end, + exit(Pid, kill), + Start = erlang:monotonic_time(millisecond), + receive {'DOWN', Mon, process, Pid, killed} -> ok end, + true = erlang:delete_module(dirty_literal_code_test), + true = erlang:purge_module(dirty_literal_code_test), + End = erlang:monotonic_time(millisecond), + %% Wait for dirty_nif to do its access... + TMO = case End - Start of + T when T < TotTMO -> + TotTMO-T; + _ -> + 0 + end, + receive after TMO -> ok end, + literal_area_collector_test:check_idle(100), + {comment, "Waited "++integer_to_list(TMO)++" milliseconds after purge"}. + %% %% Internal... %% @@ -678,6 +725,7 @@ dirty_sleeper(_) -> ?nif_stub. 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. nif_stub_error(Line) -> exit({nif_not_loaded,module,?MODULE,line,Line}). 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 0321b9898f..c54095fa1a 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 @@ -425,6 +425,62 @@ whereis_send(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) return whereis_result_term(env, rc); } +static ERL_NIF_TERM dirty_terminating_literal_access(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifPid to, self; + ERL_NIF_TERM copy, self_term, result; + ErlNifEnv* copy_env, *menv; + + /* + * Pid of test proc in argv[0] + * A literal term in argv[1] + */ + + if (argc != 2) + return enif_make_badarg(env); + + if (!enif_get_local_pid(env, argv[0], &to)) + return enif_make_badarg(env); + + if (!enif_self(env, &self)) + return enif_make_badarg(env); + + self_term = enif_make_pid(env, &self); + + copy_env = enif_alloc_env(); + copy = enif_make_copy(copy_env, argv[1]); + if (!enif_is_identical(copy, argv[1])) + return enif_make_badarg(env); + + menv = enif_alloc_env(); + result = enif_make_tuple2(menv, enif_make_atom(menv, "dirty_alive"), self_term); + enif_send(env, &to, menv, result); + enif_free_env(menv); + + while (enif_is_current_process_alive(env)); + + /* Give the system time to try and remove the literal area */ + +#ifdef __WIN32__ + Sleep(3000); +#else + sleep(3); +#endif + + /* + * If the system was successful in removing the area, + * the debug compiled emulator will have overwritten + * the data referred by argv[1] + */ + + if (!enif_is_identical(copy, argv[1])) + abort(); + + enif_free_env(copy_env); + + return self_term; +} + static ErlNifFunc nif_funcs[] = { @@ -439,7 +495,8 @@ static ErlNifFunc nif_funcs[] = {"dirty_call_while_terminated_nif", 1, dirty_call_while_terminated_nif, ERL_NIF_DIRTY_JOB_CPU_BOUND}, {"dirty_heap_access_nif", 1, dirty_heap_access_nif, ERL_NIF_DIRTY_JOB_CPU_BOUND}, {"whereis_send", 3, whereis_send, ERL_NIF_DIRTY_JOB_IO_BOUND}, - {"whereis_term", 2, whereis_term, ERL_NIF_DIRTY_JOB_CPU_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}, }; ERL_NIF_INIT(dirty_nif_SUITE,nif_funcs,load,NULL,NULL,NULL) diff --git a/erts/emulator/test/literal_area_collector_test.erl b/erts/emulator/test/literal_area_collector_test.erl new file mode 100644 index 0000000000..fb66add44c --- /dev/null +++ b/erts/emulator/test/literal_area_collector_test.erl @@ -0,0 +1,80 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(literal_area_collector_test). + +-export([check_idle/1]). + +check_idle(Timeout) when is_integer(Timeout) > 0 -> + Start = erlang:monotonic_time(millisecond), + LAC = find_lac(), + wait_until(fun () -> + case process_info(LAC, [status, + current_function, + current_stacktrace, + message_queue_len]) of + [{status,waiting}, + {current_function, + {erts_literal_area_collector,msg_loop,4}}, + {current_stacktrace, + [{erts_literal_area_collector,msg_loop,4,_}]}, + {message_queue_len,0}] -> + true; + CurrState -> + Now = erlang:monotonic_time(millisecond), + case Now - Start > Timeout of + true -> + exit({non_idle_literal_area_collecor, + CurrState}); + false -> + false + end + end + end), + ok. + + +find_lac() -> + try + lists:foreach(fun (P) -> + case process_info(P, initial_call) of + {initial_call, + {erts_literal_area_collector,start,0}} -> + throw({lac, P}); + _ -> + ok + end + end, processes()), + exit(no_literal_area_collector) + catch + throw:{lac, LAC} -> + LAC + end. + + +wait_until(Fun) -> + Res = try + Fun() + catch + T:R -> {T,R} + end, + case Res of + true -> ok; + _ -> wait_until(Fun) + end. diff --git a/erts/emulator/test/z_SUITE.erl b/erts/emulator/test/z_SUITE.erl index feea7432a9..98715ff2ed 100644 --- a/erts/emulator/test/z_SUITE.erl +++ b/erts/emulator/test/z_SUITE.erl @@ -37,7 +37,8 @@ -export([schedulers_alive/1, node_container_refc_check/1, long_timers/1, pollset_size/1, check_io_debug/1, get_check_io_info/0, - leaked_processes/1]). + leaked_processes/1, + literal_area_collector/1]). suite() -> [{ct_hooks,[ts_install_cth]}, @@ -48,7 +49,8 @@ all() -> long_timers, pollset_size, check_io_debug, %% Make sure that the leaked_processes/1 is always %% run last. - leaked_processes]. + leaked_processes, + literal_area_collector]. %%% %%% The test cases ------------------------------------------------------------- @@ -315,10 +317,14 @@ leaked_processes(Config) when is_list(Config) -> [length(Leaked)])), {comment, Comment}. +literal_area_collector(Config) when is_list(Config) -> + literal_area_collector_test:check_idle(10000). + %% %% Internal functions... %% + display_check_io(ChkIo) -> catch erlang:display('--- CHECK IO INFO ---'), catch erlang:display(ChkIo), diff --git a/erts/preloaded/ebin/erts_internal.beam b/erts/preloaded/ebin/erts_internal.beam Binary files differindex ae84edc3fe..3aaaa5cb03 100644 --- a/erts/preloaded/ebin/erts_internal.beam +++ b/erts/preloaded/ebin/erts_internal.beam diff --git a/erts/preloaded/src/erts_internal.erl b/erts/preloaded/src/erts_internal.erl index 26fb1458af..6bbbf63ba6 100644 --- a/erts/preloaded/src/erts_internal.erl +++ b/erts/preloaded/src/erts_internal.erl @@ -45,7 +45,7 @@ -export([check_process_code/3]). -export([check_dirty_process_code/2]). -export([is_process_executing_dirty/1]). --export([release_literal_area_switch/0]). +-export([release_literal_area_switch/0, wait_release_literal_area_switch/1]). -export([purge_module/2]). -export([flush_monitor_messages/3]). @@ -301,6 +301,15 @@ is_process_executing_dirty(_Pid) -> release_literal_area_switch() -> erlang:nif_error(undefined). +-spec wait_release_literal_area_switch(WaitMsg) -> 'true' | 'false' when + WaitMsg :: term(). + +wait_release_literal_area_switch(WaitMsg) -> + %% release_literal_area_switch() traps to here + %% when it needs to wait + receive WaitMsg -> ok end, + erts_internal:release_literal_area_switch(). + -spec purge_module(Module, Op) -> boolean() when Module :: module(), Op :: 'prepare' | 'prepare_on_load' | 'abort' | 'complete'. |