Merge pull request #113114 from RandomShaper/fix_load_progress_cycles

ResourceLoader: Fix potential infinite recursion in progress reporting
This commit is contained in:
Rémi Verschelde 2025-12-02 14:16:27 +01:00
commit a1848013e5
No known key found for this signature in database
GPG key ID: C3336907360768E1
2 changed files with 20 additions and 4 deletions

View file

@ -666,6 +666,14 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
float ResourceLoader::_dependency_get_progress(const String &p_path) {
if (thread_load_tasks.has(p_path)) {
ThreadLoadTask &load_task = thread_load_tasks[p_path];
if (load_task.in_progress_check) {
// Given the fact that any resource loaded when an outer stack frame is
// loading another one is considered a dependency of it, for progress
// tracking purposes, a cycle can happen if even if the original resource
// graphs involved have none. For instance, preload() can cause this.
return load_task.max_reported_progress;
}
load_task.in_progress_check = true;
float current_progress = 0.0;
int dep_count = load_task.sub_tasks.size();
if (dep_count > 0) {
@ -679,6 +687,7 @@ float ResourceLoader::_dependency_get_progress(const String &p_path) {
current_progress = load_task.progress;
}
load_task.max_reported_progress = MAX(load_task.max_reported_progress, current_progress);
load_task.in_progress_check = false;
return load_task.max_reported_progress;
} else {
return 1.0; //assume finished loading it so it no longer exists

View file

@ -176,10 +176,8 @@ private:
struct ThreadLoadTask {
WorkerThreadPool::TaskID task_id = 0; // Used if run on a worker thread from the pool.
Thread::ID thread_id = 0; // Used if running on an user thread (e.g., simple non-threaded load).
bool awaited = false; // If it's in the pool, this helps not awaiting from more than one dependent thread.
ConditionVariable *cond_var = nullptr; // In not in the worker pool or already awaiting, this is used as a secondary awaiting mechanism.
uint32_t awaiters_count = 0;
bool need_wait = true;
LoadToken *load_token = nullptr;
String local_path;
String type_hint;
@ -190,17 +188,26 @@ private:
ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE;
Error error = OK;
Ref<Resource> resource;
bool use_sub_threads = false;
HashSet<String> sub_tasks;
bool awaited : 1; // If it's in the pool, this helps not awaiting from more than one dependent thread.
bool need_wait : 1;
bool in_progress_check : 1; // Measure against recursion cycles in progress reporting. Cycles are not expected, but can happen due to how it's currently implemented.
bool use_sub_threads : 1;
struct ResourceChangedConnection {
Resource *source = nullptr;
Callable callable;
uint32_t flags = 0;
};
LocalVector<ResourceChangedConnection> resource_changed_connections;
};
ThreadLoadTask() :
awaited(false),
need_wait(true),
in_progress_check(false),
use_sub_threads(false) {}
};
static void _run_load_task(void *p_userdata);
static thread_local bool import_thread;