You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-19 14:31:59 +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:
@@ -32,6 +32,7 @@
|
||||
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/os/safe_binary_mutex.h"
|
||||
#include "core/os/thread_safe.h"
|
||||
|
||||
WorkerThreadPool::Task *const WorkerThreadPool::ThreadData::YIELDING = (Task *)1;
|
||||
@@ -46,7 +47,7 @@ void WorkerThreadPool::Task::free_template_userdata() {
|
||||
WorkerThreadPool *WorkerThreadPool::singleton = nullptr;
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
thread_local uintptr_t WorkerThreadPool::unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES] = {};
|
||||
thread_local WorkerThreadPool::UnlockableLocks WorkerThreadPool::unlockable_locks[MAX_UNLOCKABLE_LOCKS];
|
||||
#endif
|
||||
|
||||
void WorkerThreadPool::_process_task(Task *p_task) {
|
||||
@@ -428,13 +429,9 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
|
||||
|
||||
void WorkerThreadPool::_lock_unlockable_mutexes() {
|
||||
#ifdef THREADS_ENABLED
|
||||
for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) {
|
||||
if (unlockable_mutexes[i]) {
|
||||
if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) {
|
||||
((Mutex *)unlockable_mutexes[i])->lock();
|
||||
} else {
|
||||
((BinaryMutex *)(unlockable_mutexes[i] & ~1))->lock();
|
||||
}
|
||||
for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) {
|
||||
if (unlockable_locks[i].ulock) {
|
||||
unlockable_locks[i].ulock->lock();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -442,13 +439,9 @@ void WorkerThreadPool::_lock_unlockable_mutexes() {
|
||||
|
||||
void WorkerThreadPool::_unlock_unlockable_mutexes() {
|
||||
#ifdef THREADS_ENABLED
|
||||
for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) {
|
||||
if (unlockable_mutexes[i]) {
|
||||
if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) {
|
||||
((Mutex *)unlockable_mutexes[i])->unlock();
|
||||
} else {
|
||||
((BinaryMutex *)(unlockable_mutexes[i] & ~1))->unlock();
|
||||
}
|
||||
for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) {
|
||||
if (unlockable_locks[i].ulock) {
|
||||
unlockable_locks[i].ulock->unlock();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -675,37 +668,28 @@ WorkerThreadPool::TaskID WorkerThreadPool::get_caller_task_id() {
|
||||
}
|
||||
|
||||
#ifdef THREADS_ENABLED
|
||||
uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(Mutex *p_mutex) {
|
||||
return _thread_enter_unlock_allowance_zone(p_mutex, false);
|
||||
}
|
||||
|
||||
uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex) {
|
||||
return _thread_enter_unlock_allowance_zone(p_mutex, true);
|
||||
}
|
||||
|
||||
uint32_t WorkerThreadPool::_thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary) {
|
||||
for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) {
|
||||
if (unlikely((unlockable_mutexes[i] & ~1) == (uintptr_t)p_mutex)) {
|
||||
uint32_t WorkerThreadPool::_thread_enter_unlock_allowance_zone(THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &p_ulock) {
|
||||
for (uint32_t i = 0; i < MAX_UNLOCKABLE_LOCKS; i++) {
|
||||
DEV_ASSERT((bool)unlockable_locks[i].ulock == (bool)unlockable_locks[i].rc);
|
||||
if (unlockable_locks[i].ulock == &p_ulock) {
|
||||
// Already registered in the current thread.
|
||||
return UINT32_MAX;
|
||||
}
|
||||
if (!unlockable_mutexes[i]) {
|
||||
unlockable_mutexes[i] = (uintptr_t)p_mutex;
|
||||
if (p_is_binary) {
|
||||
unlockable_mutexes[i] |= 1;
|
||||
}
|
||||
unlockable_locks[i].rc++;
|
||||
return i;
|
||||
} else if (!unlockable_locks[i].ulock) {
|
||||
unlockable_locks[i].ulock = &p_ulock;
|
||||
unlockable_locks[i].rc = 1;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
ERR_FAIL_V_MSG(UINT32_MAX, "No more unlockable mutex slots available. Engine bug.");
|
||||
ERR_FAIL_V_MSG(UINT32_MAX, "No more unlockable lock slots available. Engine bug.");
|
||||
}
|
||||
|
||||
void WorkerThreadPool::thread_exit_unlock_allowance_zone(uint32_t p_zone_id) {
|
||||
if (p_zone_id == UINT32_MAX) {
|
||||
return;
|
||||
DEV_ASSERT(unlockable_locks[p_zone_id].ulock && unlockable_locks[p_zone_id].rc);
|
||||
unlockable_locks[p_zone_id].rc--;
|
||||
if (unlockable_locks[p_zone_id].rc == 0) {
|
||||
unlockable_locks[p_zone_id].ulock = nullptr;
|
||||
}
|
||||
DEV_ASSERT(unlockable_mutexes[p_zone_id]);
|
||||
unlockable_mutexes[p_zone_id] = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Reference in New Issue
Block a user