pcloudcc-lneely/pclsync/debug/psql_debug.c

531 lines
17 KiB
C

// debug/psql_debug.c - debug-build strong overrides for psql lock/query functions.
#include "../pmem.h"
// Compiled only when BUILD=debug. Strong symbols here override the weak stubs in psql.c.
#include <sqlite3.h>
#include <time.h>
#include "pcache.h"
#include "pdbg.h"
#include "plocks.h"
#include "pnetlibs.h"
#include "psettings.h"
#include "psql.h"
#include "psql_internal.h"
#include "putil.h"
#undef psql_trylock
#undef psql_lock
#undef psql_rdlock
#undef psql_statement
#undef psql_start
#undef psql_query_nocache
#undef psql_query
#undef psql_rdlock_nocache
#undef psql_query_rdlock
#undef psql_query_nolock_nocache
#undef psql_query_nolock
#undef psql_prepare_nocache
#undef psql_prepare
#define SQL_NO_LOCK 0
#define SQL_READ_LOCK 1
#define SQL_WRITE_LOCK 2
#define PSYNC_TNUMBER 1
#define PSYNC_TSTRING 2
#define PSYNC_TREAL 3
#define PSYNC_TNULL 4
extern PSYNC_THREAD const char *psync_thread_name;
/* Forward declaration */
psync_sql_res *psql_do_prepare(const char *sql, const char *file,
unsigned line);
// --------------------------------------------------------------------------
// Debug-only state
// --------------------------------------------------------------------------
typedef struct {
psync_list list;
const char *file;
const char *thread;
struct timespec tm;
unsigned line;
} rd_lock_data;
static PSYNC_THREAD rd_lock_data *rdlock = NULL;
static PSYNC_THREAD unsigned long rdlockctr = 0;
static PSYNC_THREAD struct timespec rdlockstart;
unsigned long lockctr = 0;
static struct timespec lockstart;
static const char *wrlockfile = "none";
static const char *wrlockthread = "";
static unsigned wrlockline = 0;
static unsigned wrlocked = 0;
static pthread_t wrlocker;
static psync_list rdlocks = PSYNC_LIST_STATIC_INIT(rdlocks);
static pthread_mutex_t rdmutex = PTHREAD_MUTEX_INITIALIZER;
// --------------------------------------------------------------------------
// Internal helpers
// --------------------------------------------------------------------------
static void record_wrlock(const char *file, unsigned line) {
if (unlikely(rdlock)) {
pdbg_logf(D_BUG,
"trying to get write lock at %s:%u, but read lock is already taken "
"at %s:%u, aborting",
file, line, rdlock->file, rdlock->line);
sendpdbg_logf("trying to get write lock at %s:%u, but read lock is already "
"taken at %s:%u, aborting",
file, line, rdlock->file, rdlock->line);
abort();
}
sendpdbg_assert(!wrlocked);
pdbg_assert(!wrlocked);
wrlockfile = file;
wrlockline = line;
wrlockthread = psync_thread_name;
wrlocked = 1;
wrlocker = pthread_self();
}
static void record_wrunlock() {
sendpdbg_assert(pthread_equal(pthread_self(), wrlocker));
sendpdbg_assert(wrlocked);
pdbg_assert(pthread_equal(pthread_self(), wrlocker));
pdbg_assert(wrlocked);
wrlocked = 0;
}
static void record_rdlock(const char *file, unsigned line,
struct timespec *tm) {
rd_lock_data *lock;
lock = pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(rd_lock_data));
lock->file = file;
lock->thread = psync_thread_name;
lock->line = line;
memcpy(&lock->tm, tm, sizeof(struct timespec));
pthread_mutex_lock(&rdmutex);
psync_list_add_tail(&rdlocks, &lock->list);
pthread_mutex_unlock(&rdmutex);
rdlock = lock;
}
static rd_lock_data *record_rdunlock() {
rd_lock_data *lock;
pdbg_assert(rdlock);
lock = rdlock;
rdlock = NULL;
pthread_mutex_lock(&rdmutex);
psync_list_del(&lock->list);
pthread_mutex_unlock(&rdmutex);
return lock;
}
// --------------------------------------------------------------------------
// Public debug functions
// --------------------------------------------------------------------------
void psql_dump_locks() {
rd_lock_data *lock;
char dttime[36];
if (wrlocked) {
putil_time_format(lockstart.tv_sec, lockstart.tv_nsec, dttime);
pdbg_logf(D_ERROR, "write lock taken by thread %s from %s:%u at %s",
wrlockthread, wrlockfile, wrlockline, dttime);
sendpdbg_logf("write lock taken by thread %s from %s:%u at %s", wrlockthread,
wrlockfile, wrlockline, dttime);
}
pthread_mutex_lock(&rdmutex);
psync_list_for_each_element(lock, &rdlocks, rd_lock_data, list) {
putil_time_format(lock->tm.tv_sec, lock->tm.tv_nsec, dttime);
pdbg_logf(D_ERROR, "read lock taken by thread %s from %s:%u at %s",
lock->thread, lock->file, lock->line, dttime);
sendpdbg_logf("read lock taken by thread %s from %s:%u at %s", lock->thread,
lock->file, lock->line, dttime);
}
pthread_mutex_unlock(&rdmutex);
}
int psql_do_trylock(const char *file, unsigned line) {
if (plocks_trywrlock(&dblock))
return -1;
if (++lockctr == 1) {
clock_gettime(CLOCK_REALTIME, &lockstart);
record_wrlock(file, line);
}
return 0;
}
void psql_do_lock(const char *file, unsigned line) {
if (plocks_trywrlock(&dblock)) {
struct timespec start, end;
unsigned long msec;
clock_gettime(CLOCK_REALTIME, &start);
memcpy(&end, &start, sizeof(end));
end.tv_sec += PSYNC_DEBUG_LOCK_TIMEOUT;
if (plocks_timedwrlock(&dblock, &end)) {
pdbg_logf(D_BUG, "sql write lock timed out called from %s:%u", file, line);
sendpdbg_logf("sql write lock timed out called from %s:%u", file, line);
psql_dump_locks();
abort();
}
clock_gettime(CLOCK_REALTIME, &end);
msec = (end.tv_sec - start.tv_sec) * 1000 + end.tv_nsec / 1000000 -
start.tv_nsec / 1000000;
if (msec >= 1000)
pdbg_logf(D_ERROR, "waited %lu milliseconds for database write lock", msec);
else if (msec >= 250)
pdbg_logf(D_WARNING, "waited %lu milliseconds for database write lock", msec);
else if (msec >= 5)
pdbg_logf(D_BUG, "waited %lu milliseconds for database write lock", msec);
pdbg_assert(lockctr == 0);
lockctr++;
memcpy(&lockstart, &end, sizeof(struct timespec));
record_wrlock(file, line);
} else if (++lockctr == 1) {
clock_gettime(CLOCK_REALTIME, &lockstart);
record_wrlock(file, line);
}
}
void psql_do_rdlock(const char *file, unsigned line) {
if (plocks_tryrdlock(&dblock)) {
struct timespec start, end;
unsigned long msec;
clock_gettime(CLOCK_REALTIME, &start);
memcpy(&end, &start, sizeof(end));
end.tv_sec += PSYNC_DEBUG_LOCK_TIMEOUT;
if (plocks_timedrdlock(&dblock, &end)) {
pdbg_logf(D_BUG, "sql read lock timed out, called from %s:%u", file, line);
sendpdbg_logf("sql read lock timed out, called from %s:%u", file, line);
psql_dump_locks();
abort();
}
clock_gettime(CLOCK_REALTIME, &end);
msec = (end.tv_sec - start.tv_sec) * 1000 + end.tv_nsec / 1000000 -
start.tv_nsec / 1000000;
if (msec >= 1000)
pdbg_logf(D_ERROR, "waited %lu milliseconds for database read lock", msec);
else if (msec >= 250)
pdbg_logf(D_WARNING, "waited %lu milliseconds for database read lock", msec);
else if (msec >= 5)
pdbg_logf(D_BUG, "waited %lu milliseconds for database read lock", msec);
rdlockctr++;
memcpy(&rdlockstart, &end, sizeof(struct timespec));
record_rdlock(file, line, &rdlockstart);
} else if (++rdlockctr == 1) {
clock_gettime(CLOCK_REALTIME, &rdlockstart);
record_rdlock(file, line, &rdlockstart);
}
}
// --------------------------------------------------------------------------
// Strong overrides for same-named functions (weak in psql.c)
// --------------------------------------------------------------------------
int psql_trylock() {
return psql_do_trylock(__FILE__, __LINE__);
}
void psql_lock() {
psql_do_lock(__FILE__, __LINE__);
}
void psql_rdlock() {
psql_do_rdlock(__FILE__, __LINE__);
}
void psql_unlock() {
pdbg_assert(lockctr > 0);
if (--lockctr == 0) {
struct timespec end;
unsigned long msec;
clock_gettime(CLOCK_REALTIME, &end);
msec = (end.tv_sec - lockstart.tv_sec) * 1000 + end.tv_nsec / 1000000 -
lockstart.tv_nsec / 1000000;
if (msec >= 2000)
pdbg_logf(D_ERROR,
"held database write lock for %lu milliseconds taken from %s:%u",
msec, wrlockfile, wrlockline);
else if (msec >= 500)
pdbg_logf(D_WARNING,
"held database write lock for %lu milliseconds taken from %s:%u",
msec, wrlockfile, wrlockline);
else if (msec >= 10)
pdbg_logf(D_BUG,
"held database write lock for %lu milliseconds taken from %s:%u",
msec, wrlockfile, wrlockline);
record_wrunlock();
plocks_unlock(&dblock);
} else
plocks_unlock(&dblock);
}
void psql_rdunlock() {
if (unlikely(rdlockctr == 0)) {
psql_unlock();
return;
}
if (--rdlockctr == 0) {
struct timespec end;
unsigned long msec;
rd_lock_data *lock;
plocks_unlock(&dblock);
clock_gettime(CLOCK_REALTIME, &end);
lock = record_rdunlock();
msec = (end.tv_sec - rdlockstart.tv_sec) * 1000 + end.tv_nsec / 1000000 -
rdlockstart.tv_nsec / 1000000;
if (msec >= 2000)
pdbg_logf(D_ERROR,
"held database read lock for %lu milliseconds taken at %s:%u", msec,
lock->file, lock->line);
else if (msec >= 500)
pdbg_logf(D_WARNING,
"held database read lock for %lu milliseconds taken at %s:%u", msec,
lock->file, lock->line);
else if (msec >= 20)
pdbg_logf(D_BUG,
"held database read lock for %lu milliseconds taken at %s:%u", msec,
lock->file, lock->line);
pmem_free(PMEM_SUBSYS_OTHER, lock);
} else
plocks_unlock(&dblock);
}
int psql_tryupgradeLock() {
if (plocks_holding_wrlock(&dblock))
return 0;
pdbg_assert(plocks_holding_rdlock(&dblock));
if (plocks_towrlock(&dblock))
return -1;
else {
rd_lock_data *lock = record_rdunlock();
lockctr = rdlockctr;
rdlockctr = 0;
pdbg_assert(lockctr == 1);
lockstart = rdlockstart;
record_wrlock(lock->file, lock->line);
pmem_free(PMEM_SUBSYS_OTHER, lock);
return 0;
}
}
// --------------------------------------------------------------------------
// Debug variants of query/prepare functions (called via psql.h macros)
// --------------------------------------------------------------------------
int psql_statement(const char *sql) {
char *errmsg;
int code;
psql_do_lock(__FILE__, __LINE__);
code = sqlite3_exec(psync_db, sql, NULL, NULL, &errmsg);
psql_unlock();
if (likely(code == SQLITE_OK))
return 0;
else {
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql, errmsg);
sqlite3_free(errmsg);
return -1;
}
}
int psql_do_statement(const char *sql, const char *file, unsigned line) {
char *errmsg;
int code;
psql_do_lock(file, line);
code = sqlite3_exec(psync_db, sql, NULL, NULL, &errmsg);
psql_unlock();
if (likely(code == SQLITE_OK))
return 0;
else {
pdbg_logf(D_ERROR, "error running sql statement: %s: %s called from %s:%u",
sql, errmsg, file, line);
sqlite3_free(errmsg);
return -1;
}
}
int psql_do_start_transaction(const char *file, unsigned line) {
psync_sql_res *res;
psql_do_lock(file, line);
res = psql_do_prepare("BEGIN", file, line);
pdbg_assert(!in_transaction);
if (unlikely(!res || psql_run_free(res)))
return -1;
in_transaction = 1;
transaction_failed = 0;
psync_list_init(&commitcbs);
return 0;
}
psync_sql_res *psql_do_query_nocache(const char *sql, const char *file,
unsigned line) {
sqlite3_stmt *stmt;
psync_sql_res *res;
int code, cnt;
psql_do_lock(file, line);
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
if (unlikely(code != SQLITE_OK)) {
psql_unlock();
pdbg_logf(D_ERROR, "error running sql statement: %s: %s called from %s:%u",
sql, sqlite3_errmsg(psync_db), file, line);
sendpdbg_logf("error running sql statement: %s: %s called from %s:%u",
sql, sqlite3_errmsg(psync_db), file, line);
return NULL;
}
cnt = sqlite3_column_count(stmt);
res = (psync_sql_res *)pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(psync_sql_res) +
cnt * sizeof(psync_variant));
res->stmt = stmt;
res->sql = sql;
res->column_count = cnt;
res->locked = SQL_WRITE_LOCK;
return res;
}
psync_sql_res *psql_do_query(const char *sql, const char *file,
unsigned line) {
psync_sql_res *ret;
ret = (psync_sql_res *)pcache_get(sql);
if (ret) {
ret->locked = SQL_WRITE_LOCK;
ret->sql = sql;
psql_do_lock(file, line);
return ret;
} else
return psql_do_query_nocache(sql, file, line);
}
psync_sql_res *psql_do_query_rdlock_nocache(const char *sql,
const char *file,
unsigned line) {
sqlite3_stmt *stmt;
psync_sql_res *res;
int code, cnt;
psql_do_rdlock(file, line);
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
if (unlikely(code != SQLITE_OK)) {
psql_rdunlock();
pdbg_logf(D_ERROR, "error running sql statement: %s: %s called from %s:%u",
sql, sqlite3_errmsg(psync_db), file, line);
sendpdbg_logf("error running sql statement: %s: %s called from %s:%u",
sql, sqlite3_errmsg(psync_db), file, line);
return NULL;
}
cnt = sqlite3_column_count(stmt);
res = (psync_sql_res *)pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(psync_sql_res) +
cnt * sizeof(psync_variant));
res->stmt = stmt;
res->sql = sql;
res->column_count = cnt;
res->locked = SQL_READ_LOCK;
return res;
}
psync_sql_res *psql_do_query_rdlock(const char *sql, const char *file,
unsigned line) {
psync_sql_res *ret;
ret = (psync_sql_res *)pcache_get(sql);
if (ret) {
ret->locked = SQL_READ_LOCK;
ret->sql = sql;
psql_do_rdlock(file, line);
return ret;
} else
return psql_do_query_rdlock_nocache(sql, file, line);
}
psync_sql_res *psql_do_query_nolock_nocache(const char *sql, const char *file,
unsigned line) {
sqlite3_stmt *stmt;
psync_sql_res *res;
int code, cnt;
if (!psql_locked()) {
pdbg_logf(D_BUG,
"illegal use of psync_sql_query_nolock, can only be used while "
"holding lock, invoked from %s:%u, sql: %s",
file, line, sql);
sendpdbg_logf("illegal use of psync_sql_query_nolock, can only be used while "
"holding lock, invoked from %s:%u, sql: %s",
file, line, sql);
abort();
}
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
if (unlikely(code != SQLITE_OK)) {
pdbg_logf(D_ERROR, "error running sql statement: %s: %s", sql,
sqlite3_errmsg(psync_db));
sendpdbg_logf("error running sql statement: %s: %s", sql,
sqlite3_errmsg(psync_db));
return NULL;
}
cnt = sqlite3_column_count(stmt);
res = (psync_sql_res *)pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(psync_sql_res) +
cnt * sizeof(psync_variant));
res->stmt = stmt;
res->sql = sql;
res->column_count = cnt;
res->locked = SQL_NO_LOCK;
return res;
}
psync_sql_res *psql_do_query_nolock(const char *sql, const char *file,
unsigned line) {
psync_sql_res *ret;
if (!psql_locked()) {
pdbg_logf(D_BUG,
"illegal use of psync_sql_query_nolock, can only be used while "
"holding lock, invoked from %s:%u, sql: %s",
file, line, sql);
sendpdbg_logf("illegal use of psync_sql_query_nolock, can only be used while "
"holding lock, invoked from %s:%u, sql: %s",
file, line, sql);
abort();
}
ret = (psync_sql_res *)pcache_get(sql);
if (ret) {
ret->locked = SQL_NO_LOCK;
ret->sql = sql;
return ret;
} else
return psql_do_query_nolock_nocache(sql, file, line);
}
psync_sql_res *psql_do_prepare_nocache(const char *sql, const char *file,
unsigned line) {
sqlite3_stmt *stmt;
psync_sql_res *res;
int code;
psql_do_lock(file, line);
code = sqlite3_prepare_v2(psync_db, sql, -1, &stmt, NULL);
if (unlikely(code != SQLITE_OK)) {
psql_unlock();
pdbg_logf(D_ERROR, "error running sql statement: %s: %s called from %s:%u",
sql, sqlite3_errmsg(psync_db), file, line);
sendpdbg_logf("error running sql statement: %s: %s called from %s:%u",
sql, sqlite3_errmsg(psync_db), file, line);
return NULL;
}
res = pmem_malloc(PMEM_SUBSYS_OTHER, sizeof(psync_sql_res));
res->stmt = stmt;
res->sql = sql;
res->column_count = 0;
res->locked = SQL_WRITE_LOCK;
return res;
}
psync_sql_res *psql_do_prepare(const char *sql, const char *file,
unsigned line) {
psync_sql_res *ret;
ret = pcache_get(sql);
if (ret) {
ret->locked = SQL_WRITE_LOCK;
psql_do_lock(file, line);
return ret;
} else
return psql_do_prepare_nocache(sql, file, line);
}