/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2008-2017. 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%
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef ERTS_ENABLE_LOCK_COUNT
#include "sys.h"
#include "erl_lock_count.h"
#include "erl_thr_progress.h"
#define LCNT_MAX_CARRIER_ENTRIES 255
/* - Exported global - */
Uint16 erts_lcnt_rt_options = ERTS_LCNT_OPT_PROCLOCK | ERTS_LCNT_OPT_LOCATION;
/* - Locals that are shared with the header implementation - */
int lcnt_initialization_completed__ = 0;
ethr_tsd_key lcnt_thr_data_key__;
const int lcnt_log2_tab64__[64] = {
63, 0, 58, 1, 59, 47, 53, 2,
60, 39, 48, 27, 54, 33, 42, 3,
61, 51, 37, 40, 49, 18, 28, 20,
55, 30, 34, 11, 43, 14, 22, 4,
62, 57, 46, 52, 38, 26, 32, 41,
50, 36, 17, 19, 29, 10, 13, 21,
56, 45, 25, 31, 35, 16, 9, 12,
44, 24, 15, 8, 23, 7, 6, 5};
/* - Local variables - */
static erts_lcnt_lock_info_list_t lcnt_current_lock_list;
static erts_lcnt_lock_info_list_t lcnt_deleted_lock_list;
static erts_lcnt_time_t lcnt_timer_start;
/* local functions */
static void lcnt_clear_stats(erts_lcnt_lock_info_t *info) {
size_t i;
for(i = 0; i < ERTS_LCNT_MAX_LOCK_LOCATIONS; i++) {
erts_lcnt_lock_stats_t *stats = &info->location_stats[i];
sys_memzero(&stats->wait_time_histogram, sizeof(stats->wait_time_histogram));
stats->total_time_waited.s = 0;
stats->total_time_waited.ns = 0;
stats->times_waited = 0;
stats->file = NULL;
stats->line = 0;
ethr_atomic_set(&stats->attempts, 0);
ethr_atomic_set(&stats->collisions, 0);
}
info->location_count = 1;
}
static lcnt_thread_data_t__ *lcnt_thread_data_alloc(void) {
lcnt_thread_data_t__ *eltd =
(lcnt_thread_data_t__*)malloc(sizeof(lcnt_thread_data_t__));
if(!eltd) {
ERTS_INTERNAL_ERROR("Failed to allocate lcnt thread data.");
}
eltd->timer_set = 0;
eltd->lock_in_conflict = 0;
return eltd;
}
/* debug */
#if 0
static char* lock_opt(Uint16 flag) {
if ((flag & ERTS_LCNT_LO_WRITE) && (flag & ERTS_LCNT_LO_READ)) return "rw";
if (flag & ERTS_LCNT_LO_READ ) return "r ";
if (flag & ERTS_LCNT_LO_WRITE) return " w";
return "--";
}
static void print_lock_x(erts_lcnt_lock_info_t *info, Uint16 flag, char *action) {
ethr_sint_t w_state, r_state;
char *type;
if (strcmp(info->name, "run_queue") != 0) return;
type = erts_lcnt_lock_type(info->flag);
r_state = ethr_atomic_read(&info->r_state);
w_state = ethr_atomic_read(&info->w_state);
if (info->flag & flag) {
erts_fprintf(stderr,"%10s [%24s] [r/w state %4ld/%4ld] %2s id %T\r\n",
action,
info->name,
r_state,
w_state,
type,
info->id);
}
}
#endif
/* - List operations -
*
* Info entries are kept in a doubly linked list where each entry is locked
* with its neighbors rather than a global lock. Deletion is rather quick, but
* insertion is still serial since the head becomes a de facto global lock.
*
* We rely on ad-hoc spinlocks to avoid "recursing" into this module. */
#define LCNT_SPINLOCK_YIELD_ITERATIONS 50
#define LCNT_SPINLOCK_HELPER_INIT \
Uint failed_spin_count = 0;
#define LCNT_SPINLOCK_HELPER_YIELD \
do { \
failed_spin_count++; \
if(!(failed_spin_count % LCNT_SPINLOCK_YIELD_ITERATIONS)) { \
erts_thr_yield(); \
} else { \
ERTS_SPIN_BODY; \
} \
} while(0)
static void lcnt_unlock_list_entry(erts_lcnt_lock_info_t *info) {
ethr_atomic32_set_relb(&info->lock, 0);
}
static int lcnt_try_lock_list_entry(erts_lcnt_lock_info_t *info) {
return ethr_atomic32_cmpxchg_acqb(&info->lock, 1, 0) == 0;
}
static void lcnt_lock_list_entry(erts_lcnt_lock_info_t *info) {
LCNT_SPINLOCK_HELPER_INIT;
while(!lcnt_try_lock_list_entry(info)) {
LCNT_SPINLOCK_HELPER_YIELD;
}
}
static void lcnt_lock_list_entry_with_neighbors(erts_lcnt_lock_info_t *info) {
LCNT_SPINLOCK_HELPER_INIT;
for(;;) {
if(!lcnt_try_lock_list_entry(info))
goto retry_after_entry_failed;
if(!lcnt_try_lock_list_entry(info->next))
goto retry_after_next_failed;
if(!lcnt_try_lock_list_entry(info->prev))
goto retry_after_prev_failed;
return;
retry_after_prev_failed:
lcnt_unlock_list_entry(info->next);
retry_after_next_failed:
lcnt_unlock_list_entry(info);
retry_after_entry_failed:
LCNT_SPINLOCK_HELPER_YIELD;
}
}
static void lcnt_unlock_list_entry_with_neighbors(erts_lcnt_lock_info_t *info) {
lcnt_unlock_list_entry(info->prev);
lcnt_unlock_list_entry(info->next);
lcnt_unlock_list_entry(info);
}
static void lcnt_insert_list_entry(erts_lcnt_lock_info_list_t *list, erts_lcnt_lock_info_t *info) {
erts_lcnt_lock_info_t *next, *prev;
prev = &list->head;
lcnt_lock_list_entry(prev);
next = prev->next;
lcnt_lock_list_entry(next);
info->next = next;
info->prev = prev;
prev->next = info;
next->prev = info;
lcnt_unlock_list_entry(next);
lcnt_unlock_list_entry(prev);
}
static void lcnt_insert_list_carrier(erts_lcnt_lock_info_list_t *list,
erts_lcnt_lock_info_carrier_t *carrier) {
erts_lcnt_lock_info_t *next, *prev;
size_t i;
for(i = 0; i < carrier->entry_count; i++) {
erts_lcnt_lock_info_t *info = &carrier->entries[i];
info->prev = &carrier->entries[i - 1];
info->next = &carrier->entries[i + 1];
}
prev = &list->head;
lcnt_lock_list_entry(prev);
next = prev->next;
lcnt_lock_list_entry(next);
next->prev = &carrier->entries[carrier->entry_count - 1];
carrier->entries[carrier->entry_count - 1].next = next;
prev->next = &carrier->entries[0];
carrier->entries[0].prev = prev;
lcnt_unlock_list_entry(next);
lcnt_unlock_list_entry(prev);
}
static void lcnt_init_list(erts_lcnt_lock_info_list_t *list) {
/* Ensure that ref_count operations explode when touching the sentinels in
* DEBUG mode. */
ethr_atomic_init(&(list->head.ref_count), -1);
ethr_atomic_init(&(list->tail.ref_count), -1);
ethr_atomic32_init(&(list->head.lock), 0);
(list->head).next = &list->tail;
(list->head).prev = &list->tail;
ethr_atomic32_init(&(list->tail.lock), 0);
(list->tail).next = &list->head;
(list->tail).prev = &list->head;
}
/* - Carrier operations - */
int lcnt_thr_progress_unmanaged_delay__(void) {
return erts_thr_progress_unmanaged_delay();
}
void lcnt_thr_progress_unmanaged_continue__(int handle) {
return erts_thr_progress_unmanaged_continue(handle);
}
static void lcnt_deallocate_carrier_malloc(erts_lcnt_lock_info_carrier_t *carrier) {
ASSERT(ethr_atomic_read(&carrier->ref_count) == 0);
free(carrier);
}
static void lcnt_deallocate_carrier_erts(erts_lcnt_lock_info_carrier_t *carrier) {
ASSERT(ethr_atomic_read(&carrier->ref_count) == 0);
erts_free(ERTS_ALC_T_LCNT_CARRIER, (void*)carrier);
}
static void lcnt_thr_prg_cleanup_carrier(void *data) {
erts_lcnt_lock_info_carrier_t *carrier = data;
size_t entry_count, i;
/* carrier->entry_count will be replaced with garbage if it's deallocated
* on the final iteration, so we'll tuck it away to get a clean exit. */
entry_count = carrier->entry_count;
for(i = 0; i < entry_count; i++) {
ASSERT(ethr_atomic_read(&carrier->ref_count) >= (entry_count - i));
erts_lcnt_release_lock_info(&carrier->entries[i]);
}
}
static void lcnt_schedule_carrier_cleanup(void *data) {
ErtsSchedulerData *esdp = erts_get_scheduler_data();
/* We can't issue cleanup jobs on anything other than normal schedulers, so
* we move to the first scheduler if required. */
if(!esdp || esdp->type != ERTS_SCHED_NORMAL) {
erts_schedule_misc_aux_work(1, &lcnt_schedule_carrier_cleanup, data);
} else {
erts_lcnt_lock_info_carrier_t *carrier = data;
size_t carrier_size;
carrier_size = sizeof(erts_lcnt_lock_info_carrier_t) +
sizeof(erts_lcnt_lock_info_t) * carrier->entry_count;
erts_schedule_thr_prgr_later_cleanup_op(&lcnt_thr_prg_cleanup_carrier,
data, (ErtsThrPrgrLaterOp*)&carrier->release_entries, carrier_size);
}
}
static void lcnt_info_deallocate(erts_lcnt_lock_info_t *info) {
lcnt_release_carrier__(info->carrier);
}
static void lcnt_info_dispose(erts_lcnt_lock_info_t *info) {
ASSERT(ethr_atomic_read(&info->ref_count) == 0);
if(erts_lcnt_rt_options & ERTS_LCNT_OPT_COPYSAVE) {
ethr_atomic_set(&info->ref_count, 1);
/* Move straight to deallocation the next time around. */
info->dispose = &lcnt_info_deallocate;
lcnt_insert_list_entry(&lcnt_deleted_lock_list, info);
} else {
lcnt_info_deallocate(info);
}
}
static void lcnt_lock_info_init_helper(erts_lcnt_lock_info_t *info) {
#ifdef DEBUG
ethr_atomic_init(&info->flowstate, 0);
#endif
ethr_atomic_init(&info->ref_count, 1);
ethr_atomic32_init(&info->lock, 0);
ethr_atomic_init(&info->r_state, 0);
ethr_atomic_init(&info->w_state, 0);
info->dispose = &lcnt_info_dispose;
lcnt_clear_stats(info);
}
erts_lcnt_lock_info_carrier_t *erts_lcnt_create_lock_info_carrier(int entry_count) {
erts_lcnt_lock_info_carrier_t *result;
size_t carrier_size, i;
ASSERT(entry_count > 0 && entry_count <= LCNT_MAX_CARRIER_ENTRIES);
carrier_size = sizeof(erts_lcnt_lock_info_carrier_t) +
sizeof(erts_lcnt_lock_info_t) * entry_count;
if(lcnt_initialization_completed__) {
result = (erts_lcnt_lock_info_carrier_t*)erts_alloc(ERTS_ALC_T_LCNT_CARRIER, carrier_size);
result->deallocate = &lcnt_deallocate_carrier_erts;
} else {
result = (erts_lcnt_lock_info_carrier_t*)malloc(carrier_size);
result->deallocate = &lcnt_deallocate_carrier_malloc;
}
ethr_atomic_init(&result->ref_count, entry_count);
result->entry_count = entry_count;
for(i = 0; i < entry_count; i++) {
erts_lcnt_lock_info_t *info = &result->entries[i];
lcnt_lock_info_init_helper(info);
info->carrier = result;
}
return result;
}
void erts_lcnt_install(erts_lcnt_ref_t *ref, erts_lcnt_lock_info_carrier_t *carrier) {
ethr_sint_t swapped_carrier;
swapped_carrier = ethr_atomic_cmpxchg_mb(ref, (ethr_sint_t)carrier, (ethr_sint_t)NULL);
if(swapped_carrier != (ethr_sint_t)NULL) {
#ifdef DEBUG
ASSERT(ethr_atomic_read(&carrier->ref_count) == carrier->entry_count);
ethr_atomic_set(&carrier->ref_count, 0);
#endif
carrier->deallocate(carrier);
} else {
lcnt_insert_list_carrier(&lcnt_current_lock_list, carrier);
}
}
void erts_lcnt_uninstall(erts_lcnt_ref_t *ref) {
ethr_sint_t previous_carrier, swapped_carrier;
previous_carrier = ethr_atomic_read(ref);
swapped_carrier = ethr_atomic_cmpxchg_mb(ref, (ethr_sint_t)NULL, previous_carrier);
if(previous_carrier && previous_carrier == swapped_carrier) {
lcnt_schedule_carrier_cleanup((void*)previous_carrier);
}
}
/* - Initialization - */
void erts_lcnt_pre_thr_init() {
/* Ensure that the dependency hack mentioned in the header doesn't
* explode at runtime. */
ERTS_CT_ASSERT(sizeof(LcntThrPrgrLaterOp) >= sizeof(ErtsThrPrgrLaterOp));
ERTS_CT_ASSERT(ERTS_THR_PRGR_DHANDLE_MANAGED ==
(ErtsThrPrgrDelayHandle)LCNT_THR_PRGR_DHANDLE_MANAGED);
lcnt_init_list(&lcnt_current_lock_list);
lcnt_init_list(&lcnt_deleted_lock_list);
}
void erts_lcnt_post_thr_init() {
/* ASSUMPTION: this is safe since it runs prior to the creation of other
* threads (Directly after ethread init). */
ethr_tsd_key_create(&lcnt_thr_data_key__, "lcnt_data");
erts_lcnt_thread_setup();
}
void erts_lcnt_late_init() {
/* Set start timer and zero all statistics */
erts_lcnt_clear_counters();
erts_thr_install_exit_handler(erts_lcnt_thread_exit_handler);
/* It's safe to use erts_alloc and thread progress past this point. */
lcnt_initialization_completed__ = 1;
}
void erts_lcnt_thread_setup() {
lcnt_thread_data_t__ *eltd = lcnt_thread_data_alloc();
ASSERT(eltd);
ethr_tsd_set(lcnt_thr_data_key__, eltd);
}
void erts_lcnt_thread_exit_handler() {
lcnt_thread_data_t__ *eltd = lcnt_get_thread_data__();
if (eltd) {
free(eltd);
}
}
/* - BIF interface - */
void erts_lcnt_retain_lock_info(erts_lcnt_lock_info_t *info) {
#ifdef DEBUG
ASSERT(ethr_atomic_inc_read_acqb(&info->ref_count) >= 2);
#else
ethr_atomic_inc_acqb(&info->ref_count);
#endif
}
void erts_lcnt_release_lock_info(erts_lcnt_lock_info_t *info) {
ethr_sint_t count;
/* We need to acquire the lock before decrementing ref_count to avoid
* racing with list iteration; there's a short window between reading the
* reference to info and increasing its ref_count. */
lcnt_lock_list_entry_with_neighbors(info);
count = ethr_atomic_dec_read(&info->ref_count);
ASSERT(count >= 0);
if(count > 0) {
lcnt_unlock_list_entry_with_neighbors(info);
} else {
(info->next)->prev = info->prev;
(info->prev)->next = info->next;
lcnt_unlock_list_entry_with_neighbors(info);
info->dispose(info);
}
}
Uint16 erts_lcnt_set_rt_opt(Uint16 opt) {
Uint16 prev;
prev = (erts_lcnt_rt_options & opt);
erts_lcnt_rt_options |= opt;
return prev;
}
Uint16 erts_lcnt_clear_rt_opt(Uint16 opt) {
Uint16 prev;
prev = (erts_lcnt_rt_options & opt);
erts_lcnt_rt_options &= ~opt;
return prev;
}
void erts_lcnt_clear_counters(void) {
erts_lcnt_lock_info_t *iterator;
lcnt_time__(&lcnt_timer_start);
iterator = NULL;
while(erts_lcnt_iterate_list(&lcnt_current_lock_list, &iterator)) {
lcnt_clear_stats(iterator);
}
iterator = NULL;
while(erts_lcnt_iterate_list(&lcnt_deleted_lock_list, &iterator)) {
erts_lcnt_release_lock_info(iterator);
}
}
erts_lcnt_data_t erts_lcnt_get_data(void) {
erts_lcnt_time_t timer_stop;
erts_lcnt_data_t result;
lcnt_time__(&timer_stop);
result.timer_start = lcnt_timer_start;
result.current_locks = &lcnt_current_lock_list;
result.deleted_locks = &lcnt_deleted_lock_list;
lcnt_time_diff__(&result.duration, &timer_stop, &result.timer_start);
return result;
}
const char *erts_lcnt_lock_type(Uint16 type) {
switch(type & ERTS_LCNT_LT_ALL) {
case ERTS_LCNT_LT_SPINLOCK: return "spinlock";
case ERTS_LCNT_LT_RWSPINLOCK: return "rw_spinlock";
case ERTS_LCNT_LT_MUTEX: return "mutex";
case ERTS_LCNT_LT_RWMUTEX: return "rw_mutex";
case ERTS_LCNT_LT_PROCLOCK: return "proclock";
default: return "";
}
}
int erts_lcnt_iterate_list(erts_lcnt_lock_info_list_t *list, erts_lcnt_lock_info_t **iterator) {
erts_lcnt_lock_info_t *current, *next;
current = *iterator ? *iterator : &list->head;
ASSERT(current != &list->tail);
lcnt_lock_list_entry(current);
next = current->next;
if(next != &list->tail) {
erts_lcnt_retain_lock_info(next);
}
lcnt_unlock_list_entry(current);
if(current != &list->head) {
erts_lcnt_release_lock_info(current);
}
*iterator = next;
return next != &list->tail;
}
#endif /* #ifdef ERTS_ENABLE_LOCK_COUNT */