You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-04 12:00:25 +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.
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