You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-17 14:11:06 +00:00
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.
(cherry picked from commit f4d76853b9)
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user