summaryrefslogtreecommitdiff
path: root/erts/emulator/beam/code_ix.c
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/beam/code_ix.c')
-rw-r--r--erts/emulator/beam/code_ix.c317
1 files changed, 207 insertions, 110 deletions
diff --git a/erts/emulator/beam/code_ix.c b/erts/emulator/beam/code_ix.c
index 8f393f92cf..3888c72363 100644
--- a/erts/emulator/beam/code_ix.c
+++ b/erts/emulator/beam/code_ix.c
@@ -46,20 +46,24 @@ erts_atomic32_t outstanding_blocking_code_barriers;
erts_atomic32_t the_active_code_index;
erts_atomic32_t the_staging_code_index;
-static int code_writing_seized = 0;
-static Process* code_writing_process = NULL;
-struct code_write_queue_item {
- Process *p;
- void (*aux_func)(void *);
- void *aux_arg;
- struct code_write_queue_item* next;
+struct code_permission {
+ erts_mtx_t lock;
+
+ ErtsSchedulerData *scheduler;
+ Process *owner;
+
+ int seized;
+ struct code_permission_queue_item {
+ Process *p;
+ void (*aux_func)(void *);
+ void *aux_arg;
+
+ struct code_permission_queue_item *next;
+ } *queue;
};
-static struct code_write_queue_item* code_write_queue = NULL;
-static erts_mtx_t code_write_permission_mtx;
-#ifdef ERTS_ENABLE_LOCK_CHECK
-static erts_tsd_key_t has_code_write_permission;
-#endif
+static struct code_permission code_mod_permission = {0};
+static struct code_permission code_stage_permission = {0};
#ifdef DEBUG
static erts_tsd_key_t needs_code_barrier;
@@ -74,12 +78,14 @@ void erts_code_ix_init(void)
erts_atomic32_init_nob(&outstanding_blocking_code_barriers, 0);
erts_atomic32_init_nob(&the_active_code_index, 0);
erts_atomic32_init_nob(&the_staging_code_index, 0);
- erts_mtx_init(&code_write_permission_mtx, "code_write_permission", NIL,
+
+ erts_mtx_init(&code_mod_permission.lock,
+ "code_mod_permission", NIL,
ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
-#ifdef ERTS_ENABLE_LOCK_CHECK
- erts_tsd_key_create(&has_code_write_permission,
- "erts_has_code_write_permission");
-#endif
+ erts_mtx_init(&code_stage_permission.lock,
+ "code_stage_permission", NIL,
+ ERTS_LOCK_FLAGS_PROPERTY_STATIC | ERTS_LOCK_FLAGS_CATEGORY_GENERIC);
+
#ifdef DEBUG
erts_tsd_key_create(&needs_code_barrier,
"erts_needs_code_barrier");
@@ -90,16 +96,17 @@ void erts_code_ix_init(void)
void erts_start_staging_code_ix(int num_new)
{
beam_catches_start_staging();
+ erts_fun_start_staging();
export_start_staging();
module_start_staging();
erts_start_staging_ranges(num_new);
CIX_TRACE("start");
}
-
void erts_end_staging_code_ix(void)
{
beam_catches_end_staging(1);
+ erts_fun_end_staging(1);
export_end_staging(1);
module_end_staging(1);
erts_end_staging_ranges(1);
@@ -123,122 +130,235 @@ void erts_commit_staging_code_ix(void)
void erts_abort_staging_code_ix(void)
{
beam_catches_end_staging(0);
+ erts_fun_end_staging(0);
export_end_staging(0);
module_end_staging(0);
erts_end_staging_ranges(0);
CIX_TRACE("abort");
}
-static int try_seize_cwp(Process* c_p,
- void (*aux_func)(void *),
- void *aux_arg);
-
#if defined(DEBUG) || defined(ADDRESS_SANITIZER)
# define CWP_DBG_FORCE_TRAP
#endif
-/*
- * Calller _must_ yield if we return 0
- */
-int erts_try_seize_code_write_permission(Process* c_p)
-{
- ASSERT(c_p != NULL);
-
-#ifdef CWP_DBG_FORCE_TRAP
- if (!(c_p->flags & F_DBG_FORCED_TRAP)) {
- c_p->flags |= F_DBG_FORCED_TRAP;
- return 0;
- } else {
- /* back from forced trap */
- c_p->flags &= ~F_DBG_FORCED_TRAP;
- }
+#ifdef ERTS_ENABLE_LOCK_CHECK
+static int has_code_permission(struct code_permission *lock);
#endif
- return try_seize_cwp(c_p, NULL, NULL);
-}
-
-int erts_try_seize_code_write_permission_aux(void (*aux_func)(void *),
- void *aux_arg)
-{
- ASSERT(aux_func != NULL);
- return try_seize_cwp(NULL, aux_func, aux_arg);
-}
-
-static int try_seize_cwp(Process* c_p,
- void (*aux_func)(void *),
- void *aux_arg)
+static int try_seize_code_permission(struct code_permission *perm,
+ Process* c_p,
+ void (*aux_func)(void *),
+ void *aux_arg)
{
int success;
- ASSERT(!erts_thr_progress_is_blocking()); /* to avoid deadlock */
- erts_mtx_lock(&code_write_permission_mtx);
- success = !code_writing_seized;
+ ASSERT(!erts_thr_progress_is_blocking()); /* To avoid deadlock */
+
+ erts_mtx_lock(&perm->lock);
+ success = !perm->seized;
+
if (success) {
- code_writing_seized = 1;
- code_writing_process = c_p;
-#ifdef ERTS_ENABLE_LOCK_CHECK
- erts_tsd_set(has_code_write_permission, (void *) 1);
-#endif
- }
- else { /* Already locked */
- struct code_write_queue_item* qitem;
+ if (c_p == NULL) {
+ ASSERT(aux_func);
+ perm->scheduler = erts_get_scheduler_data();
+ }
+
+ perm->owner = c_p;
+ perm->seized = 1;
+ } else { /* Already locked */
+ struct code_permission_queue_item* qitem;
+
qitem = erts_alloc(ERTS_ALC_T_CODE_IX_LOCK_Q, sizeof(*qitem));
if (c_p) {
- ASSERT(code_writing_process != c_p);
+ ERTS_LC_ASSERT(perm->owner != c_p);
+
qitem->p = c_p;
qitem->aux_func = NULL;
qitem->aux_arg = NULL;
erts_proc_inc_refc(c_p);
erts_suspend(c_p, ERTS_PROC_LOCK_MAIN, NULL);
- }
- else {
+ } else {
qitem->p = NULL;
qitem->aux_func = aux_func;
qitem->aux_arg = aux_arg;
}
- qitem->next = code_write_queue;
- code_write_queue = qitem;
+
+ qitem->next = perm->queue;
+ perm->queue = qitem;
}
- erts_mtx_unlock(&code_write_permission_mtx);
- return success;
+
+ erts_mtx_unlock(&perm->lock);
+
+ return success;
}
-void erts_release_code_write_permission(void)
-{
- erts_mtx_lock(&code_write_permission_mtx);
- ERTS_LC_ASSERT(erts_has_code_write_permission());
- while (code_write_queue != NULL) { /* unleash the entire herd */
- struct code_write_queue_item* qitem = code_write_queue;
+static void release_code_permission(struct code_permission *perm) {
+ ERTS_LC_ASSERT(has_code_permission(perm));
+
+ erts_mtx_lock(&perm->lock);
+
+ /* Unleash the entire herd */
+ while (perm->queue != NULL) {
+ struct code_permission_queue_item* qitem = perm->queue;
+
if (qitem->p) {
erts_proc_lock(qitem->p, ERTS_PROC_LOCK_STATUS);
+
if (!ERTS_PROC_IS_EXITING(qitem->p)) {
erts_resume(qitem->p, ERTS_PROC_LOCK_STATUS);
}
+
erts_proc_unlock(qitem->p, ERTS_PROC_LOCK_STATUS);
erts_proc_dec_refc(qitem->p);
- }
- else { /* aux work*/
+ } else { /* aux work */
ErtsSchedulerData *esdp = erts_get_scheduler_data();
ASSERT(esdp && esdp->type == ERTS_SCHED_NORMAL);
erts_schedule_misc_aux_work((int) esdp->no,
qitem->aux_func,
qitem->aux_arg);
}
- code_write_queue = qitem->next;
- erts_free(ERTS_ALC_T_CODE_IX_LOCK_Q, qitem);
+
+ perm->queue = qitem->next;
+ erts_free(ERTS_ALC_T_CODE_IX_LOCK_Q, qitem);
+ }
+
+ perm->scheduler = NULL;
+ perm->owner = NULL;
+ perm->seized = 0;
+
+ erts_mtx_unlock(&perm->lock);
+}
+
+int erts_try_seize_code_mod_permission_aux(void (*aux_func)(void *),
+ void *aux_arg)
+{
+ ASSERT(aux_func != NULL);
+ return try_seize_code_permission(&code_mod_permission, NULL,
+ aux_func, aux_arg);
+}
+
+int erts_try_seize_code_mod_permission(Process* c_p)
+{
+ ASSERT(c_p != NULL);
+
+#ifdef CWP_DBG_FORCE_TRAP
+ if (!(c_p->flags & F_DBG_FORCED_TRAP)) {
+ c_p->flags |= F_DBG_FORCED_TRAP;
+ return 0;
+ } else {
+ /* back from forced trap */
+ c_p->flags &= ~F_DBG_FORCED_TRAP;
+ }
+#endif
+
+ return try_seize_code_permission(&code_mod_permission, c_p, NULL, NULL);
+}
+
+void erts_release_code_mod_permission(void)
+{
+ release_code_permission(&code_mod_permission);
+}
+
+int erts_try_seize_code_stage_permission(Process* c_p)
+{
+ ASSERT(c_p != NULL);
+
+#ifdef CWP_DBG_FORCE_TRAP
+ if (!(c_p->flags & F_DBG_FORCED_TRAP)) {
+ c_p->flags |= F_DBG_FORCED_TRAP;
+ return 0;
+ } else {
+ /* back from forced trap */
+ c_p->flags &= ~F_DBG_FORCED_TRAP;
+ }
+#endif
+
+ return try_seize_code_permission(&code_stage_permission, c_p, NULL, NULL);
+}
+
+void erts_release_code_stage_permission(void) {
+ release_code_permission(&code_stage_permission);
+}
+
+int erts_try_seize_code_load_permission(Process* c_p) {
+ ASSERT(c_p != NULL);
+
+#ifdef CWP_DBG_FORCE_TRAP
+ if (!(c_p->flags & F_DBG_FORCED_TRAP)) {
+ c_p->flags |= F_DBG_FORCED_TRAP;
+ return 0;
+ } else {
+ /* back from forced trap */
+ c_p->flags &= ~F_DBG_FORCED_TRAP;
}
- code_writing_seized = 0;
- code_writing_process = NULL;
-#ifdef ERTS_ENABLE_LOCK_CHECK
- erts_tsd_set(has_code_write_permission, (void *) 0);
#endif
- erts_mtx_unlock(&code_write_permission_mtx);
+
+ if (try_seize_code_permission(&code_stage_permission, c_p, NULL, NULL)) {
+ if (try_seize_code_permission(&code_mod_permission, c_p, NULL, NULL)) {
+ return 1;
+ }
+
+ erts_release_code_stage_permission();
+ }
+
+ return 0;
+}
+
+void erts_release_code_load_permission(void) {
+ erts_release_code_mod_permission();
+ erts_release_code_stage_permission();
}
#ifdef ERTS_ENABLE_LOCK_CHECK
-int erts_has_code_write_permission(void)
+static int has_code_permission(struct code_permission *perm)
{
- return code_writing_seized && erts_tsd_get(has_code_write_permission);
+ const ErtsSchedulerData *esdp = erts_get_scheduler_data();
+
+ if (esdp && esdp->type == ERTS_SCHED_NORMAL) {
+ int res;
+
+ erts_mtx_lock(&perm->lock);
+
+ res = perm->seized;
+
+ if (esdp->current_process != NULL) {
+ /* If we're running a process, it has to match the owner of the
+ * permission. We don't care about which scheduler we are running
+ * on in order to support holding permissions when yielding (such
+ * as in code purging). */
+ res &= perm->owner == esdp->current_process;
+ } else {
+ /* If we're running an aux job, we crudely assume that this current
+ * job was started by the owner if there is one, and therefore has
+ * permission.
+ *
+ * If we don't have an owner, we assume that we have permission if
+ * we're running on the same scheduler that started the job.
+ *
+ * This is very blunt and only catches _some_ cases where we lack
+ * lack permission, but at least it's better than the old method of
+ * using thread-specific-data. */
+ res &= perm->owner || perm->scheduler == esdp;
+ }
+
+ erts_mtx_unlock(&perm->lock);
+
+ return res;
+ }
+
+ return 0;
+}
+
+int erts_has_code_load_permission() {
+ return erts_has_code_stage_permission() && erts_has_code_mod_permission();
+}
+
+int erts_has_code_stage_permission() {
+ return has_code_permission(&code_stage_permission);
+}
+
+int erts_has_code_mod_permission() {
+ return has_code_permission(&code_mod_permission);
}
#endif
@@ -278,29 +398,7 @@ static void issue_instruction_barrier(void *barrier_) {
ERTS_THR_INSTRUCTION_BARRIER;
if (erts_refc_dectest(&barrier->pending_schedulers, 0) == 0) {
-# ifdef ERTS_ENABLE_LOCK_CHECK
- ErtsSchedulerData *initial_esdp = (ErtsSchedulerData*)barrier->esdp;
-
- /* HACK: Dodges a broken lock-checking assertion, which requires that
- * the _thread_ that takes a code permission is also the one that
- * releases it.
- *
- * This has never held since processes can migrate between schedulers,
- * but we have to roll with the punches. Commit the code on the
- * scheduler that called `erts_schedule_code_barrier` in the hopes that
- * it's the right one.
- *
- * This has been fixed in `master`. */
- if (initial_esdp && initial_esdp != erts_get_scheduler_data()) {
- erts_schedule_misc_aux_work(initial_esdp->no,
- schedule_code_barrier_later_op,
- barrier);
- } else {
- schedule_code_barrier_later_op(barrier);
- }
-# else
schedule_code_barrier_later_op(barrier);
-# endif
}
}
#endif
@@ -320,7 +418,6 @@ void erts_schedule_code_barrier_cleanup(ErtsCodeBarrier *barrier,
erts_debug_unrequire_code_barrier();
#endif
- barrier->esdp = erts_get_scheduler_data();
barrier->later_function = later_function;
barrier->later_data = later_data;
barrier->size = size;
@@ -377,7 +474,7 @@ static void schedule_blocking_code_barriers(void *ignored) {
}
#endif
-void erts_blocking_code_barrier()
+void erts_blocking_code_barrier(void)
{
#ifdef DEBUG
erts_debug_unrequire_code_barrier();
@@ -390,7 +487,7 @@ void erts_blocking_code_barrier()
#endif
}
-void erts_code_ix_finalize_wait() {
+void erts_code_ix_finalize_wait(void) {
#ifdef CODE_IX_ISSUE_INSTRUCTION_BARRIERS
if (erts_atomic32_read_nob(&outstanding_blocking_code_barriers) != 0) {
ERTS_THR_INSTRUCTION_BARRIER;