/* * %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 "global.h" #include "erl_lock_count.h" #include "erl_thr_progress.h" #include "erl_node_tables.h" #include "erl_alloc_util.h" #include "erl_check_io.h" #include "erl_poll.h" #include "erl_db.h" #define LCNT_MAX_CARRIER_ENTRIES 255 /* - Locals that are shared with the header implementation - */ #ifdef DEBUG int lcnt_initialization_completed__; #endif erts_lock_flags_t lcnt_category_mask__; 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 - */ typedef struct lcnt_static_lock_ref_ { erts_lcnt_ref_t *reference; erts_lock_flags_t flags; const char *name; Eterm id; struct lcnt_static_lock_ref_ *next; } lcnt_static_lock_ref_t; static ethr_atomic_t lcnt_static_lock_registry; 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; static int lcnt_preserve_info; /* 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; } /* - 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); } void lcnt_deallocate_carrier__(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(lcnt_preserve_info) { 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) { 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); ASSERT(lcnt_initialization_completed__); carrier_size = sizeof(erts_lcnt_lock_info_carrier_t) + sizeof(erts_lcnt_lock_info_t) * entry_count; result = (erts_lcnt_lock_info_carrier_t*)erts_alloc(ERTS_ALC_T_LCNT_CARRIER, carrier_size); result->entry_count = entry_count; ethr_atomic_init(&result->ref_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; #ifdef DEBUG int i; /* Verify that all locks share the same categories/static property; all * other flags are fair game. */ for(i = 1; i < carrier->entry_count; i++) { const erts_lock_flags_t SIGNIFICANT_DIFF_MASK = ERTS_LOCK_FLAGS_MASK_CATEGORY | ERTS_LOCK_FLAGS_PROPERTY_STATIC; erts_lcnt_lock_info_t *previous, *current; previous = &carrier->entries[i - 1]; current = &carrier->entries[i]; ASSERT(!((previous->flags ^ current->flags) & SIGNIFICANT_DIFF_MASK)); } #endif 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 lcnt_deallocate_carrier__(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); } } /* - Static lock registry - * * Since static locks can be trusted to never disappear, we can track them * pretty cheaply and won't need to bother writing an "erts_lcnt_update_xx" * variant. */ static void lcnt_init_static_lock_registry(void) { ethr_atomic_init(&lcnt_static_lock_registry, (ethr_sint_t)NULL); } static void lcnt_update_static_locks(void) { lcnt_static_lock_ref_t *iterator = (lcnt_static_lock_ref_t*)ethr_atomic_read(&lcnt_static_lock_registry); while(iterator != NULL) { if(!erts_lcnt_check_enabled(iterator->flags)) { erts_lcnt_uninstall(iterator->reference); } else if(!erts_lcnt_check_ref_installed(iterator->reference)) { erts_lcnt_lock_info_carrier_t *carrier = erts_lcnt_create_lock_info_carrier(1); erts_lcnt_init_lock_info_idx(carrier, 0, iterator->name, iterator->id, iterator->flags); erts_lcnt_install(iterator->reference, carrier); } iterator = iterator->next; } } void lcnt_register_static_lock__(erts_lcnt_ref_t *reference, const char *name, Eterm id, erts_lock_flags_t flags) { lcnt_static_lock_ref_t *lock = malloc(sizeof(lcnt_static_lock_ref_t)); int retry_insertion; ASSERT(flags & ERTS_LOCK_FLAGS_PROPERTY_STATIC); lock->reference = reference; lock->flags = flags; lock->name = name; lock->id = id; do { ethr_sint_t swapped_head; lock->next = (lcnt_static_lock_ref_t*)ethr_atomic_read(&lcnt_static_lock_registry); swapped_head = ethr_atomic_cmpxchg_acqb( &lcnt_static_lock_registry, (ethr_sint_t)lock, (ethr_sint_t)lock->next); retry_insertion = (swapped_head != (ethr_sint_t)lock->next); } while(retry_insertion); } /* - 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); lcnt_init_static_lock_registry(); } 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); #ifdef DEBUG /* It's safe to use erts_alloc and thread progress past this point. */ lcnt_initialization_completed__ = 1; #endif } void erts_lcnt_post_startup(void) { /* Default to capturing everything to match the behavior of the old lock * counter build. */ erts_lcnt_set_category_mask(ERTS_LOCK_FLAGS_MASK_CATEGORY); } 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); } } erts_lock_flags_t erts_lcnt_get_category_mask() { return lcnt_category_mask__; } void erts_lcnt_set_category_mask(erts_lock_flags_t mask) { erts_lock_flags_t changed_categories; ASSERT(!(mask & ~ERTS_LOCK_FLAGS_MASK_CATEGORY)); ASSERT(lcnt_initialization_completed__); changed_categories = (lcnt_category_mask__ ^ mask); lcnt_category_mask__ = mask; if(changed_categories) { lcnt_update_static_locks(); } if(changed_categories & ERTS_LOCK_FLAGS_CATEGORY_DISTRIBUTION) { erts_lcnt_update_distribution_locks(mask & ERTS_LOCK_FLAGS_CATEGORY_DISTRIBUTION); } if(changed_categories & ERTS_LOCK_FLAGS_CATEGORY_ALLOCATOR) { erts_lcnt_update_allocator_locks(mask & ERTS_LOCK_FLAGS_CATEGORY_ALLOCATOR); } if(changed_categories & ERTS_LOCK_FLAGS_CATEGORY_PROCESS) { erts_lcnt_update_process_locks(mask & ERTS_LOCK_FLAGS_CATEGORY_PROCESS); } if(changed_categories & ERTS_LOCK_FLAGS_CATEGORY_IO) { erts_lcnt_update_cio_locks(mask & ERTS_LOCK_FLAGS_CATEGORY_IO); erts_lcnt_update_driver_locks(mask & ERTS_LOCK_FLAGS_CATEGORY_IO); erts_lcnt_update_port_locks(mask & ERTS_LOCK_FLAGS_CATEGORY_IO); } if(changed_categories & ERTS_LOCK_FLAGS_CATEGORY_DB) { erts_lcnt_update_db_locks(mask & ERTS_LOCK_FLAGS_CATEGORY_DB); } } void erts_lcnt_set_preserve_info(int enable) { lcnt_preserve_info = enable; } int erts_lcnt_get_preserve_info() { return lcnt_preserve_info; } 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; } 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 */