| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "libnetdata/libnetdata.h" |
| 4 | |
| 5 | #define MAX_USEC 512 // Maximum backoff limit in microseconds |
| 6 | |
| 7 | // ---------------------------------------------------------------------------- |
| 8 | // Deadlock detection function |
| 9 | void spinlock_deadlock_detect(usec_t *timestamp, const char *type, const char *func) { |
| 10 | if (!*timestamp) { |
| 11 | // First time checking - initialize the timestamp |
| 12 | *timestamp = now_monotonic_usec(); |
| 13 | return; |
| 14 | } |
| 15 | |
| 16 | // Check if we've exceeded the timeout |
| 17 | usec_t now = now_monotonic_usec(); |
| 18 | if (now - *timestamp >= SPINLOCK_DEADLOCK_TIMEOUT_SEC * USEC_PER_SEC) { |
| 19 | // We've been spinning for too long - likely deadlock |
| 20 | fatal("DEADLOCK DETECTED: %s in function '%s' could not be acquired for %"PRIi64" seconds", |
| 21 | type, func, (int64_t)((now - *timestamp) / USEC_PER_SEC)); |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | // ---------------------------------------------------------------------------- |
| 26 | // spinlock implementation |
| 27 | // https://www.youtube.com/watch?v=rmGJc9PXpuE&t=41s |
| 28 | |
| 29 | #ifndef SPINLOCK_IMPL_WITH_MUTEX |
| 30 | |
| 31 | ALWAYS_INLINE void spinlock_init_with_trace(SPINLOCK *spinlock, const char *func __maybe_unused) { |
| 32 | memset(spinlock, 0, sizeof(SPINLOCK)); |
| 33 | } |
| 34 | |
| 35 | ALWAYS_INLINE void spinlock_lock_with_trace(SPINLOCK *spinlock, const char *func) { |
| 36 | size_t spins = 0; |
| 37 | usec_t usec = 1; |
| 38 | usec_t deadlock_timestamp = 0; |
| 39 | |
| 40 | while (true) { |
| 41 | if (!__atomic_load_n(&spinlock->locked, __ATOMIC_RELAXED) && |
| 42 | !__atomic_test_and_set(&spinlock->locked, __ATOMIC_ACQUIRE)) { |
| 43 | // Acquired the lock |
| 44 | break; |
| 45 | } |
| 46 | |
| 47 | // Backoff strategy with exponential growth |
| 48 | spins++; |
| 49 | |
| 50 | // Check for deadlock every SPINS_BEFORE_DEADLOCK_CHECK iterations |
| 51 | if ((spins % SPINS_BEFORE_DEADLOCK_CHECK) == 0) { |
| 52 | spinlock_deadlock_detect(&deadlock_timestamp, "spinlock", func); |
| 53 | } |
| 54 | |
| 55 | microsleep(usec); |
| 56 | usec = usec >= MAX_USEC ? MAX_USEC : usec * 2; |
| 57 | } |
| 58 | |
| 59 | #ifdef NETDATA_INTERNAL_CHECKS |
| 60 | spinlock->spins += spins; |
| 61 | spinlock->locker_pid = gettid_cached(); |
| 62 | #endif |
| 63 | |
| 64 | nd_thread_spinlock_locked(); |
| 65 | worker_spinlock_contention(func, spins); |
| 66 | } |
| 67 | |
| 68 | ALWAYS_INLINE void spinlock_unlock_with_trace(SPINLOCK *spinlock, const char *func __maybe_unused) { |
| 69 | #ifdef NETDATA_INTERNAL_CHECKS |
| 70 | spinlock->locker_pid = 0; |
| 71 | #endif |
| 72 | |
| 73 | __atomic_clear(&spinlock->locked, __ATOMIC_RELEASE); |
| 74 | |
| 75 | nd_thread_spinlock_unlocked(); |
| 76 | } |
| 77 | |
| 78 | ALWAYS_INLINE bool spinlock_trylock_with_trace(SPINLOCK *spinlock, const char *func __maybe_unused) { |
| 79 | if (!__atomic_load_n(&spinlock->locked, __ATOMIC_RELAXED) && |
| 80 | !__atomic_test_and_set(&spinlock->locked, __ATOMIC_ACQUIRE)) { |
| 81 | // Acquired the lock |
| 82 | nd_thread_spinlock_locked(); |
| 83 | return true; |
| 84 | } |
| 85 | |
| 86 | return false; |
| 87 | } |
| 88 | |
| 89 | #endif // SPINLOCK_IMPL_WITH_MUTEX |