diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 714e22c9d9e..9e0a65bc2f8 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1623,6 +1623,27 @@ void GDScript::clear(ClearData *p_clear_data) { } } +void GDScript::cancel_pending_functions(bool warn) { + MutexLock lock(GDScriptLanguage::get_singleton()->mutex); + + while (SelfList *E = pending_func_states.first()) { + // Order matters since clearing the stack may already cause + // the GDScriptFunctionState to be destroyed and thus removed from the list. + pending_func_states.remove(E); + GDScriptFunctionState *state = E->self(); +#ifdef DEBUG_ENABLED + if (warn) { + WARN_PRINT("Canceling suspended execution of \"" + state->get_readable_function() + "\" due to a script reload."); + } +#endif + ObjectID state_id = state->get_instance_id(); + state->_clear_connections(); + if (ObjectDB::get_instance(state_id)) { + state->_clear_stack(); + } + } +} + GDScript::~GDScript() { if (destructing) { return; @@ -1638,21 +1659,7 @@ GDScript::~GDScript() { clear(); - { - MutexLock lock(GDScriptLanguage::get_singleton()->mutex); - - while (SelfList *E = pending_func_states.first()) { - // Order matters since clearing the stack may already cause - // the GDScriptFunctionState to be destroyed and thus removed from the list. - pending_func_states.remove(E); - GDScriptFunctionState *state = E->self(); - ObjectID state_id = state->get_instance_id(); - state->_clear_connections(); - if (ObjectDB::get_instance(state_id)) { - state->_clear_stack(); - } - } - } + cancel_pending_functions(false); { MutexLock lock(GDScriptLanguage::get_singleton()->mutex); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 11e00b4c3c3..0f2b1c0778e 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -243,6 +243,9 @@ public: void clear(GDScript::ClearData *p_clear_data = nullptr); + // Cancels all functions of the script that are are waiting to be resumed after using await. + void cancel_pending_functions(bool warn); + virtual bool is_valid() const override { return valid; } virtual bool is_abstract() const override { return false; } // GDScript does not support abstract classes. diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index dc0a17ffe75..2fa58e35d3e 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -2659,6 +2659,8 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP p_script->clearing = true; + p_script->cancel_pending_functions(true); + p_script->native = Ref(); p_script->base = Ref(); p_script->_base = nullptr; diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index c44990ac80b..5638352363c 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -615,6 +615,13 @@ public: bool is_valid(bool p_extended_check = false) const; Variant resume(const Variant &p_arg = Variant()); +#ifdef DEBUG_ENABLED + // Returns a human-readable representation of the function. + String get_readable_function() { + return state.function_name; + } +#endif + void _clear_stack(); void _clear_connections(); diff --git a/modules/gdscript/tests/scripts/runtime/errors/reload_suspended_function.notest.gd b/modules/gdscript/tests/scripts/runtime/errors/reload_suspended_function.notest.gd new file mode 100644 index 00000000000..7ac8f99446c --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/reload_suspended_function.notest.gd @@ -0,0 +1,12 @@ +# TODO: This test is currently disabled since it triggers some complex memory leaks. Try enabling it again once GH-101830 is fixed. + +signal finished + +const scr: GDScript = preload("reload_suspended_function_helper.notest.gd") + +func test(): + @warning_ignore("UNSAFE_METHOD_ACCESS") + scr.test(self) + @warning_ignore("RETURN_VALUE_DISCARDED") + scr.reload(true) + finished.emit() diff --git a/modules/gdscript/tests/scripts/runtime/errors/reload_suspended_function.out b/modules/gdscript/tests/scripts/runtime/errors/reload_suspended_function.out new file mode 100644 index 00000000000..d86e986852f --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/reload_suspended_function.out @@ -0,0 +1,2 @@ +GDTEST_RUNTIME_ERROR +>> WARNING: Canceling suspended execution of "test" due to a script reload. diff --git a/modules/gdscript/tests/scripts/runtime/errors/reload_suspended_function_helper.notest.gd b/modules/gdscript/tests/scripts/runtime/errors/reload_suspended_function_helper.notest.gd new file mode 100644 index 00000000000..cc876f769c5 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/reload_suspended_function_helper.notest.gd @@ -0,0 +1,3 @@ +static func test(a): + await a.finished + pass