瀏覽代碼

Fix deadlocks related to ClassDB queries about global classes

`ClassDB::can_instantiate()` and other reflection methods deadlock if the type is an script global class, when such script indirectly uses a not-yet-registered class. The reason is the `ClassDB` read lock is still held when invoking the `ResourceLoader` to load the class script, which may in turn need to lock for writing (for the class registration).

In particular, this happens with some types related to animation tree, that aren't registered at engine startup, but can happen with others, especially ones from the user. Registration statements are also added for the animation-related types that were lacking them.
Pedro J. Estébanez 10 月之前
父節點
當前提交
a5f6e49862
共有 2 個文件被更改,包括 56 次插入35 次删除
  1. 53 35
      core/object/class_db.cpp
  2. 3 0
      scene/register_scene_types.cpp

+ 53 - 35
core/object/class_db.cpp

@@ -664,58 +664,76 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName &
 }
 
 bool ClassDB::can_instantiate(const StringName &p_class) {
-	OBJTYPE_RLOCK;
+	String script_path;
+	{
+		OBJTYPE_RLOCK;
 
-	ClassInfo *ti = classes.getptr(p_class);
-	if (!ti) {
-		if (!ScriptServer::is_global_class(p_class)) {
-			ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+		ClassInfo *ti = classes.getptr(p_class);
+		if (!ti) {
+			if (!ScriptServer::is_global_class(p_class)) {
+				ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+			}
+			script_path = ScriptServer::get_global_class_path(p_class);
+			goto use_script; // Open the lock for resource loading.
 		}
-		String path = ScriptServer::get_global_class_path(p_class);
-		Ref<Script> scr = ResourceLoader::load(path);
-		return scr.is_valid() && scr->is_valid() && !scr->is_abstract();
-	}
 #ifdef TOOLS_ENABLED
-	if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
-		return false;
-	}
+		if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
+			return false;
+		}
 #endif
-	return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance));
+		return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance));
+	}
+
+use_script:
+	Ref<Script> scr = ResourceLoader::load(script_path);
+	return scr.is_valid() && scr->is_valid() && !scr->is_abstract();
 }
 
 bool ClassDB::is_abstract(const StringName &p_class) {
-	OBJTYPE_RLOCK;
+	String script_path;
+	{
+		OBJTYPE_RLOCK;
 
-	ClassInfo *ti = classes.getptr(p_class);
-	if (!ti) {
-		if (!ScriptServer::is_global_class(p_class)) {
-			ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+		ClassInfo *ti = classes.getptr(p_class);
+		if (!ti) {
+			if (!ScriptServer::is_global_class(p_class)) {
+				ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+			}
+			script_path = ScriptServer::get_global_class_path(p_class);
+			goto use_script; // Open the lock for resource loading.
 		}
-		String path = ScriptServer::get_global_class_path(p_class);
-		Ref<Script> scr = ResourceLoader::load(path);
-		return scr.is_valid() && scr->is_valid() && scr->is_abstract();
+		return ti->creation_func == nullptr && (!ti->gdextension || ti->gdextension->create_instance == nullptr);
 	}
-	return ti->creation_func == nullptr && (!ti->gdextension || ti->gdextension->create_instance == nullptr);
+
+use_script:
+	Ref<Script> scr = ResourceLoader::load(script_path);
+	return scr.is_valid() && scr->is_valid() && scr->is_abstract();
 }
 
 bool ClassDB::is_virtual(const StringName &p_class) {
-	OBJTYPE_RLOCK;
+	String script_path;
+	{
+		OBJTYPE_RLOCK;
 
-	ClassInfo *ti = classes.getptr(p_class);
-	if (!ti) {
-		if (!ScriptServer::is_global_class(p_class)) {
-			ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+		ClassInfo *ti = classes.getptr(p_class);
+		if (!ti) {
+			if (!ScriptServer::is_global_class(p_class)) {
+				ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'.");
+			}
+			script_path = ScriptServer::get_global_class_path(p_class);
+			goto use_script; // Open the lock for resource loading.
 		}
-		String path = ScriptServer::get_global_class_path(p_class);
-		Ref<Script> scr = ResourceLoader::load(path);
-		return scr.is_valid() && scr->is_valid() && scr->is_abstract();
-	}
 #ifdef TOOLS_ENABLED
-	if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
-		return false;
-	}
+		if ((ti->api == API_EDITOR || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) {
+			return false;
+		}
 #endif
-	return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance) && ti->is_virtual);
+		return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance) && ti->is_virtual);
+	}
+
+use_script:
+	Ref<Script> scr = ResourceLoader::load(script_path);
+	return scr.is_valid() && scr->is_valid() && scr->is_abstract();
 }
 
 void ClassDB::_add_class2(const StringName &p_class, const StringName &p_inherits) {

+ 3 - 0
scene/register_scene_types.cpp

@@ -511,6 +511,9 @@ void register_scene_types() {
 	GDREGISTER_CLASS(AnimationNodeStateMachine);
 	GDREGISTER_CLASS(AnimationNodeStateMachinePlayback);
 
+	GDREGISTER_INTERNAL_CLASS(AnimationNodeStartState);
+	GDREGISTER_INTERNAL_CLASS(AnimationNodeEndState);
+
 	GDREGISTER_CLASS(AnimationNodeSync);
 	GDREGISTER_CLASS(AnimationNodeStateMachineTransition);
 	GDREGISTER_CLASS(AnimationNodeOutput);