/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 2005-2018. 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%
*/
/*
* Description: A lock checker that checks that each thread acquires
* locks according to a predefined global lock order. The
* global lock order is used to prevent deadlocks. If the
* lock order is violated, an error message is printed
* and the emulator aborts. The lock checker is only
* intended to be enabled when debugging.
*
* Author: Rickard Green
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
/* Needed for VxWorks va_arg */
#include "sys.h"
#ifdef ERTS_ENABLE_LOCK_CHECK
#include "erl_lock_check.h"
#include "erl_term.h"
#include "erl_threads.h"
#include "erl_atom_table.h"
#include "erl_utils.h"
typedef struct {
char *name;
char *internal_order;
} erts_lc_lock_order_t;
/*
* Global lock order for locks in the emulator.
*
* Locks early (low indexes) in the 'erts_lock_order' array should be
* locked before locks late (high indexes) in the array. Each lock has
* a name which is set on initialization. If multiple locks with the
* same name are used, either an immediate Erlang term (e.g. internal
* pid) or the address of the lock is used for internal lock order.
* The immediate Erlang term used for internal lock order is also set
* on initialization. Locks with small immediate Erlang terms should
* be locked before locks with large immediate Erlang terms, and
* locks with small addresses should be locked before locks with
* large addresses. The immediate terms and adresses (boxed pointers)
* are compared as unsigned integers not as Erlang terms.
*
* Once a spinlock or rw(spin)lock has been locked, the thread is not
* allowed to lock mutexes, rwmutexes or process locks until all
* spinlocks and rwlocks have been unlocked. This restriction is not
* reflected by the lock order below, but the lock checker will still
* check for violations of this restriction.
*/
static erts_lc_lock_order_t erts_lock_order[] = {
/*
* "Lock name" "Internal lock order
* description (NULL
* if only one lock use
* the lock name)"
*/
{ "NO LOCK", NULL },
{ "driver_lock", "driver_name" },
{ "port_lock", "port_id" },
{ "port_data_lock", "address" },
{ "reg_tab", NULL },
{ "proc_main", "pid" },
{ "old_code", "address" },
#ifdef HIPE
{ "hipe_mfait_lock", NULL },
#endif
{ "nodes_monitors", NULL },
{ "meta_name_tab", "address" },
{ "db_tab", "address" },
{ "db_tab_fix", "address" },
{ "db_hash_slot", "address" },
{ "erl_db_catree_base_node", NULL },
{ "erl_db_catree_route_node", "index" },
{ "resource_monitors", "address" },
{ "driver_list", NULL },
{ "proc_msgq", "pid" },
{ "proc_btm", "pid" },
{ "dist_entry", "address" },
{ "dist_entry_links", "address" },
{ "update_persistent_term_permission", NULL },
{ "persistent_term_delete_permission", NULL },
{ "code_write_permission", NULL },
{ "purge_state", NULL },
{ "proc_status", "pid" },
{ "proc_trace", "pid" },
{ "node_table", NULL },
{ "dist_table", NULL },
{ "sys_tracers", NULL },
{ "export_tab", NULL },
{ "fun_tab", NULL },
{ "environ", NULL },
{ "release_literal_areas", NULL },
{ "drv_ev_state_grow", NULL, },
{ "drv_ev_state", "address" },
{ "safe_hash", "address" },
{ "state_prealloc", NULL },
{ "schdlr_sspnd", NULL },
{ "migration_info_update", NULL },
{ "run_queue", "address" },
{ "dirty_run_queue_sleep_list", "address" },
{ "dirty_gc_info", NULL },
{ "dirty_break_point_index", NULL },
{ "process_table", NULL },
{ "cpu_info", NULL },
{ "pollset", "address" },
#ifdef __WIN32__
{ "pollwaiter", "address" },
{ "break_waiter_lock", NULL },
#endif /* __WIN32__ */
{ "alcu_init_atoms", NULL },
{ "mseg_init_atoms", NULL },
{ "mmap_init_atoms", NULL },
{ "drv_tsd", NULL },
{ "async_enq_mtx", NULL },
{ "msacc_list_mutex", NULL },
{ "msacc_unmanaged_mutex", NULL },
{ "atom_tab", NULL },
{ "dist_entry_out_queue", "address" },
{ "port_sched_lock", "port_id" },
{ "sys_msg_q", NULL },
{ "tracer_mtx", NULL },
{ "port_table", NULL },
{ "magic_ref_table", "address" },
{ "mtrace_op", NULL },
{ "instr_x", NULL },
{ "instr", NULL },
{ "alcu_allocator", "index" },
{ "mseg", NULL },
{ "get_time", NULL },
{ "get_corrected_time", NULL },
{ "runtime", NULL },
{ "pix_lock", "address" },
{ "sched_stat", NULL },
{ "async_init_mtx", NULL },
#ifdef __WIN32__
#ifdef DEBUG
{ "save_ops_lock", NULL },
#endif
#endif
{ "mtrace_buf", NULL },
{ "os_monotonic_time", NULL },
{ "erts_alloc_hard_debug", NULL },
{ "hard_dbg_mseg", NULL },
{ "erts_mmap", NULL },
{ "sad", NULL}
};
#define ERTS_LOCK_ORDER_SIZE \
(sizeof(erts_lock_order)/sizeof(erts_lc_lock_order_t))
#define LOCK_IS_TYPE_ORDER_VIOLATION(LCK_FLG, LCKD_FLG) \
(((LCKD_FLG) & ERTS_LOCK_FLAGS_MASK_TYPE) == ERTS_LOCK_FLAGS_TYPE_SPINLOCK \
&& \
((LCK_FLG) & ERTS_LOCK_FLAGS_MASK_TYPE) != ERTS_LOCK_FLAGS_TYPE_SPINLOCK)
static __decl_noreturn void __noreturn lc_abort(void);
static const char *rw_op_str(erts_lock_options_t options)
{
if(options == ERTS_LOCK_OPTIONS_WRITE) {
ERTS_INTERNAL_ERROR("Only write flag present");
}
return erts_lock_options_get_short_desc(options);
}
typedef struct lc_locked_lock_t_ lc_locked_lock_t;
struct lc_locked_lock_t_ {
lc_locked_lock_t *next;
lc_locked_lock_t *prev;
UWord extra;
Sint16 id;
char *file;
unsigned int line;
erts_lock_flags_t flags;
erts_lock_options_t taken_options;
/*
* Pointer back to the lock instance if it exists or NULL for proc locks.
* If set, we use it to allow trylock of other lock instance
* but with identical lock order as an already locked lock.
*/
erts_lc_lock_t *lck;
};
typedef struct {
lc_locked_lock_t *first;
lc_locked_lock_t *last;
} lc_locked_lock_list_t;
typedef union lc_free_block_t_ lc_free_block_t;
union lc_free_block_t_ {
lc_free_block_t *next;
lc_locked_lock_t lock;
};
typedef struct {
/*
* m[X][Y] & 1 if we locked X directly after Y was locked.
* m[X][Y] & 2 if we locked X indirectly after Y was locked.
* m[X][0] = 1 if we locked X when nothing else was locked.
* m[0][] is unused as it would represent locking "NO LOCK"
*/
char m[ERTS_LOCK_ORDER_SIZE][ERTS_LOCK_ORDER_SIZE];
} lc_matrix_t;
static lc_matrix_t tot_lc_matrix;
#define ERTS_LC_FB_CHUNK_SIZE 10
typedef struct lc_alloc_chunk_t_ lc_alloc_chunk_t;
struct lc_alloc_chunk_t_ {
lc_alloc_chunk_t* next;
lc_free_block_t array[ERTS_LC_FB_CHUNK_SIZE];
};
typedef struct lc_thread_t_ lc_thread_t;
struct lc_thread_t_ {
char *thread_name;
int emu_thread;
erts_tid_t tid;
lc_thread_t *next;
lc_thread_t *prev;
lc_locked_lock_list_t locked;
lc_locked_lock_list_t required;
lc_free_block_t *free_blocks;
lc_alloc_chunk_t *chunks;
lc_matrix_t matrix;
};
static ethr_tsd_key locks_key;
static lc_thread_t *lc_threads = NULL;
static ethr_spinlock_t lc_threads_lock;
static ERTS_INLINE void
lc_lock_threads(void)
{
ethr_spin_lock(&lc_threads_lock);
}
static ERTS_INLINE void
lc_unlock_threads(void)
{
ethr_spin_unlock(&lc_threads_lock);
}
static ERTS_INLINE void lc_free(lc_thread_t* thr, lc_locked_lock_t *p)
{
lc_free_block_t *fb = (lc_free_block_t *) p;
#ifdef DEBUG
sys_memset((void *) p, 0xdf, sizeof(lc_free_block_t));
#endif
fb->next = thr->free_blocks;
thr->free_blocks = fb;
}
static lc_locked_lock_t *lc_core_alloc(lc_thread_t* thr)
{
int i;
lc_alloc_chunk_t* chunk;
lc_free_block_t* fbs;
chunk = (lc_alloc_chunk_t*) malloc(sizeof(lc_alloc_chunk_t));
if (!chunk) {
ERTS_INTERNAL_ERROR("Lock checker failed to allocate memory!");
}
chunk->next = thr->chunks;
thr->chunks = chunk;
fbs = chunk->array;
for (i = 1; i < ERTS_LC_FB_CHUNK_SIZE - 1; i++) {
#ifdef DEBUG
sys_memset((void *) &fbs[i], 0xdf, sizeof(lc_free_block_t));
#endif
fbs[i].next = &fbs[i+1];
}
#ifdef DEBUG
sys_memset((void *) &fbs[ERTS_LC_FB_CHUNK_SIZE-1],
0xdf, sizeof(lc_free_block_t));
#endif
fbs[ERTS_LC_FB_CHUNK_SIZE-1].next = thr->free_blocks;
thr->free_blocks = &fbs[1];
return &fbs[0].lock;
}
static ERTS_INLINE lc_locked_lock_t *lc_alloc(lc_thread_t* thr)
{
lc_locked_lock_t *res;
if (!thr->free_blocks)
res = lc_core_alloc(thr);
else {
res = &thr->free_blocks->lock;
thr->free_blocks = thr->free_blocks->next;
}
return res;
}
static lc_thread_t *
create_thread_data(char *thread_name)
{
lc_thread_t *thr = malloc(sizeof(lc_thread_t));
if (!thr)
ERTS_INTERNAL_ERROR("Lock checker failed to allocate memory!");
thr->thread_name = strdup(thread_name ? thread_name : "unknown");
if (!thr->thread_name)
ERTS_INTERNAL_ERROR("Lock checker failed to allocate memory!");
thr->emu_thread = 0;
thr->tid = erts_thr_self();
thr->required.first = NULL;
thr->required.last = NULL;
thr->locked.first = NULL;
thr->locked.last = NULL;
thr->prev = NULL;
thr->free_blocks = NULL;
thr->chunks = NULL;
sys_memzero(&thr->matrix, sizeof(thr->matrix));
lc_lock_threads();
thr->next = lc_threads;
if (lc_threads)
lc_threads->prev = thr;
lc_threads = thr;
lc_unlock_threads();
erts_tsd_set(locks_key, (void *) thr);
return thr;
}
static void collect_matrix(lc_matrix_t*);
static void
destroy_thread_data(lc_thread_t *thr)
{
ASSERT(thr->thread_name);
free((void *) thr->thread_name);
ASSERT(thr->required.first == NULL);
ASSERT(thr->required.last == NULL);
ASSERT(thr->locked.first == NULL);
ASSERT(thr->locked.last == NULL);
lc_lock_threads();
if (thr->prev)
thr->prev->next = thr->next;
else {
ASSERT(lc_threads == thr);
lc_threads = thr->next;
}
if (thr->next)
thr->next->prev = thr->prev;
collect_matrix(&thr->matrix);
lc_unlock_threads();
while (thr->chunks) {
lc_alloc_chunk_t* free_me = thr->chunks;
thr->chunks = thr->chunks->next;
free(free_me);
}
free((void *) thr);
}
static ERTS_INLINE lc_thread_t *
get_my_locked_locks(void)
{
return erts_tsd_get(locks_key);
}
static ERTS_INLINE lc_thread_t *
make_my_locked_locks(void)
{
lc_thread_t *thr = get_my_locked_locks();
if (thr)
return thr;
else
return create_thread_data(NULL);
}
static ERTS_INLINE lc_locked_lock_t *
new_locked_lock(lc_thread_t* thr,
erts_lc_lock_t *lck, erts_lock_options_t options,
char *file, unsigned int line)
{
lc_locked_lock_t *ll = lc_alloc(thr);
ll->next = NULL;
ll->prev = NULL;
ll->id = lck->id;
ll->extra = lck->extra;
ll->file = file;
ll->line = line;
ll->flags = lck->flags;
ll->taken_options = options;
if ((lck->flags & ERTS_LOCK_FLAGS_MASK_TYPE) == ERTS_LOCK_FLAGS_TYPE_PROCLOCK)
ll->lck = NULL;
else
ll->lck = lck;
return ll;
}
static void
raw_print_lock(char *prefix, Sint16 id, Wterm extra, erts_lock_flags_t flags,
char* file, unsigned int line, char *suffix)
{
char *lname = (1 <= id && id < ERTS_LOCK_ORDER_SIZE
? erts_lock_order[id].name
: "unknown");
erts_fprintf(stderr,"%s'%s:",prefix,lname);
if (is_not_immed(extra))
erts_fprintf(stderr,"%p",_unchecked_boxed_val(extra));
else
erts_fprintf(stderr,"%T",extra);
erts_fprintf(stderr,"[%s]",erts_lock_flags_get_type_name(flags));
if (file)
erts_fprintf(stderr,"(%s:%d)",file,line);
erts_fprintf(stderr,"'(%s)%s",rw_op_str(flags),suffix);
}
static void
print_lock2(char *prefix, Sint16 id, Wterm extra, erts_lock_flags_t flags, char *suffix)
{
raw_print_lock(prefix, id, extra, flags, NULL, 0, suffix);
}
static void
print_lock(char *prefix, erts_lc_lock_t *lck, char *suffix)
{
raw_print_lock(prefix, lck->id, lck->extra, lck->flags, NULL, 0, suffix);
}
static void
print_curr_locks(lc_thread_t *thr)
{
lc_locked_lock_t *ll;
if (!thr || !thr->locked.first)
erts_fprintf(stderr,
"Currently no locks are locked by the %s thread.\n",
thr->thread_name);
else {
erts_fprintf(stderr,
"Currently these locks are locked by the %s thread:\n",
thr->thread_name);
for (ll = thr->locked.first; ll; ll = ll->next)
raw_print_lock(" ", ll->id, ll->extra, ll->flags,
ll->file, ll->line, "\n");
}
}
static void
print_lock_order(void)
{
int i;
erts_fprintf(stderr, "Lock order:\n");
for (i = 1; i < ERTS_LOCK_ORDER_SIZE; i++) {
if (erts_lock_order[i].internal_order)
erts_fprintf(stderr,
" %s:%s\n",
erts_lock_order[i].name,
erts_lock_order[i].internal_order);
else
erts_fprintf(stderr, " %s\n", erts_lock_order[i].name);
}
}
static void
uninitialized_lock(void)
{
erts_fprintf(stderr, "Performing operations on uninitialized lock!\n");
print_curr_locks(get_my_locked_locks());
lc_abort();
}
static void
lock_twice(char *prefix, lc_thread_t *thr, erts_lc_lock_t *lck,
erts_lock_options_t options)
{
erts_fprintf(stderr, "%s (%s)", prefix, rw_op_str(options));
print_lock(" ", lck, " lock which is already locked by thread!\n");
print_curr_locks(thr);
lc_abort();
}
static void
unlock_op_mismatch(lc_thread_t *thr, erts_lc_lock_t *lck,
erts_lock_options_t options)
{
erts_fprintf(stderr, "Unlocking (%s) ", rw_op_str(options));
print_lock("", lck, " lock which mismatch previous lock operation!\n");
print_curr_locks(thr);
lc_abort();
}
static void
unlock_of_not_locked(lc_thread_t *thr, erts_lc_lock_t *lck)
{
print_lock("Unlocking ", lck, " lock which is not locked by thread!\n");
print_curr_locks(thr);
lc_abort();
}
static void
lock_order_violation(lc_thread_t *thr, erts_lc_lock_t *lck)
{
print_lock("Lock order violation occured when locking ", lck, "!\n");
print_curr_locks(thr);
print_lock_order();
lc_abort();
}
static void
type_order_violation(char *op, lc_thread_t *thr,
erts_lc_lock_t *lck)
{
erts_fprintf(stderr, "Lock type order violation occured when ");
print_lock(op, lck, "!\n");
ASSERT(thr);
print_curr_locks(thr);
lc_abort();
}
static void
lock_mismatch(lc_thread_t *thr, int exact,
int failed_have, erts_lc_lock_t *have, int have_len,
int failed_have_not, erts_lc_lock_t *have_not, int have_not_len)
{
int i;
erts_fprintf(stderr, "Lock mismatch found!\n");
if (failed_have >= 0) {
ASSERT(have && have_len > failed_have);
print_lock2("At least the ",
have[failed_have].id, have[failed_have].extra, 0,
" lock is not locked when it should have been\n");
}
else if (failed_have_not >= 0) {
ASSERT(have_not && have_not_len > failed_have_not);
print_lock2("At least the ",
have_not[failed_have_not].id,
have_not[failed_have_not].extra,
0,
" lock is locked when it should not have been\n");
}
if (exact) {
if (!have || have_len <= 0)
erts_fprintf(stderr,
"Thread should not have any locks locked at all\n");
else {
erts_fprintf(stderr,
"Thread should have these and only these locks "
"locked:\n");
for (i = 0; i < have_len; i++)
print_lock2(" ", have[i].id, have[i].extra, 0, "\n");
}
}
else {
if (have && have_len > 0) {
erts_fprintf(stderr,
"Thread should at least have these locks locked:\n");
for (i = 0; i < have_len; i++)
print_lock2(" ", have[i].id, have[i].extra, 0, "\n");
}
if (have_not && have_not_len > 0) {
erts_fprintf(stderr,
"Thread should at least not have these locks "
"locked:\n");
for (i = 0; i < have_not_len; i++)
print_lock2(" ", have_not[i].id, have_not[i].extra, 0, "\n");
}
}
print_curr_locks(thr);
lc_abort();
}
static void
unlock_of_required_lock(lc_thread_t *thr, erts_lc_lock_t *lck)
{
print_lock("Unlocking required ", lck, " lock!\n");
print_curr_locks(thr);
lc_abort();
}
static void
unrequire_of_not_required_lock(lc_thread_t *thr, erts_lc_lock_t *lck)
{
print_lock("Unrequire on ", lck, " lock not required!\n");
print_curr_locks(thr);
lc_abort();
}
static void
require_twice(lc_thread_t *thr, erts_lc_lock_t *lck)
{
print_lock("Require on ", lck, " lock already required!\n");
print_curr_locks(thr);
lc_abort();
}
static void
required_not_locked(lc_thread_t *thr, erts_lc_lock_t *lck)
{
print_lock("Required ", lck, " lock not locked!\n");
print_curr_locks(thr);
lc_abort();
}
static void
thread_exit_handler(void)
{
lc_thread_t *thr = get_my_locked_locks();
if (thr) {
if (thr->locked.first) {
erts_fprintf(stderr,
"Thread exiting while having locked locks!\n");
print_curr_locks(thr);
lc_abort();
}
destroy_thread_data(thr);
/* erts_tsd_set(locks_key, NULL); */
}
}
static __decl_noreturn void
lc_abort(void)
{
#ifdef __WIN32__
DebugBreak();
#else
abort();
#endif
}
void
erts_lc_set_thread_name(char *thread_name)
{
lc_thread_t *thr = get_my_locked_locks();
if (!thr)
thr = create_thread_data(thread_name);
else {
ASSERT(thr->thread_name);
free((void *) thr->thread_name);
thr->thread_name = strdup(thread_name ? thread_name : "unknown");
if (!thr->thread_name)
ERTS_INTERNAL_ERROR("strdup failed");
}
thr->emu_thread = 1;
}
int
erts_lc_is_emu_thr(void)
{
lc_thread_t *thr = get_my_locked_locks();
return thr->emu_thread;
}
int
erts_lc_assert_failed(char *file, int line, char *assertion)
{
erts_fprintf(stderr, "%s:%d: Lock check assertion \"%s\" failed!\n",
file, line, assertion);
print_curr_locks(get_my_locked_locks());
lc_abort();
return 0;
}
void erts_lc_fail(char *fmt, ...)
{
va_list args;
erts_fprintf(stderr, "Lock check failed: ");
va_start(args, fmt);
erts_vfprintf(stderr, fmt, args);
va_end(args);
erts_fprintf(stderr, "\n");
print_curr_locks(get_my_locked_locks());
lc_abort();
}
Sint16
erts_lc_get_lock_order_id(char *name)
{
int i;
if (!name || name[0] == '\0')
erts_fprintf(stderr, "Missing lock name\n");
else {
for (i = 0; i < ERTS_LOCK_ORDER_SIZE; i++)
if (sys_strcmp(erts_lock_order[i].name, name) == 0)
return i;
erts_fprintf(stderr,
"Lock name '%s' missing in lock order "
"(update erl_lock_check.c)\n",
name);
}
lc_abort();
return (Sint16) -1;
}
static int
lc_is_term_order(Sint16 id)
{
return erts_lock_order[id].internal_order != NULL
&& sys_strcmp(erts_lock_order[id].internal_order, "term") == 0;
}
static int compare_locked_by_id(lc_locked_lock_t *locked_lock, erts_lc_lock_t *comparand)
{
if(locked_lock->id < comparand->id) {
return -1;
} else if(locked_lock->id > comparand->id) {
return 1;
}
return 0;
}
static int compare_locked_by_id_extra(lc_locked_lock_t *ll, erts_lc_lock_t *comparand)
{
int order = compare_locked_by_id(ll, comparand);
if(order) {
return order;
}
if (ll->flags & ERTS_LOCK_FLAGS_PROPERTY_TERM_ORDER) {
ASSERT(!is_header(ll->extra) && !is_header(comparand->extra));
return CMP(ll->extra, comparand->extra);
}
if(ll->extra < comparand->extra) {
return -1;
} else if(ll->extra > comparand->extra) {
return 1;
}
return 0;
}
typedef int (*locked_compare_func)(lc_locked_lock_t *, erts_lc_lock_t *);
/* Searches through a list of taken locks, bailing when it hits an entry whose
* order relative to the search template is the opposite of the one at the
* start of the search. (*closest_neighbor) is either set to the exact match,
* or the one closest to it in the sort order. */
static int search_locked_list(locked_compare_func compare,
lc_locked_lock_t *locked_locks,
erts_lc_lock_t *search_template,
lc_locked_lock_t **closest_neighbor)
{
lc_locked_lock_t *iterator = locked_locks;
(*closest_neighbor) = iterator;
if(iterator) {
int relative_order = compare(iterator, search_template);
if(relative_order < 0) {
while((iterator = iterator->next) != NULL) {
relative_order = compare(iterator, search_template);
if(relative_order >= 0) {
(*closest_neighbor) = iterator;
break;
}
}
} else if(relative_order > 0) {
while((iterator = iterator->prev) != NULL) {
relative_order = compare(iterator, search_template);
if(relative_order <= 0) {
(*closest_neighbor) = iterator;
break;
}
}
}
return relative_order == 0;
}
return 0;
}
/* Searches for a lock in the given list that matches search_template, and sets
* (*locked_locks) to the closest lock in the sort order. */
static int
find_lock(lc_locked_lock_t **locked_locks, erts_lc_lock_t *search_template)
{
lc_locked_lock_t *closest_neighbor;
int found_lock;
found_lock = search_locked_list(compare_locked_by_id_extra,
(*locked_locks),
search_template,
&closest_neighbor);
(*locked_locks) = closest_neighbor;
if(found_lock) {
erts_lock_options_t relevant_options;
erts_lock_flags_t relevant_flags;
/* We only care about the options and flags that are set in the
* template. */
relevant_options = (closest_neighbor->taken_options & search_template->taken_options);
relevant_flags = (closest_neighbor->flags & search_template->flags);
return search_template->taken_options == relevant_options &&
search_template->flags == relevant_flags;
}
return 0;
}
/* Searches for a lock in the given list by id, and sets (*locked_locks) to the
* closest lock in the sort order. */
static int
find_id(lc_locked_lock_t **locked_locks, Sint16 id)
{
lc_locked_lock_t *closest_neighbor;
erts_lc_lock_t search_template;
int found_lock;
search_template.id = id;
found_lock = search_locked_list(compare_locked_by_id,
(*locked_locks),
&search_template,
&closest_neighbor);
(*locked_locks) = closest_neighbor;
return found_lock;
}
void
erts_lc_have_locks(int *resv, erts_lc_lock_t *locks, int len)
{
lc_thread_t *thr = get_my_locked_locks();
int i;
if (!thr) {
for (i = 0; i < len; i++)
resv[i] = 0;
}
else {
lc_locked_lock_t *ll = thr->locked.first;
for (i = 0; i < len; i++)
resv[i] = find_lock(&ll, &locks[i]);
}
}
void
erts_lc_have_lock_ids(int *resv, int *ids, int len)
{
lc_thread_t *thr = get_my_locked_locks();
int i;
if (!thr) {
for (i = 0; i < len; i++)
resv[i] = 0;
}
else {
lc_locked_lock_t *ll = thr->locked.first;
for (i = 0; i < len; i++)
resv[i] = find_id(&ll, ids[i]);
}
}
void
erts_lc_check(erts_lc_lock_t *have, int have_len,
erts_lc_lock_t *have_not, int have_not_len)
{
int i;
lc_thread_t *thr = get_my_locked_locks();
lc_locked_lock_t *ll;
if (have && have_len > 0) {
if (!thr)
lock_mismatch(NULL, 0,
-1, have, have_len,
-1, have_not, have_not_len);
ll = thr->locked.first;
for (i = 0; i < have_len; i++) {
if (!find_lock(&ll, &have[i]))
lock_mismatch(thr, 0,
i, have, have_len,
-1, have_not, have_not_len);
}
}
if (have_not && have_not_len > 0 && thr) {
ll = thr->locked.first;
for (i = 0; i < have_not_len; i++) {
if (find_lock(&ll, &have_not[i]))
lock_mismatch(thr, 0,
-1, have, have_len,
i, have_not, have_not_len);
}
}
}
void
erts_lc_check_exact(erts_lc_lock_t *have, int have_len)
{
lc_thread_t *thr = get_my_locked_locks();
if (!thr) {
if (have && have_len > 0)
lock_mismatch(NULL, 1,
-1, have, have_len,
-1, NULL, 0);
}
else {
int i;
lc_locked_lock_t *ll = thr->locked.first;
for (i = 0; i < have_len; i++) {
if (!find_lock(&ll, &have[i]))
lock_mismatch(thr, 1,
i, have, have_len,
-1, NULL, 0);
}
for (i = 0, ll = thr->locked.first; ll; ll = ll->next)
i++;
if (i != have_len)
lock_mismatch(thr, 1,
-1, have, have_len,
-1, NULL, 0);
}
}
void
erts_lc_check_no_locked_of_type(erts_lock_flags_t type)
{
lc_thread_t *thr = get_my_locked_locks();
if (thr) {
lc_locked_lock_t *ll = thr->locked.first;
for (ll = thr->locked.first; ll; ll = ll->next) {
if ((ll->flags & ERTS_LOCK_FLAGS_MASK_TYPE) == type) {
erts_fprintf(stderr,
"Locked lock of type %s found which isn't "
"allowed here!\n",
erts_lock_flags_get_type_name(ll->flags));
print_curr_locks(thr);
lc_abort();
}
}
}
}
int
erts_lc_trylock_force_busy_flg(erts_lc_lock_t *lck, erts_lock_options_t options)
{
#ifdef ERTS_LC_DO_NOT_FORCE_BUSY_TRYLOCK_ON_LOCK_ORDER_VIOLATION
return 0;
#else
/*
* Force busy trylock if locking doesn't follow lock order.
* This in order to make sure that caller can handle
* the situation without causing a lock order violation.
*/
lc_thread_t *thr;
if (lck->inited != ERTS_LC_INITITALIZED)
uninitialized_lock();
if (lck->id < 0)
return 0;
thr = get_my_locked_locks();
if (!thr || !thr->locked.first) {
ASSERT(!thr || !thr->locked.last);
return 0;
}
else {
lc_locked_lock_t *ll;
int order;
ASSERT(thr->locked.last);
#if 0 /* Ok when trylocking I guess... */
if (LOCK_IS_TYPE_ORDER_VIOLATION(lck->flags, thr->locked.last->flags))
type_order_violation("trylocking ", thr, lck);
#endif
ll = thr->locked.last;
order = compare_locked_by_id_extra(ll, lck);
if (order < 0)
return 0;
/*
* TryLock order violation
*/
/* Check that we are not trying to lock this lock twice */
do {
if (order == 0 && (ll->lck == lck || !ll->lck))
lock_twice("Trylocking", thr, lck, options);
ll = ll->prev;
if (!ll)
break;
order = compare_locked_by_id_extra(ll, lck);
} while (order >= 0);
#ifndef ERTS_LC_ALLWAYS_FORCE_BUSY_TRYLOCK_ON_LOCK_ORDER_VIOLATION
/* We only force busy if a lock order violation would occur
and when on an even millisecond. */
{
SysTimeval tv;
sys_gettimeofday(&tv);
if ((tv.tv_usec / 1000) & 1)
return 0;
}
#endif
return 1;
}
#endif
}
void erts_lc_trylock_flg_x(int locked, erts_lc_lock_t *lck, erts_lock_options_t options,
char *file, unsigned int line)
{
lc_thread_t *thr;
lc_locked_lock_t *ll;
if (lck->inited != ERTS_LC_INITITALIZED)
uninitialized_lock();
if (lck->id < 0)
return;
thr = make_my_locked_locks();
ll = locked ? new_locked_lock(thr, lck, options, file, line) : NULL;
if (!thr->locked.last) {
ASSERT(!thr->locked.first);
if (locked)
thr->locked.first = thr->locked.last = ll;
}
else {
lc_locked_lock_t *tl_lck;
#if 0 /* Ok when trylocking I guess... */
if (LOCK_IS_TYPE_ORDER_VIOLATION(lck->flags, thr->locked.last->flags))
type_order_violation("trylocking ", thr, lck);
#endif
for (tl_lck = thr->locked.last; tl_lck; tl_lck = tl_lck->prev) {
int order = compare_locked_by_id_extra(tl_lck, lck);
if (order <= 0) {
if (order == 0 && (tl_lck->lck == lck || !tl_lck->lck))
lock_twice("Trylocking", thr, lck, options);
if (locked) {
ll->next = tl_lck->next;
ll->prev = tl_lck;
if (tl_lck->next)
tl_lck->next->prev = ll;
else
thr->locked.last = ll;
tl_lck->next = ll;
}
return;
}
}
if (locked) {
ll->next = thr->locked.first;
thr->locked.first->prev = ll;
thr->locked.first = ll;
}
}
}
void erts_lc_require_lock_flg(erts_lc_lock_t *lck, erts_lock_options_t options,
char *file, unsigned int line)
{
lc_thread_t *thr = make_my_locked_locks();
lc_locked_lock_t *ll = thr->locked.first;
if (!find_lock(&ll, lck))
required_not_locked(thr, lck);
ll = new_locked_lock(thr, lck, options, file, line);
if (!thr->required.last) {
ASSERT(!thr->required.first);
ll->next = ll->prev = NULL;
thr->required.first = thr->required.last = ll;
}
else {
lc_locked_lock_t *l_lck2;
ASSERT(thr->required.first);
for (l_lck2 = thr->required.last;
l_lck2;
l_lck2 = l_lck2->prev) {
int order = compare_locked_by_id_extra(l_lck2, lck);
if (order < 0)
break;
if (order == 0)
require_twice(thr, lck);
}
if (!l_lck2) {
ll->next = thr->required.first;
ll->prev = NULL;
thr->required.first->prev = ll;
thr->required.first = ll;
}
else {
ll->next = l_lck2->next;
if (ll->next) {
ASSERT(thr->required.last != l_lck2);
ll->next->prev = ll;
}
else {
ASSERT(thr->required.last == l_lck2);
thr->required.last = ll;
}
ll->prev = l_lck2;
l_lck2->next = ll;
}
}
}
void erts_lc_unrequire_lock_flg(erts_lc_lock_t *lck, erts_lock_options_t options)
{
lc_thread_t *thr = make_my_locked_locks();
lc_locked_lock_t *ll = thr->locked.first;
if (!find_lock(&ll, lck))
required_not_locked(thr, lck);
ll = thr->required.first;
if (!find_lock(&ll, lck))
unrequire_of_not_required_lock(thr, lck);
if (ll->prev) {
ASSERT(thr->required.first != ll);
ll->prev->next = ll->next;
}
else {
ASSERT(thr->required.first == ll);
thr->required.first = ll->next;
}
if (ll->next) {
ASSERT(thr->required.last != ll);
ll->next->prev = ll->prev;
}
else {
ASSERT(thr->required.last == ll);
thr->required.last = ll->prev;
}
lc_free(thr, ll);
}
void erts_lc_lock_flg_x(erts_lc_lock_t *lck, erts_lock_options_t options,
char *file, unsigned int line)
{
lc_thread_t *thr;
lc_locked_lock_t *new_ll;
int order;
if (lck->inited != ERTS_LC_INITITALIZED)
uninitialized_lock();
if (lck->id < 0)
return;
thr = make_my_locked_locks();
new_ll = new_locked_lock(thr, lck, options, file, line);
if (!thr->locked.last) {
ASSERT(!thr->locked.first);
thr->locked.last = thr->locked.first = new_ll;
ASSERT(0 < lck->id && lck->id < ERTS_LOCK_ORDER_SIZE);
thr->matrix.m[lck->id][0] = 1;
return;
}
order = compare_locked_by_id_extra(thr->locked.last, lck);
if (order < 0) {
lc_locked_lock_t* ll;
if (LOCK_IS_TYPE_ORDER_VIOLATION(lck->flags, thr->locked.last->flags)) {
type_order_violation("locking ", thr, lck);
}
ASSERT(0 < lck->id && lck->id < ERTS_LOCK_ORDER_SIZE);
ll = thr->locked.last;
thr->matrix.m[lck->id][ll->id] |= 1;
for (ll = ll->prev; ll; ll = ll->prev) {
ASSERT(0 < ll->id && ll->id < ERTS_LOCK_ORDER_SIZE);
thr->matrix.m[lck->id][ll->id] |= 2;
}
new_ll->prev = thr->locked.last;
thr->locked.last->next = new_ll;
thr->locked.last = new_ll;
}
else if (order == 0)
lock_twice("Locking", thr, lck, options);
else
lock_order_violation(thr, lck);
}
void erts_lc_unlock_flg(erts_lc_lock_t *lck, erts_lock_options_t options)
{
lc_thread_t *thr;
lc_locked_lock_t *ll;
if (lck->inited != ERTS_LC_INITITALIZED)
uninitialized_lock();
if (lck->id < 0)
return;
thr = get_my_locked_locks();
if (thr) {
ll = thr->required.first;
if (find_lock(&ll, lck))
unlock_of_required_lock(thr, lck);
}
for (ll = thr ? thr->locked.last : NULL; ll; ll = ll->prev) {
if (ll->id == lck->id && ll->extra == lck->extra) {
if ((ll->taken_options & ERTS_LOCK_OPTIONS_RDWR) != options)
unlock_op_mismatch(thr, lck, options);
if (ll->prev)
ll->prev->next = ll->next;
else
thr->locked.first = ll->next;
if (ll->next)
ll->next->prev = ll->prev;
else
thr->locked.last = ll->prev;
lc_free(thr, ll);
return;
}
}
unlock_of_not_locked(thr, lck);
}
void erts_lc_might_unlock_flg(erts_lc_lock_t *lck, erts_lock_options_t options)
{
lc_thread_t *thr;
lc_locked_lock_t *ll;
if (lck->inited != ERTS_LC_INITITALIZED)
uninitialized_lock();
if (lck->id < 0)
return;
thr = get_my_locked_locks();
if (thr) {
ll = thr->required.first;
if (find_lock(&ll, lck))
unlock_of_required_lock(thr, lck);
}
ll = thr->locked.first;
if (!find_lock(&ll, lck))
unlock_of_not_locked(thr, lck);
}
int
erts_lc_trylock_force_busy(erts_lc_lock_t *lck)
{
return erts_lc_trylock_force_busy_flg(lck, 0);
}
void
erts_lc_trylock_x(int locked, erts_lc_lock_t *lck, char *file, unsigned int line)
{
erts_lc_trylock_flg_x(locked, lck, 0, file, line);
}
void
erts_lc_lock_x(erts_lc_lock_t *lck, char *file, unsigned int line)
{
erts_lc_lock_flg_x(lck, 0, file, line);
}
void
erts_lc_unlock(erts_lc_lock_t *lck)
{
erts_lc_unlock_flg(lck, 0);
}
void erts_lc_might_unlock(erts_lc_lock_t *lck)
{
erts_lc_might_unlock_flg(lck, 0);
}
void erts_lc_require_lock(erts_lc_lock_t *lck, char *file, unsigned int line)
{
erts_lc_require_lock_flg(lck, 0, file, line);
}
void erts_lc_unrequire_lock(erts_lc_lock_t *lck)
{
erts_lc_unrequire_lock_flg(lck, 0);
}
void
erts_lc_init_lock(erts_lc_lock_t *lck, char *name, erts_lock_flags_t flags)
{
lck->id = erts_lc_get_lock_order_id(name);
lck->extra = (UWord) &lck->extra;
ASSERT(is_not_immed(lck->extra));
lck->flags = flags;
lck->taken_options = 0;
lck->inited = ERTS_LC_INITITALIZED;
}
void
erts_lc_init_lock_x(erts_lc_lock_t *lck, char *name, erts_lock_flags_t flags, Eterm extra)
{
lck->id = erts_lc_get_lock_order_id(name);
lck->extra = extra;
lck->flags = flags;
if (lc_is_term_order(lck->id)) {
lck->flags |= ERTS_LOCK_FLAGS_PROPERTY_TERM_ORDER;
ASSERT(!is_header(lck->extra));
}
else
ASSERT(is_immed(lck->extra));
lck->taken_options = 0;
lck->inited = ERTS_LC_INITITALIZED;
}
void
erts_lc_destroy_lock(erts_lc_lock_t *lck)
{
if (lck->inited != ERTS_LC_INITITALIZED)
uninitialized_lock();
lck->inited = 0;
lck->id = -1;
lck->extra = THE_NON_VALUE;
lck->flags = 0;
lck->taken_options = 0;
}
void
erts_lc_init(void)
{
if (ethr_spinlock_init(&lc_threads_lock) != 0)
ERTS_INTERNAL_ERROR("spinlock_init failed");
erts_tsd_key_create(&locks_key,"erts_lock_check_key");
}
void
erts_lc_late_init(void)
{
erts_thr_install_exit_handler(thread_exit_handler);
}
/*
* erts_lc_pll(): print locked locks...
*/
void
erts_lc_pll(void)
{
print_curr_locks(get_my_locked_locks());
}
static void collect_matrix(lc_matrix_t* matrix)
{
int i, j;
for (i = 1; i < ERTS_LOCK_ORDER_SIZE; i++) {
for (j = 0; j <= i; j++) {
tot_lc_matrix.m[i][j] |= matrix->m[i][j];
}
#ifdef DEBUG
for ( ; j < ERTS_LOCK_ORDER_SIZE; j++) {
ASSERT(matrix->m[i][j] == 0);
}
#endif
}
}
Eterm
erts_lc_dump_graph(void)
{
const char* basename = "lc_graph.";
char filename[40];
lc_matrix_t* tot = &tot_lc_matrix;
lc_thread_t* thr;
int i, j, name_max = 0;
FILE* ff;
lc_lock_threads();
for (thr = lc_threads; thr; thr = thr->next) {
collect_matrix(&thr->matrix);
}
lc_unlock_threads();
sys_strcpy(filename, basename);
sys_get_pid(filename + strlen(basename),
sizeof(filename) - strlen(basename));
ff = fopen(filename, "w");
if (!ff)
return am_error;
for (i = 1; i < ERTS_LOCK_ORDER_SIZE; i++) {
int len = strlen(erts_lock_order[i].name);
if (name_max < len)
name_max = len;
}
fputs("%This file was generated by erts_debug:lc_graph()\n\n", ff);
fputs("%{ThisLockName, ThisLockId, LockedDirectlyBeforeThis, LockedIndirectlyBeforeThis}\n", ff);
fprintf(ff, "[{%*s, %2d}", name_max, "\"NO LOCK\"", 0);
for (i = 1; i < ERTS_LOCK_ORDER_SIZE; i++) {
char* delim = "";
fprintf(ff, ",\n {%*s, %2d, [", name_max, erts_lock_order[i].name, i);
for (j = 0; j < ERTS_LOCK_ORDER_SIZE; j++) {
if (tot->m[i][j] & 1) {
fprintf(ff, "%s%d", delim, j);
delim = ",";
}
}
fprintf(ff, "], [");
delim = "";
for (j = 0; j < ERTS_LOCK_ORDER_SIZE; j++) {
if (tot->m[i][j] == 2) {
fprintf(ff, "%s%d", delim, j);
delim = ",";
}
}
fputs("]}", ff);
}
fputs("].", ff);
fclose(ff);
erts_fprintf(stderr, "Created file '%s' in current working directory\n",
filename);
return am_ok;
}
#endif /* #ifdef ERTS_ENABLE_LOCK_CHECK */