summaryrefslogtreecommitdiff
path: root/erts
diff options
context:
space:
mode:
authorRickard Green <rickard@erlang.org>2019-10-17 16:23:56 +0200
committerRickard Green <rickard@erlang.org>2019-10-29 19:53:35 +0100
commit4a59ebeb43184fb9dcdb3d2b4aeb454a2caed700 (patch)
treee4a515c0082f5d0bbb5df5396264115004b01190 /erts
parentdb6059a9217767a6e42e93cec05089c0ec977d20 (diff)
downloaderlang-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.names1
-rw-r--r--erts/emulator/beam/beam_bif_load.c493
-rw-r--r--erts/emulator/beam/erl_alloc.types1
-rw-r--r--erts/emulator/beam/erl_process.c64
-rw-r--r--erts/emulator/beam/global.h3
-rw-r--r--erts/emulator/test/Makefile3
-rw-r--r--erts/emulator/test/code_SUITE.erl1
-rw-r--r--erts/emulator/test/dirty_nif_SUITE.erl50
-rw-r--r--erts/emulator/test/dirty_nif_SUITE_data/dirty_nif_SUITE.c59
-rw-r--r--erts/emulator/test/literal_area_collector_test.erl80
-rw-r--r--erts/emulator/test/z_SUITE.erl10
-rw-r--r--erts/preloaded/ebin/erts_internal.beambin11060 -> 11296 bytes
-rw-r--r--erts/preloaded/src/erts_internal.erl11
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
index ae84edc3fe..3aaaa5cb03 100644
--- a/erts/preloaded/ebin/erts_internal.beam
+++ b/erts/preloaded/ebin/erts_internal.beam
Binary files differ
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'.