summaryrefslogtreecommitdiffstats
path: root/libnetdata/locks
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 02:57:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 02:57:58 +0000
commitbe1c7e50e1e8809ea56f2c9d472eccd8ffd73a97 (patch)
tree9754ff1ca740f6346cf8483ec915d4054bc5da2d /libnetdata/locks
parentInitial commit. (diff)
downloadnetdata-upstream/1.44.3.tar.xz
netdata-upstream/1.44.3.zip
Adding upstream version 1.44.3.upstream/1.44.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--libnetdata/locks/Makefile.am8
-rw-r--r--libnetdata/locks/README.md107
-rw-r--r--libnetdata/locks/locks.c594
-rw-r--r--libnetdata/locks/locks.h160
4 files changed, 869 insertions, 0 deletions
diff --git a/libnetdata/locks/Makefile.am b/libnetdata/locks/Makefile.am
new file mode 100644
index 00000000..161784b8
--- /dev/null
+++ b/libnetdata/locks/Makefile.am
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+AUTOMAKE_OPTIONS = subdir-objects
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+
+dist_noinst_DATA = \
+ README.md \
+ $(NULL)
diff --git a/libnetdata/locks/README.md b/libnetdata/locks/README.md
new file mode 100644
index 00000000..5560832b
--- /dev/null
+++ b/libnetdata/locks/README.md
@@ -0,0 +1,107 @@
+<!--
+title: "Locks"
+custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/locks/README.md
+sidebar_label: "Locks"
+learn_status: "Published"
+learn_topic_type: "Tasks"
+learn_rel_path: "Developers/libnetdata"
+-->
+
+# Locks
+
+## How to trace netdata locks
+
+To enable tracing rwlocks in netdata, compile netdata by setting `CFLAGS="-DNETDATA_TRACE_RWLOCKS=1"`, like this:
+
+```
+CFLAGS="-O1 -ggdb -DNETDATA_TRACE_RWLOCKS=1" ./netdata-installer.sh
+```
+
+During compilation, the compiler will log:
+
+```
+libnetdata/locks/locks.c:105:2: warning: #warning NETDATA_TRACE_RWLOCKS ENABLED - EXPECT A LOT OF OUTPUT [-Wcpp]
+ 105 | #warning NETDATA_TRACE_RWLOCKS ENABLED - EXPECT A LOT OF OUTPUT
+ | ^~~~~~~
+```
+
+Once compiled, netdata will do the following:
+
+Every call to `netdata_rwlock_*()` is now measured in time.
+
+### logging of slow locks/unlocks
+
+If any call takes more than 10 usec, it will be logged like this:
+
+```
+RW_LOCK ON LOCK 0x0x7fbe1f2e5190: 4157038, 'ACLK_Query_2' (function build_context_param_list() 99@web/api/formatters/rrd2json.c) WAITED to UNLOCK for 29 usec.
+```
+
+The time can be changed by setting this `-DNETDATA_TRACE_RWLOCKS_WAIT_TIME_TO_IGNORE_USEC=20` (or whatever number) to the CFLAGS.
+
+### logging of long hold times
+
+If any lock is holded for more than 10000 usec, it will be logged like this:
+
+```
+RW_LOCK ON LOCK 0x0x55a20afc1b20: 4187198, 'ANALYTICS' (function analytics_gather_mutable_meta_data() 532@daemon/analytics.c) holded a 'R' for 13232 usec.
+```
+
+The time can be changed by setting this `-DNETDATA_TRACE_RWLOCKS_HOLD_TIME_TO_IGNORE_USEC=20000` (or whatever number) to the CFLAGS.
+
+### logging for probable pauses (predictive)
+
+The library maintains a linked-list of all the lock holders (one entry per thread). For this linked-list a mutex is used. So every call to the r/w locks now also has a mutex lock.
+
+If any call is expected to pause the caller (ie the caller is attempting a read lock while there is a write lock in place and vice versa), the library will log something like this:
+
+```
+RW_LOCK ON LOCK 0x0x5651c9fcce20: 4190039 'HEALTH' (function health_execute_pending_updates() 661@health/health.c) WANTS a 'W' lock (while holding 1 rwlocks and 1 mutexes).
+There are 7 readers and 0 writers are holding the lock:
+ => 1: RW_LOCK: process 4190091 'WEB_SERVER[static14]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 709847 usec.
+ => 2: RW_LOCK: process 4190079 'WEB_SERVER[static6]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 709869 usec.
+ => 3: RW_LOCK: process 4190084 'WEB_SERVER[static10]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 709948 usec.
+ => 4: RW_LOCK: process 4190076 'WEB_SERVER[static3]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710190 usec.
+ => 5: RW_LOCK: process 4190092 'WEB_SERVER[static15]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710195 usec.
+ => 6: RW_LOCK: process 4190077 'WEB_SERVER[static4]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710208 usec.
+ => 7: RW_LOCK: process 4190044 'WEB_SERVER[static1]' (function web_client_api_request_v1_data() 526@web/api/web_api_v1.c) is having 1 'R' lock for 710221 usec.
+```
+
+And each of the above is paired with a `GOT` log, like this:
+
+```
+RW_LOCK ON LOCK 0x0x5651c9fcce20: 4190039 'HEALTH' (function health_execute_pending_updates() 661@health/health.c) GOT a 'W' lock (while holding 2 rwlocks and 1 mutexes).
+There are 0 readers and 1 writers are holding the lock:
+ => 1: RW_LOCK: process 4190039 'HEALTH' (function health_execute_pending_updates() 661@health/health.c) is having 1 'W' lock for 36 usec.
+```
+
+Keep in mind that the lock and log are not atomic. The list of callers is indicative (and sometimes just empty because the original holders of the lock, unlocked it until we had the chance to print their names).
+
+### POSIX compliance check
+
+The library may also log messages about POSIX unsupported cases, like this:
+
+```
+RW_LOCK FATAL ON LOCK 0x0x622000109290: 3609368 'PLUGIN[proc]' (function __rrdset_check_rdlock() 10@database/rrdset.c) attempts to acquire a 'W' lock.
+But it is not supported by POSIX because: ALREADY HAS THIS LOCK
+At this attempt, the task is holding 1 rwlocks and 1 mutexes.
+There are 1 readers and 0 writers are holding the lock requested now:
+ => 1: RW_LOCK: process 3609368 'PLUGIN[proc]' (function rrdset_done() 1398@database/rrdset.c) is having 1 'R' lock for 0 usec.
+```
+
+### nested read locks
+
+When compiled with `-DNETDATA_TRACE_RWLOCKS_LOG_NESTED=1` the library will also detect nested read locks and print them like this:
+
+```
+RW_LOCK ON LOCK 0x0x7ff6ea46d190: 4140225 'WEB_SERVER[static14]' (function rrdr_json_wrapper_begin() 34@web/api/formatters/json_wrapper.c) NESTED READ LOCK REQUEST a 'R' lock (while holding 1 rwlocks and 1 mutexes).
+There are 5 readers and 0 writers are holding the lock:
+ => 1: RW_LOCK: process 4140225 'WEB_SERVER[static14]' (function rrdr_lock_rrdset() 70@web/api/queries/rrdr.c) is having 1 'R' lock for 216667 usec.
+ => 2: RW_LOCK: process 4140211 'WEB_SERVER[static6]' (function rrdr_lock_rrdset() 70@web/api/queries/rrdr.c) is having 1 'R' lock for 220001 usec.
+ => 3: RW_LOCK: process 4140218 'WEB_SERVER[static8]' (function rrdr_lock_rrdset() 70@web/api/queries/rrdr.c) is having 1 'R' lock for 220001 usec.
+ => 4: RW_LOCK: process 4140224 'WEB_SERVER[static13]' (function rrdr_lock_rrdset() 70@web/api/queries/rrdr.c) is having 1 'R' lock for 220001 usec.
+ => 5: RW_LOCK: process 4140227 'WEB_SERVER[static16]' (function rrdr_lock_rrdset() 70@web/api/queries/rrdr.c) is having 1 'R' lock for 220001 usec.
+```
+
+
+
diff --git a/libnetdata/locks/locks.c b/libnetdata/locks/locks.c
new file mode 100644
index 00000000..625dd052
--- /dev/null
+++ b/libnetdata/locks/locks.c
@@ -0,0 +1,594 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "../libnetdata.h"
+
+#ifdef NETDATA_TRACE_RWLOCKS
+
+#ifndef NETDATA_TRACE_RWLOCKS_WAIT_TIME_TO_IGNORE_USEC
+#define NETDATA_TRACE_RWLOCKS_WAIT_TIME_TO_IGNORE_USEC 10
+#endif
+
+#ifndef NETDATA_TRACE_RWLOCKS_HOLD_TIME_TO_IGNORE_USEC
+#define NETDATA_TRACE_RWLOCKS_HOLD_TIME_TO_IGNORE_USEC 10000
+#endif
+
+#ifndef NETDATA_THREAD_LOCKS_ARRAY_SIZE
+#define NETDATA_THREAD_LOCKS_ARRAY_SIZE 10
+#endif
+
+#endif // NETDATA_TRACE_RWLOCKS
+
+// ----------------------------------------------------------------------------
+// automatic thread cancelability management, based on locks
+
+static __thread int netdata_thread_first_cancelability = 0;
+static __thread int netdata_thread_nested_disables = 0;
+
+static __thread size_t netdata_locks_acquired_rwlocks = 0;
+static __thread size_t netdata_locks_acquired_mutexes = 0;
+
+inline void netdata_thread_disable_cancelability(void) {
+ if(!netdata_thread_nested_disables) {
+ int old;
+ int ret = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old);
+
+ if(ret != 0)
+ netdata_log_error("THREAD_CANCELABILITY: pthread_setcancelstate() on thread %s returned error %d",
+ netdata_thread_tag(), ret);
+
+ netdata_thread_first_cancelability = old;
+ }
+
+ netdata_thread_nested_disables++;
+}
+
+inline void netdata_thread_enable_cancelability(void) {
+ if(unlikely(netdata_thread_nested_disables < 1)) {
+ internal_fatal(true, "THREAD_CANCELABILITY: trying to enable cancelability, but it was not not disabled");
+
+ netdata_log_error("THREAD_CANCELABILITY: netdata_thread_enable_cancelability(): invalid thread cancelability count %d "
+ "on thread %s - results will be undefined - please report this!",
+ netdata_thread_nested_disables, netdata_thread_tag());
+
+ netdata_thread_nested_disables = 1;
+ }
+
+ if(netdata_thread_nested_disables == 1) {
+ int old = 1;
+ int ret = pthread_setcancelstate(netdata_thread_first_cancelability, &old);
+ if(ret != 0)
+ netdata_log_error("THREAD_CANCELABILITY: pthread_setcancelstate() on thread %s returned error %d",
+ netdata_thread_tag(),
+ ret);
+ else {
+ if(old != PTHREAD_CANCEL_DISABLE) {
+ internal_fatal(true, "THREAD_CANCELABILITY: invalid old state cancelability");
+
+ netdata_log_error("THREAD_CANCELABILITY: netdata_thread_enable_cancelability(): old thread cancelability "
+ "on thread %s was changed, expected DISABLED (%d), found %s (%d) - please report this!",
+ netdata_thread_tag(), PTHREAD_CANCEL_DISABLE,
+ (old == PTHREAD_CANCEL_ENABLE) ? "ENABLED" : "UNKNOWN",
+ old);
+ }
+ }
+ }
+
+ netdata_thread_nested_disables--;
+}
+
+// ----------------------------------------------------------------------------
+// mutex
+
+int __netdata_mutex_init(netdata_mutex_t *mutex) {
+ int ret = pthread_mutex_init(mutex, NULL);
+ if(unlikely(ret != 0))
+ netdata_log_error("MUTEX_LOCK: failed to initialize (code %d).", ret);
+ return ret;
+}
+
+int __netdata_mutex_destroy(netdata_mutex_t *mutex) {
+ int ret = pthread_mutex_destroy(mutex);
+ if(unlikely(ret != 0))
+ netdata_log_error("MUTEX_LOCK: failed to destroy (code %d).", ret);
+ return ret;
+}
+
+int __netdata_mutex_lock(netdata_mutex_t *mutex) {
+ netdata_thread_disable_cancelability();
+
+ int ret = pthread_mutex_lock(mutex);
+ if(unlikely(ret != 0)) {
+ netdata_thread_enable_cancelability();
+ netdata_log_error("MUTEX_LOCK: failed to get lock (code %d)", ret);
+ }
+ else
+ netdata_locks_acquired_mutexes++;
+
+ return ret;
+}
+
+int __netdata_mutex_trylock(netdata_mutex_t *mutex) {
+ netdata_thread_disable_cancelability();
+
+ int ret = pthread_mutex_trylock(mutex);
+ if(ret != 0)
+ netdata_thread_enable_cancelability();
+ else
+ netdata_locks_acquired_mutexes++;
+
+ return ret;
+}
+
+int __netdata_mutex_unlock(netdata_mutex_t *mutex) {
+ int ret = pthread_mutex_unlock(mutex);
+ if(unlikely(ret != 0))
+ netdata_log_error("MUTEX_LOCK: failed to unlock (code %d).", ret);
+ else {
+ netdata_locks_acquired_mutexes--;
+ netdata_thread_enable_cancelability();
+ }
+
+ return ret;
+}
+
+#ifdef NETDATA_TRACE_RWLOCKS
+
+int netdata_mutex_init_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_mutex_t *mutex) {
+ netdata_log_debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_init(%p) from %lu@%s, %s()", mutex, line, file, function);
+
+ int ret = __netdata_mutex_init(mutex);
+
+ netdata_log_debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_init(%p) = %d, from %lu@%s, %s()", mutex, ret, line, file, function);
+
+ return ret;
+}
+
+int netdata_mutex_destroy_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_mutex_t *mutex) {
+ netdata_log_debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_destroy(%p) from %lu@%s, %s()", mutex, line, file, function);
+
+ int ret = __netdata_mutex_destroy(mutex);
+
+ netdata_log_debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_destroy(%p) = %d, from %lu@%s, %s()", mutex, ret, line, file, function);
+
+ return ret;
+}
+
+int netdata_mutex_lock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_mutex_t *mutex) {
+ netdata_log_debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_lock(%p) from %lu@%s, %s()", mutex, line, file, function);
+
+ usec_t start_s = now_monotonic_high_precision_usec();
+ int ret = __netdata_mutex_lock(mutex);
+ usec_t end_s = now_monotonic_high_precision_usec();
+
+ // remove compiler unused variables warning
+ (void)start_s;
+ (void)end_s;
+
+ netdata_log_debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_lock(%p) = %d in %llu usec, from %lu@%s, %s()", mutex, ret, end_s - start_s, line, file, function);
+
+ return ret;
+}
+
+int netdata_mutex_trylock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_mutex_t *mutex) {
+ netdata_log_debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_trylock(%p) from %lu@%s, %s()", mutex, line, file, function);
+
+ usec_t start_s = now_monotonic_high_precision_usec();
+ int ret = __netdata_mutex_trylock(mutex);
+ usec_t end_s = now_monotonic_high_precision_usec();
+
+ // remove compiler unused variables warning
+ (void)start_s;
+ (void)end_s;
+
+ netdata_log_debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_trylock(%p) = %d in %llu usec, from %lu@%s, %s()", mutex, ret, end_s - start_s, line, file, function);
+
+ return ret;
+}
+
+int netdata_mutex_unlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_mutex_t *mutex) {
+ netdata_log_debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_unlock(%p) from %lu@%s, %s()", mutex, line, file, function);
+
+ usec_t start_s = now_monotonic_high_precision_usec();
+ int ret = __netdata_mutex_unlock(mutex);
+ usec_t end_s = now_monotonic_high_precision_usec();
+
+ // remove compiler unused variables warning
+ (void)start_s;
+ (void)end_s;
+
+ netdata_log_debug(D_LOCKS, "MUTEX_LOCK: netdata_mutex_unlock(%p) = %d in %llu usec, from %lu@%s, %s()", mutex, ret, end_s - start_s, line, file, function);
+
+ return ret;
+}
+
+#endif // NETDATA_TRACE_RWLOCKS
+
+// ----------------------------------------------------------------------------
+// rwlock
+
+int __netdata_rwlock_destroy(netdata_rwlock_t *rwlock) {
+ int ret = pthread_rwlock_destroy(&rwlock->rwlock_t);
+ if(unlikely(ret != 0))
+ netdata_log_error("RW_LOCK: failed to destroy lock (code %d)", ret);
+ return ret;
+}
+
+int __netdata_rwlock_init(netdata_rwlock_t *rwlock) {
+ int ret = pthread_rwlock_init(&rwlock->rwlock_t, NULL);
+ if(unlikely(ret != 0))
+ netdata_log_error("RW_LOCK: failed to initialize lock (code %d)", ret);
+ return ret;
+}
+
+int __netdata_rwlock_rdlock(netdata_rwlock_t *rwlock) {
+ netdata_thread_disable_cancelability();
+
+ int ret = pthread_rwlock_rdlock(&rwlock->rwlock_t);
+ if(unlikely(ret != 0)) {
+ netdata_thread_enable_cancelability();
+ netdata_log_error("RW_LOCK: failed to obtain read lock (code %d)", ret);
+ }
+ else
+ netdata_locks_acquired_rwlocks++;
+
+ return ret;
+}
+
+int __netdata_rwlock_wrlock(netdata_rwlock_t *rwlock) {
+ netdata_thread_disable_cancelability();
+
+ int ret = pthread_rwlock_wrlock(&rwlock->rwlock_t);
+ if(unlikely(ret != 0)) {
+ netdata_log_error("RW_LOCK: failed to obtain write lock (code %d)", ret);
+ netdata_thread_enable_cancelability();
+ }
+ else
+ netdata_locks_acquired_rwlocks++;
+
+ return ret;
+}
+
+int __netdata_rwlock_unlock(netdata_rwlock_t *rwlock) {
+ int ret = pthread_rwlock_unlock(&rwlock->rwlock_t);
+ if(unlikely(ret != 0))
+ netdata_log_error("RW_LOCK: failed to release lock (code %d)", ret);
+ else {
+ netdata_thread_enable_cancelability();
+ netdata_locks_acquired_rwlocks--;
+ }
+
+ return ret;
+}
+
+int __netdata_rwlock_tryrdlock(netdata_rwlock_t *rwlock) {
+ netdata_thread_disable_cancelability();
+
+ int ret = pthread_rwlock_tryrdlock(&rwlock->rwlock_t);
+ if(ret != 0)
+ netdata_thread_enable_cancelability();
+ else
+ netdata_locks_acquired_rwlocks++;
+
+ return ret;
+}
+
+int __netdata_rwlock_trywrlock(netdata_rwlock_t *rwlock) {
+ netdata_thread_disable_cancelability();
+
+ int ret = pthread_rwlock_trywrlock(&rwlock->rwlock_t);
+ if(ret != 0)
+ netdata_thread_enable_cancelability();
+ else
+ netdata_locks_acquired_rwlocks++;
+
+ return ret;
+}
+
+// ----------------------------------------------------------------------------
+// spinlock implementation
+// https://www.youtube.com/watch?v=rmGJc9PXpuE&t=41s
+
+void spinlock_init(SPINLOCK *spinlock) {
+ memset(spinlock, 0, sizeof(SPINLOCK));
+}
+
+void spinlock_lock(SPINLOCK *spinlock) {
+ static const struct timespec ns = { .tv_sec = 0, .tv_nsec = 1 };
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ size_t spins = 0;
+#endif
+
+ netdata_thread_disable_cancelability();
+
+ for(int i = 1;
+ __atomic_load_n(&spinlock->locked, __ATOMIC_RELAXED) ||
+ __atomic_test_and_set(&spinlock->locked, __ATOMIC_ACQUIRE)
+ ; i++
+ ) {
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ spins++;
+#endif
+ if(unlikely(i == 8)) {
+ i = 0;
+ nanosleep(&ns, NULL);
+ }
+ }
+
+ // we have the lock
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ spinlock->spins += spins;
+ spinlock->locker_pid = gettid();
+#endif
+}
+
+void spinlock_unlock(SPINLOCK *spinlock) {
+#ifdef NETDATA_INTERNAL_CHECKS
+ spinlock->locker_pid = 0;
+#endif
+ __atomic_clear(&spinlock->locked, __ATOMIC_RELEASE);
+ netdata_thread_enable_cancelability();
+}
+
+bool spinlock_trylock(SPINLOCK *spinlock) {
+ netdata_thread_disable_cancelability();
+
+ if(!__atomic_load_n(&spinlock->locked, __ATOMIC_RELAXED) &&
+ !__atomic_test_and_set(&spinlock->locked, __ATOMIC_ACQUIRE))
+ // we got the lock
+ return true;
+
+ // we didn't get the lock
+ netdata_thread_enable_cancelability();
+ return false;
+}
+
+// ----------------------------------------------------------------------------
+// rw_spinlock implementation
+
+void rw_spinlock_init(RW_SPINLOCK *rw_spinlock) {
+ rw_spinlock->readers = 0;
+ spinlock_init(&rw_spinlock->spinlock);
+}
+
+void rw_spinlock_read_lock(RW_SPINLOCK *rw_spinlock) {
+ netdata_thread_disable_cancelability();
+
+ spinlock_lock(&rw_spinlock->spinlock);
+ __atomic_add_fetch(&rw_spinlock->readers, 1, __ATOMIC_RELAXED);
+ spinlock_unlock(&rw_spinlock->spinlock);
+}
+
+void rw_spinlock_read_unlock(RW_SPINLOCK *rw_spinlock) {
+#ifndef NETDATA_INTERNAL_CHECKS
+ __atomic_sub_fetch(&rw_spinlock->readers, 1, __ATOMIC_RELAXED);
+#else
+ int32_t x = __atomic_sub_fetch(&rw_spinlock->readers, 1, __ATOMIC_RELAXED);
+ if(x < 0)
+ fatal("RW_SPINLOCK: readers is negative %d", x);
+#endif
+
+ netdata_thread_enable_cancelability();
+}
+
+void rw_spinlock_write_lock(RW_SPINLOCK *rw_spinlock) {
+ static const struct timespec ns = { .tv_sec = 0, .tv_nsec = 1 };
+
+ size_t spins = 0;
+ while(1) {
+ spins++;
+ spinlock_lock(&rw_spinlock->spinlock);
+
+ if(__atomic_load_n(&rw_spinlock->readers, __ATOMIC_RELAXED) == 0)
+ break;
+
+ // Busy wait until all readers have released their locks.
+ spinlock_unlock(&rw_spinlock->spinlock);
+ nanosleep(&ns, NULL);
+ }
+
+ (void)spins;
+}
+
+void rw_spinlock_write_unlock(RW_SPINLOCK *rw_spinlock) {
+ spinlock_unlock(&rw_spinlock->spinlock);
+}
+
+bool rw_spinlock_tryread_lock(RW_SPINLOCK *rw_spinlock) {
+ if(spinlock_trylock(&rw_spinlock->spinlock)) {
+ __atomic_add_fetch(&rw_spinlock->readers, 1, __ATOMIC_RELAXED);
+ spinlock_unlock(&rw_spinlock->spinlock);
+ netdata_thread_disable_cancelability();
+ return true;
+ }
+
+ return false;
+}
+
+bool rw_spinlock_trywrite_lock(RW_SPINLOCK *rw_spinlock) {
+ if(spinlock_trylock(&rw_spinlock->spinlock)) {
+ if (__atomic_load_n(&rw_spinlock->readers, __ATOMIC_RELAXED) == 0) {
+ // No readers, we've successfully acquired the write lock
+ return true;
+ }
+ else {
+ // There are readers, unlock the spinlock and return false
+ spinlock_unlock(&rw_spinlock->spinlock);
+ }
+ }
+
+ return false;
+}
+
+
+#ifdef NETDATA_TRACE_RWLOCKS
+
+// ----------------------------------------------------------------------------
+// lockers list
+
+static netdata_rwlock_locker *find_rwlock_locker(const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
+ pid_t pid = gettid();
+ netdata_rwlock_locker *locker = NULL;
+
+ __netdata_mutex_lock(&rwlock->lockers_mutex);
+ Pvoid_t *PValue = JudyLGet(rwlock->lockers_pid_JudyL, pid, PJE0);
+ if(PValue && *PValue)
+ locker = *PValue;
+ __netdata_mutex_unlock(&rwlock->lockers_mutex);
+
+ return locker;
+}
+
+static netdata_rwlock_locker *add_rwlock_locker(const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock, LOCKER_REQUEST lock_type) {
+ netdata_rwlock_locker *locker;
+
+ locker = find_rwlock_locker(file, function, line, rwlock);
+ if(locker) {
+ locker->lock |= lock_type;
+ locker->refcount++;
+ }
+ else {
+ locker = mallocz(sizeof(netdata_rwlock_locker));
+ locker->pid = gettid();
+ locker->tag = netdata_thread_tag();
+ locker->refcount = 1;
+ locker->lock = lock_type;
+ locker->got_it = false;
+ locker->file = file;
+ locker->function = function;
+ locker->line = line;
+
+ __netdata_mutex_lock(&rwlock->lockers_mutex);
+ DOUBLE_LINKED_LIST_APPEND_UNSAFE(rwlock->lockers, locker, prev, next);
+ Pvoid_t *PValue = JudyLIns(&rwlock->lockers_pid_JudyL, locker->pid, PJE0);
+ *PValue = locker;
+ if (lock_type == RWLOCK_REQUEST_READ || lock_type == RWLOCK_REQUEST_TRYREAD) rwlock->readers++;
+ if (lock_type == RWLOCK_REQUEST_WRITE || lock_type == RWLOCK_REQUEST_TRYWRITE) rwlock->writers++;
+ __netdata_mutex_unlock(&rwlock->lockers_mutex);
+ }
+
+ return locker;
+}
+
+static void remove_rwlock_locker(const char *file __maybe_unused, const char *function __maybe_unused, const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock, netdata_rwlock_locker *locker) {
+ __netdata_mutex_lock(&rwlock->lockers_mutex);
+ locker->refcount--;
+ if(!locker->refcount) {
+ DOUBLE_LINKED_LIST_REMOVE_UNSAFE(rwlock->lockers, locker, prev, next);
+ JudyLDel(&rwlock->lockers_pid_JudyL, locker->pid, PJE0);
+ if (locker->lock == RWLOCK_REQUEST_READ || locker->lock == RWLOCK_REQUEST_TRYREAD) rwlock->readers--;
+ else if (locker->lock == RWLOCK_REQUEST_WRITE || locker->lock == RWLOCK_REQUEST_TRYWRITE) rwlock->writers--;
+ freez(locker);
+ }
+ __netdata_mutex_unlock(&rwlock->lockers_mutex);
+}
+
+// ----------------------------------------------------------------------------
+// debug versions of rwlock
+
+int netdata_rwlock_destroy_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
+
+ int ret = __netdata_rwlock_destroy(rwlock);
+ if(!ret) {
+ while (rwlock->lockers)
+ remove_rwlock_locker(file, function, line, rwlock, rwlock->lockers);
+ }
+
+ return ret;
+}
+
+int netdata_rwlock_init_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
+
+ int ret = __netdata_rwlock_init(rwlock);
+ if(!ret) {
+ __netdata_mutex_init(&rwlock->lockers_mutex);
+ rwlock->lockers_pid_JudyL = NULL;
+ rwlock->lockers = NULL;
+ rwlock->readers = 0;
+ rwlock->writers = 0;
+ }
+
+ return ret;
+}
+
+int netdata_rwlock_rdlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
+
+ netdata_rwlock_locker *locker = add_rwlock_locker(file, function, line, rwlock, RWLOCK_REQUEST_READ);
+
+ int ret = __netdata_rwlock_rdlock(rwlock);
+ if(!ret)
+ locker->got_it = true;
+ else
+ remove_rwlock_locker(file, function, line, rwlock, locker);
+
+ return ret;
+}
+
+int netdata_rwlock_wrlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
+
+ netdata_rwlock_locker *locker = add_rwlock_locker(file, function, line, rwlock, RWLOCK_REQUEST_WRITE);
+
+ int ret = __netdata_rwlock_wrlock(rwlock);
+ if(!ret)
+ locker->got_it = true;
+ else
+ remove_rwlock_locker(file, function, line, rwlock, locker);
+
+ return ret;
+}
+
+int netdata_rwlock_unlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
+
+ netdata_rwlock_locker *locker = find_rwlock_locker(file, function, line, rwlock);
+
+ if(unlikely(!locker))
+ fatal("UNLOCK WITHOUT LOCK");
+
+ int ret = __netdata_rwlock_unlock(rwlock);
+ if(likely(!ret))
+ remove_rwlock_locker(file, function, line, rwlock, locker);
+
+ return ret;
+}
+
+int netdata_rwlock_tryrdlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
+
+ netdata_rwlock_locker *locker = add_rwlock_locker(file, function, line, rwlock, RWLOCK_REQUEST_TRYREAD);
+
+ int ret = __netdata_rwlock_tryrdlock(rwlock);
+ if(!ret)
+ locker->got_it = true;
+ else
+ remove_rwlock_locker(file, function, line, rwlock, locker);
+
+ return ret;
+}
+
+int netdata_rwlock_trywrlock_debug(const char *file __maybe_unused, const char *function __maybe_unused,
+ const unsigned long line __maybe_unused, netdata_rwlock_t *rwlock) {
+
+ netdata_rwlock_locker *locker = add_rwlock_locker(file, function, line, rwlock, RWLOCK_REQUEST_TRYWRITE);
+
+ int ret = __netdata_rwlock_trywrlock(rwlock);
+ if(!ret)
+ locker->got_it = true;
+ else
+ remove_rwlock_locker(file, function, line, rwlock, locker);
+
+ return ret;
+}
+
+#endif // NETDATA_TRACE_RWLOCKS
diff --git a/libnetdata/locks/locks.h b/libnetdata/locks/locks.h
new file mode 100644
index 00000000..6b492ae4
--- /dev/null
+++ b/libnetdata/locks/locks.h
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_LOCKS_H
+#define NETDATA_LOCKS_H 1
+
+#include "../libnetdata.h"
+#include "../clocks/clocks.h"
+
+typedef pthread_mutex_t netdata_mutex_t;
+#define NETDATA_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER
+
+typedef struct netdata_spinlock {
+ bool locked;
+#ifdef NETDATA_INTERNAL_CHECKS
+ pid_t locker_pid;
+ size_t spins;
+#endif
+} SPINLOCK;
+
+#define NETDATA_SPINLOCK_INITIALIZER \
+ { .locked = false }
+
+void spinlock_init(SPINLOCK *spinlock);
+void spinlock_lock(SPINLOCK *spinlock);
+void spinlock_unlock(SPINLOCK *spinlock);
+bool spinlock_trylock(SPINLOCK *spinlock);
+
+typedef struct netdata_rw_spinlock {
+ int32_t readers;
+ SPINLOCK spinlock;
+} RW_SPINLOCK;
+
+#define NETDATA_RW_SPINLOCK_INITIALIZER \
+ { .readers = 0, .spinlock = NETDATA_SPINLOCK_INITIALIZER }
+
+void rw_spinlock_init(RW_SPINLOCK *rw_spinlock);
+void rw_spinlock_read_lock(RW_SPINLOCK *rw_spinlock);
+void rw_spinlock_read_unlock(RW_SPINLOCK *rw_spinlock);
+void rw_spinlock_write_lock(RW_SPINLOCK *rw_spinlock);
+void rw_spinlock_write_unlock(RW_SPINLOCK *rw_spinlock);
+bool rw_spinlock_tryread_lock(RW_SPINLOCK *rw_spinlock);
+bool rw_spinlock_trywrite_lock(RW_SPINLOCK *rw_spinlock);
+
+#ifdef NETDATA_TRACE_RWLOCKS
+
+typedef enum {
+ RWLOCK_REQUEST_READ = (1 << 0),
+ RWLOCK_REQUEST_WRITE = (1 << 1),
+ RWLOCK_REQUEST_TRYREAD = (1 << 2),
+ RWLOCK_REQUEST_TRYWRITE = (1 << 3),
+} LOCKER_REQUEST;
+
+typedef struct netdata_rwlock_locker {
+ LOCKER_REQUEST lock;
+ bool got_it;
+ pid_t pid;
+ size_t refcount;
+ const char *tag;
+ const char *file;
+ const char *function;
+ unsigned long line;
+ struct netdata_rwlock_locker *next, *prev;
+} netdata_rwlock_locker;
+
+typedef struct netdata_rwlock_t {
+ pthread_rwlock_t rwlock_t; // the lock
+ size_t readers; // the number of reader on the lock
+ size_t writers; // the number of writers on the lock
+ netdata_mutex_t lockers_mutex; // a mutex to protect the linked list of the lock holding threads
+ netdata_rwlock_locker *lockers; // the linked list of the lock holding threads
+ Pvoid_t lockers_pid_JudyL;
+} netdata_rwlock_t;
+
+#define NETDATA_RWLOCK_INITIALIZER { \
+ .rwlock_t = PTHREAD_RWLOCK_INITIALIZER, \
+ .readers = 0, \
+ .writers = 0, \
+ .lockers_mutex = NETDATA_MUTEX_INITIALIZER, \
+ .lockers = NULL, \
+ .lockers_pid_JudyL = NULL, \
+ }
+
+#else // NETDATA_TRACE_RWLOCKS
+
+typedef struct netdata_rwlock_t {
+ pthread_rwlock_t rwlock_t;
+} netdata_rwlock_t;
+
+#define NETDATA_RWLOCK_INITIALIZER { \
+ .rwlock_t = PTHREAD_RWLOCK_INITIALIZER \
+ }
+
+#endif // NETDATA_TRACE_RWLOCKS
+
+int __netdata_mutex_init(netdata_mutex_t *mutex);
+int __netdata_mutex_destroy(netdata_mutex_t *mutex);
+int __netdata_mutex_lock(netdata_mutex_t *mutex);
+int __netdata_mutex_trylock(netdata_mutex_t *mutex);
+int __netdata_mutex_unlock(netdata_mutex_t *mutex);
+
+int __netdata_rwlock_destroy(netdata_rwlock_t *rwlock);
+int __netdata_rwlock_init(netdata_rwlock_t *rwlock);
+int __netdata_rwlock_rdlock(netdata_rwlock_t *rwlock);
+int __netdata_rwlock_wrlock(netdata_rwlock_t *rwlock);
+int __netdata_rwlock_unlock(netdata_rwlock_t *rwlock);
+int __netdata_rwlock_tryrdlock(netdata_rwlock_t *rwlock);
+int __netdata_rwlock_trywrlock(netdata_rwlock_t *rwlock);
+
+void netdata_thread_disable_cancelability(void);
+void netdata_thread_enable_cancelability(void);
+
+#ifdef NETDATA_TRACE_RWLOCKS
+
+int netdata_mutex_init_debug( const char *file, const char *function, const unsigned long line, netdata_mutex_t *mutex);
+int netdata_mutex_destroy_debug( const char *file, const char *function, const unsigned long line, netdata_mutex_t *mutex);
+int netdata_mutex_lock_debug( const char *file, const char *function, const unsigned long line, netdata_mutex_t *mutex);
+int netdata_mutex_trylock_debug( const char *file, const char *function, const unsigned long line, netdata_mutex_t *mutex);
+int netdata_mutex_unlock_debug( const char *file, const char *function, const unsigned long line, netdata_mutex_t *mutex);
+
+int netdata_rwlock_destroy_debug( const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock);
+int netdata_rwlock_init_debug( const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock);
+int netdata_rwlock_rdlock_debug( const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock);
+int netdata_rwlock_wrlock_debug( const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock);
+int netdata_rwlock_unlock_debug( const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock);
+int netdata_rwlock_tryrdlock_debug( const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock);
+int netdata_rwlock_trywrlock_debug( const char *file, const char *function, const unsigned long line, netdata_rwlock_t *rwlock);
+
+#define netdata_mutex_init(mutex) netdata_mutex_init_debug(__FILE__, __FUNCTION__, __LINE__, mutex)
+#define netdata_mutex_destroy(mutex) netdata_mutex_init_debug(__FILE__, __FUNCTION__, __LINE__, mutex)
+#define netdata_mutex_lock(mutex) netdata_mutex_lock_debug(__FILE__, __FUNCTION__, __LINE__, mutex)
+#define netdata_mutex_trylock(mutex) netdata_mutex_trylock_debug(__FILE__, __FUNCTION__, __LINE__, mutex)
+#define netdata_mutex_unlock(mutex) netdata_mutex_unlock_debug(__FILE__, __FUNCTION__, __LINE__, mutex)
+
+#define netdata_rwlock_destroy(rwlock) netdata_rwlock_destroy_debug(__FILE__, __FUNCTION__, __LINE__, rwlock)
+#define netdata_rwlock_init(rwlock) netdata_rwlock_init_debug(__FILE__, __FUNCTION__, __LINE__, rwlock)
+#define netdata_rwlock_rdlock(rwlock) netdata_rwlock_rdlock_debug(__FILE__, __FUNCTION__, __LINE__, rwlock)
+#define netdata_rwlock_wrlock(rwlock) netdata_rwlock_wrlock_debug(__FILE__, __FUNCTION__, __LINE__, rwlock)
+#define netdata_rwlock_unlock(rwlock) netdata_rwlock_unlock_debug(__FILE__, __FUNCTION__, __LINE__, rwlock)
+#define netdata_rwlock_tryrdlock(rwlock) netdata_rwlock_tryrdlock_debug(__FILE__, __FUNCTION__, __LINE__, rwlock)
+#define netdata_rwlock_trywrlock(rwlock) netdata_rwlock_trywrlock_debug(__FILE__, __FUNCTION__, __LINE__, rwlock)
+
+#else // !NETDATA_TRACE_RWLOCKS
+
+#define netdata_mutex_init(mutex) __netdata_mutex_init(mutex)
+#define netdata_mutex_destroy(mutex) __netdata_mutex_destroy(mutex)
+#define netdata_mutex_lock(mutex) __netdata_mutex_lock(mutex)
+#define netdata_mutex_trylock(mutex) __netdata_mutex_trylock(mutex)
+#define netdata_mutex_unlock(mutex) __netdata_mutex_unlock(mutex)
+
+#define netdata_rwlock_destroy(rwlock) __netdata_rwlock_destroy(rwlock)
+#define netdata_rwlock_init(rwlock) __netdata_rwlock_init(rwlock)
+#define netdata_rwlock_rdlock(rwlock) __netdata_rwlock_rdlock(rwlock)
+#define netdata_rwlock_wrlock(rwlock) __netdata_rwlock_wrlock(rwlock)
+#define netdata_rwlock_unlock(rwlock) __netdata_rwlock_unlock(rwlock)
+#define netdata_rwlock_tryrdlock(rwlock) __netdata_rwlock_tryrdlock(rwlock)
+#define netdata_rwlock_trywrlock(rwlock) __netdata_rwlock_trywrlock(rwlock)
+
+#endif // NETDATA_TRACE_RWLOCKS
+
+#endif //NETDATA_LOCKS_H