Repository: godotengine/godot (language: cpp)

Issue:
Deadlock between PilelineHashMapRD::local_mutex and GDScriptCache::mutex
### Tested versions

master(commit b5bdb88062a31a982c8f3bf4b820188edd53c6c5)
Godot 4.4 release
and highly likely some previous versions of 4.4

### System information

Windows 10, CPU AMD Ryzen 5 5600X 6-Core Processor, GPU: NVIDIA GeForce RTX 3060 Ti

### Issue description

Godot freezes on loading resources. I'm not able to produce an MRP, but I think I found the cause of the deadlock.

### Steps to reproduce

In debugger there is a two important threads. Here are theirs callstacks:
<details>
  <summary>Thread 16964 callstack</summary>

```
 	[External Code]	
 	[Inline Frame] godot.windows.editor.x86_64.exe!std::_Mutex_base::lock() Line 52	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!std::unique_lock<std::recursive_mutex>::{ctor}(std::recursive_mutex &) Line 144	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!MutexLock<MutexImpl<std::recursive_mutex>>::{ctor}(const MutexImpl<std::recursive_mutex> &) Line 77	C++
>	godot.windows.editor.x86_64.exe!PipelineHashMapRD<RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData::PipelineKey,RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData,void (__cdecl RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData::*)(RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData::PipelineKey)>::compile_pipeline(const RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData::PipelineKey & p_key, unsigned int p_key_hash, RenderingServer::PipelineSource p_source, bool p_high_priority) Line 97	C++
 	godot.windows.editor.x86_64.exe!RendererSceneRenderImplementation::RenderForwardClustered::_mesh_compile_pipeline_for_surface(RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData * p_shader, void * p_mesh_surface, bool p_ubershader, bool p_instanced_surface, RenderingServer::PipelineSource p_source, RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData::PipelineKey & r_pipeline_key, Vector<Pair<RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData *,RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData::PipelineKey>> * r_pipeline_pairs) Line 4440	C++
 	godot.windows.editor.x86_64.exe!RendererSceneRenderImplementation::RenderForwardClustered::_mesh_compile_pipelines_for_surface(const RendererSceneRenderImplementation::RenderForwardClustered::SurfacePipelineData & p_surface, const RendererSceneRenderImplementation::RenderForwardClustered::GlobalPipelineData & p_global, RenderingServer::PipelineSource p_source, Vector<Pair<RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData *,RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData::PipelineKey>> * r_pipeline_pairs) Line 4492	C++
 	godot.windows.editor.x86_64.exe!RendererSceneRenderImplementation::RenderForwardClustered::mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) Line 4754	C++
 	godot.windows.editor.x86_64.exe!RenderingServerDefault::mesh_create_from_surfaces(const Vector<RenderingServer::SurfaceData> & p_surfaces, int p_blend_shape_count) Line 344	C++
 	godot.windows.editor.x86_64.exe!ArrayMesh::_set_surfaces(const Array & p_surfaces) Line 1674	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!call_with_variant_args_helper(ArrayMesh *) Line 314	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!call_with_variant_args_dv(ArrayMesh *) Line 461	C++
 	godot.windows.editor.x86_64.exe!MethodBindT<ArrayMesh,Array const &>::call(Object * p_object, const Variant * * p_args, int p_arg_count, Callable::CallError & r_error) Line 342	C++
 	godot.windows.editor.x86_64.exe!ClassDB::set_property(Object * p_object, const StringName & p_property, const Variant & p_value, bool * r_valid) Line 1634	C++
 	godot.windows.editor.x86_64.exe!Object::set(const StringName & p_name, const Variant & p_value, bool * r_valid) Line 284	C++
 	godot.windows.editor.x86_64.exe!ResourceLoaderBinary::load() Line 874	C++
 	godot.windows.editor.x86_64.exe!ResourceFormatLoaderBinary::load(const String & p_path, const String & p_original_path, Error * r_error, bool p_use_sub_threads, float * r_progress, ResourceFormatLoader::CacheMode p_cache_mode) Line 1251	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load(const String & p_path, const String & p_original_path, const String & p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error * r_error, bool p_use_sub_threads, float * r_progress) Line 307	C++
 	godot.windows.editor.x86_64.exe!ResourceFormatImporter::load_internal(const String & p_path, Error * r_error, bool p_use_sub_threads, float * r_progress, ResourceFormatLoader::CacheMode p_cache_mode, bool p_silence_errors) Line 177	C++
 	godot.windows.editor.x86_64.exe!ResourceFormatImporter::load(const String & p_path, const String & p_original_path, Error * r_error, bool p_use_sub_threads, float * r_progress, ResourceFormatLoader::CacheMode p_cache_mode) Line 153	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load(const String & p_path, const String & p_original_path, const String & p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error * r_error, bool p_use_sub_threads, float * r_progress) Line 307	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_run_load_task(void * p_userdata) Line 389	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load_start(const String & p_path, const String & p_type_hint, ResourceLoader::LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode, bool p_for_user) Line 655	C++
 	godot.windows.editor.x86_64.exe!ResourceLoaderText::load() Line 466	C++
 	godot.windows.editor.x86_64.exe!ResourceFormatLoaderText::load(const String & p_path, const String & p_original_path, Error * r_error, bool p_use_sub_threads, float * r_progress, ResourceFormatLoader::CacheMode p_cache_mode) Line 1413	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load(const String & p_path, const String & p_original_path, const String & p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error * r_error, bool p_use_sub_threads, float * r_progress) Line 307	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_run_load_task(void * p_userdata) Line 389	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::_process_task(WorkerThreadPool::Task * p_task) Line 142	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::_wait_collaboratively(WorkerThreadPool::ThreadData * p_caller_pool_thread, WorkerThreadPool::Task * p_task) Line 462	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::wait_for_task_completion(__int64 p_task_id) Line 414	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load_complete_inner(ResourceLoader::LoadToken & p_load_token, Error * r_error, MutexLock<SafeBinaryMutex<1>> & p_thread_load_lock) Line 813	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!ResourceLoader::_load_complete(ResourceLoader::LoadToken &) Line 772	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::load(const String & p_path, const String & p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error * r_error) Line 555	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode * p_preload) Line 4691	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode * p_expression, bool p_is_root) Line 2597	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode * p_assignable, const char * p_kind) Line 1989	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode * p_constant, bool p_is_local) Line 2123	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode * p_class, int p_index, const GDScriptParser::Node * p_source) Line 1113	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode * p_class, const GDScriptParser::Node * p_source) Line 1319	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode * p_class, const GDScriptParser::Node * p_source) Line 1295	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode * p_class, const GDScriptParser::Node * p_source) Line 1315	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode * p_class, bool p_recursive) Line 1346	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_interface() Line 6306	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::analyze() Line 6341	C++
 	godot.windows.editor.x86_64.exe!GDScript::reload(bool p_keep_state) Line 837	C++
 	godot.windows.editor.x86_64.exe!GDScriptCache::get_full_script(const String & p_path, Error & r_error, const String & p_owner, bool p_update_from_disk) Line 386	C++
 	godot.windows.editor.x86_64.exe!ResourceFormatLoaderGDScript::load(const String & p_path, const String & p_original_path, Error * r_error, bool p_use_sub_threads, float * r_progress, ResourceFormatLoader::CacheMode p_cache_mode) Line 3018	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load(const String & p_path, const String & p_original_path, const String & p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error * r_error, bool p_use_sub_threads, float * r_progress) Line 307	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_run_load_task(void * p_userdata) Line 389	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::_process_task(WorkerThreadPool::Task * p_task) Line 142	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::_thread_function(void * p_user) Line 193	C++
 	godot.windows.editor.x86_64.exe!Thread::callback(unsigned __int64 p_caller_id, const Thread::Settings & p_settings, void(*)(void *) p_callback, void * p_userdata) Line 66	C++
 	[External Code]	
 	godot.windows.editor.x86_64.exe!thread_start<unsigned int (__cdecl*)(void *),1>(void * const parameter) Line 97	C++
 	[External Code]	
```
</details>
<details>
  <summary>Thread 13924 callstack</summary>

```
 	[External Code]	
 	[Inline Frame] godot.windows.editor.x86_64.exe!std::_Mutex_base::lock() Line 52	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!std::unique_lock<std::mutex>::lock() Line 198	C++
>	godot.windows.editor.x86_64.exe!WorkerThreadPool::_lock_unlockable_mutexes() Line 439	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::_wait_collaboratively(WorkerThreadPool::ThreadData * p_caller_pool_thread, WorkerThreadPool::Task * p_task) Line 529	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::wait_for_task_completion(__int64 p_task_id) Line 414	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!PipelineHashMapRD<RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData::PipelineKey,RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData,void (__cdecl RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData::*)(RendererSceneRenderImplementation::SceneShaderForwardClustered::ShaderData::PipelineKey)>::wait_for_pipeline(unsigned int p_key_hash) Line 149	C++
 	godot.windows.editor.x86_64.exe!RendererSceneRenderImplementation::RenderForwardClustered::mesh_generate_pipelines(RID p_mesh, bool p_background_compilation) Line 4798	C++
 	godot.windows.editor.x86_64.exe!RenderingServerDefault::mesh_create_from_surfaces(const Vector<RenderingServer::SurfaceData> & p_surfaces, int p_blend_shape_count) Line 344	C++
 	godot.windows.editor.x86_64.exe!ArrayMesh::_set_surfaces(const Array & p_surfaces) Line 1674	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!call_with_variant_args_helper(ArrayMesh *) Line 314	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!call_with_variant_args_dv(ArrayMesh *) Line 461	C++
 	godot.windows.editor.x86_64.exe!MethodBindT<ArrayMesh,Array const &>::call(Object * p_object, const Variant * * p_args, int p_arg_count, Callable::CallError & r_error) Line 342	C++
 	godot.windows.editor.x86_64.exe!ClassDB::set_property(Object * p_object, const StringName & p_property, const Variant & p_value, bool * r_valid) Line 1634	C++
 	godot.windows.editor.x86_64.exe!Object::set(const StringName & p_name, const Variant & p_value, bool * r_valid) Line 284	C++
 	godot.windows.editor.x86_64.exe!ResourceLoaderBinary::load() Line 874	C++
 	godot.windows.editor.x86_64.exe!ResourceFormatLoaderBinary::load(const String & p_path, const String & p_original_path, Error * r_error, bool p_use_sub_threads, float * r_progress, ResourceFormatLoader::CacheMode p_cache_mode) Line 1251	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load(const String & p_path, const String & p_original_path, const String & p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error * r_error, bool p_use_sub_threads, float * r_progress) Line 307	C++
 	godot.windows.editor.x86_64.exe!ResourceFormatImporter::load_internal(const String & p_path, Error * r_error, bool p_use_sub_threads, float * r_progress, ResourceFormatLoader::CacheMode p_cache_mode, bool p_silence_errors) Line 177	C++
 	godot.windows.editor.x86_64.exe!ResourceFormatImporter::load(const String & p_path, const String & p_original_path, Error * r_error, bool p_use_sub_threads, float * r_progress, ResourceFormatLoader::CacheMode p_cache_mode) Line 153	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load(const String & p_path, const String & p_original_path, const String & p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error * r_error, bool p_use_sub_threads, float * r_progress) Line 307	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_run_load_task(void * p_userdata) Line 389	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load_start(const String & p_path, const String & p_type_hint, ResourceLoader::LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode, bool p_for_user) Line 655	C++
 	godot.windows.editor.x86_64.exe!ResourceLoaderText::load() Line 466	C++
 	godot.windows.editor.x86_64.exe!ResourceFormatLoaderText::load(const String & p_path, const String & p_original_path, Error * r_error, bool p_use_sub_threads, float * r_progress, ResourceFormatLoader::CacheMode p_cache_mode) Line 1413	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load(const String & p_path, const String & p_original_path, const String & p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error * r_error, bool p_use_sub_threads, float * r_progress) Line 307	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_run_load_task(void * p_userdata) Line 389	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::_process_task(WorkerThreadPool::Task * p_task) Line 142	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::_wait_collaboratively(WorkerThreadPool::ThreadData * p_caller_pool_thread, WorkerThreadPool::Task * p_task) Line 462	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::wait_for_task_completion(__int64 p_task_id) Line 414	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load_complete_inner(ResourceLoader::LoadToken & p_load_token, Error * r_error, MutexLock<SafeBinaryMutex<1>> & p_thread_load_lock) Line 813	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!ResourceLoader::_load_complete(ResourceLoader::LoadToken &) Line 772	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::load(const String & p_path, const String & p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error * r_error) Line 555	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode * p_preload) Line 4691	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::reduce_expression(GDScriptParser::ExpressionNode * p_expression, bool p_is_root) Line 2597	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode * p_assignable, const char * p_kind) Line 1989	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_constant(GDScriptParser::ConstantNode * p_constant, bool p_is_local) Line 2123	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode * p_class, int p_index, const GDScriptParser::Node * p_source) Line 1113	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode * p_class, const GDScriptParser::Node * p_source) Line 1319	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode * p_class, bool p_recursive) Line 1346	C++
 	[Inline Frame] godot.windows.editor.x86_64.exe!GDScriptAnalyzer::resolve_interface() Line 6306	C++
 	godot.windows.editor.x86_64.exe!GDScriptAnalyzer::analyze() Line 6341	C++
 	godot.windows.editor.x86_64.exe!GDScript::reload(bool p_keep_state) Line 837	C++
 	godot.windows.editor.x86_64.exe!GDScriptCache::get_full_script(const String & p_path, Error & r_error, const String & p_owner, bool p_update_from_disk) Line 386	C++
 	godot.windows.editor.x86_64.exe!GDScriptCache::finish_compiling(const String & p_owner) Line 425	C++
 	godot.windows.editor.x86_64.exe!GDScriptCompiler::compile(const GDScriptParser * p_parser, GDScript * p_script, bool p_keep_state) Line 3292	C++
 	godot.windows.editor.x86_64.exe!GDScript::reload(bool p_keep_state) Line 854	C++
 	godot.windows.editor.x86_64.exe!GDScriptCache::get_full_script(const String & p_path, Error & r_error, const String & p_owner, bool p_update_from_disk) Line 386	C++
 	godot.windows.editor.x86_64.exe!ResourceFormatLoaderGDScript::load(const String & p_path, const String & p_original_path, Error * r_error, bool p_use_sub_threads, float * r_progress, ResourceFormatLoader::CacheMode p_cache_mode) Line 3018	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_load(const String & p_path, const String & p_original_path, const String & p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error * r_error, bool p_use_sub_threads, float * r_progress) Line 307	C++
 	godot.windows.editor.x86_64.exe!ResourceLoader::_run_load_task(void * p_userdata) Line 389	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::_process_task(WorkerThreadPool::Task * p_task) Line 142	C++
 	godot.windows.editor.x86_64.exe!WorkerThreadPool::_thread_function(void * p_user) Line 193	C++
 	godot.windows.editor.x86_64.exe!Thread::callback(unsigned __int64 p_caller_id, const Thread::Settings & p_settings, void(*)(void *) p_callback, void * p_userdata) Line 66	C++
 	[External Code]	
 	godot.windows.editor.x86_64.exe!thread_start<unsigned int (__cdecl*)(void *),1>(void * const parameter) Line 97	C++
 	[External Code]	
```
</details>
Thread 16964 locks GDScriptCache::mutex in function GDScriptCache::get_full_script and waits on PipelineHashMapRD<...>::local_mutex in function PipelineHashMapRD<...>::compile_pipeline. Other thread locks PipelineHashMapRD<...>::local_mutex in function PipelineHashMapRD<...>::wait_for_pipeline and wait GDScriptCache::mutex in function WorkerThreadPool::_lock_unlockable_mutexes. One of mutexes in 'unlockable_locks' is actually a GDScriptCache::mutex.

### Minimal reproduction project (MRP)

I'm not able to make MRP, I will make PR for this issue.


Discussion / hints:

Fix patch:
diff --git a/servers/rendering/renderer_rd/pipeline_hash_map_rd.h b/servers/rendering/renderer_rd/pipeline_hash_map_rd.h
index f657433ea424..f3d17e3836df 100644
--- a/servers/rendering/renderer_rd/pipeline_hash_map_rd.h
+++ b/servers/rendering/renderer_rd/pipeline_hash_map_rd.h
@@ -77,9 +77,16 @@ class PipelineHashMapRD {
 	}
 
 	void _wait_for_all_pipelines() {
-		MutexLock local_lock(local_mutex);
-		for (KeyValue<uint32_t, WorkerThreadPool::TaskID> key_value : compilation_tasks) {
-			WorkerThreadPool::get_singleton()->wait_for_task_completion(key_value.value);
+		thread_local LocalVector<WorkerThreadPool::TaskID> tasks_to_wait;
+		{
+			MutexLock local_lock(local_mutex);
+			for (KeyValue<uint32_t, WorkerThreadPool::TaskID> key_value : compilation_tasks) {
+				tasks_to_wait.push_back(key_value.value);
+			}
+		}
+
+		for (WorkerThreadPool::TaskID task_id : tasks_to_wait) {
+			WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id);
 		}
 	}
 
@@ -137,17 +144,25 @@ class PipelineHashMapRD {
 	}
 
 	void wait_for_pipeline(uint32_t p_key_hash) {
-		MutexLock local_lock(local_mutex);
-		if (!compilation_set.has(p_key_hash)) {
-			// The pipeline was never submitted, we can't wait for it.
-			return;
+		WorkerThreadPool::TaskID task_id_to_wait = WorkerThreadPool::INVALID_TASK_ID;
+
+		{
+			MutexLock local_lock(local_mutex);
+			if (!compilation_set.has(p_key_hash)) {
+				// The pipeline was never submitted, we can't wait for it.
+				return;
+			}
+
+			HashMap<uint32_t, WorkerThreadPool::TaskID>::Iterator task_it = compilation_tasks.find(p_key_hash);
+			if (task_it != compilation_tasks.end()) {
+				// Wait for and remove the compilation task if it exists.
+				task_id_to_wait = task_it->value;
+				compilation_tasks.remove(task_it);
+			}
 		}
 
-		HashMap<uint32_t, WorkerThreadPool::TaskID>::Iterator task_it = compilation_tasks.find(p_key_hash);
-		if (task_it != compilation_tasks.end()) {
-			// Wait for and remove the compilation task if it exists.
-			WorkerThreadPool::get_singleton()->wait_for_task_completion(task_it->value);
-			compilation_tasks.remove(task_it);
+		if (task_id_to_wait != WorkerThreadPool::INVALID_TASK_ID) {
+			WorkerThreadPool::get_singleton()->wait_for_task_completion(task_id_to_wait);
 		}
 	}
 


Test patch: