master
c 89 lines 2.8 KB
Raw
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