WorkerThreadPool (plus friends): Overhaul unlock allowance zones

This fixes a rare but possible deadlock, maybe due to undefined behavior. The new implementation is safer, at the cost of some added boilerplate.
This commit is contained in:
Pedro J. Estébanez
2024-07-18 14:54:58 +02:00
parent df23858488
commit f4d76853b9
12 changed files with 118 additions and 110 deletions

View File

@ -47,76 +47,76 @@
// Also, don't forget to declare the thread_local variable on each use.
template <int Tag>
class SafeBinaryMutex {
friend class MutexLock<SafeBinaryMutex>;
friend class MutexLock<SafeBinaryMutex<Tag>>;
using StdMutexType = THREADING_NAMESPACE::mutex;
mutable THREADING_NAMESPACE::mutex mutex;
static thread_local uint32_t count;
struct TLSData {
mutable THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> lock;
uint32_t count = 0;
TLSData(SafeBinaryMutex<Tag> &p_mutex) :
lock(p_mutex.mutex, THREADING_NAMESPACE::defer_lock) {}
};
static thread_local TLSData tls_data;
public:
_ALWAYS_INLINE_ void lock() const {
if (++count == 1) {
mutex.lock();
if (++tls_data.count == 1) {
tls_data.lock.lock();
}
}
_ALWAYS_INLINE_ void unlock() const {
DEV_ASSERT(count);
if (--count == 0) {
mutex.unlock();
DEV_ASSERT(tls_data.count);
if (--tls_data.count == 0) {
tls_data.lock.unlock();
}
}
_ALWAYS_INLINE_ bool try_lock() const {
if (count) {
count++;
return true;
} else {
if (mutex.try_lock()) {
count++;
return true;
} else {
return false;
}
}
_ALWAYS_INLINE_ THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &_get_lock() const {
return const_cast<THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &>(tls_data.lock);
}
~SafeBinaryMutex() {
DEV_ASSERT(!count);
_ALWAYS_INLINE_ SafeBinaryMutex() {
}
_ALWAYS_INLINE_ ~SafeBinaryMutex() {
DEV_ASSERT(!tls_data.count);
}
};
// This specialization is needed so manual locking and MutexLock can be used
// at the same time on a SafeBinaryMutex.
template <int Tag>
class MutexLock<SafeBinaryMutex<Tag>> {
friend class ConditionVariable;
THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> lock;
const SafeBinaryMutex<Tag> &mutex;
public:
_ALWAYS_INLINE_ explicit MutexLock(const SafeBinaryMutex<Tag> &p_mutex) :
lock(p_mutex.mutex) {
SafeBinaryMutex<Tag>::count++;
};
_ALWAYS_INLINE_ ~MutexLock() {
SafeBinaryMutex<Tag>::count--;
};
explicit MutexLock(const SafeBinaryMutex<Tag> &p_mutex) :
mutex(p_mutex) {
mutex.lock();
}
~MutexLock() {
mutex.unlock();
}
};
#else // No threads.
template <int Tag>
class SafeBinaryMutex : public MutexImpl {
static thread_local uint32_t count;
};
class SafeBinaryMutex {
struct TLSData {
TLSData(SafeBinaryMutex<Tag> &p_mutex) {}
};
static thread_local TLSData tls_data;
template <int Tag>
class MutexLock<SafeBinaryMutex<Tag>> {
public:
MutexLock(const SafeBinaryMutex<Tag> &p_mutex) {}
~MutexLock() {}
void lock() const {}
void unlock() const {}
};
#endif // THREADS_ENABLED