Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDScript: Cancel suspended functions when reloading a script #102521

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 22 additions & 15 deletions modules/gdscript/gdscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,27 @@ void GDScript::clear(ClearData *p_clear_data) {
}
}

void GDScript::cancel_pending_functions(bool warn) {
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);

while (SelfList<GDScriptFunctionState> *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;
Expand All @@ -1640,21 +1661,7 @@ GDScript::~GDScript() {

clear();

{
MutexLock lock(GDScriptLanguage::get_singleton()->mutex);

while (SelfList<GDScriptFunctionState> *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);
Expand Down
3 changes: 3 additions & 0 deletions modules/gdscript/gdscript.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ class GDScript : public Script {

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.

Expand Down
2 changes: 2 additions & 0 deletions modules/gdscript/gdscript_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2660,6 +2660,8 @@ Error GDScriptCompiler::_prepare_compilation(GDScript *p_script, const GDScriptP

p_script->clearing = true;

p_script->cancel_pending_functions(true);

p_script->native = Ref<GDScriptNativeClass>();
p_script->base = Ref<GDScript>();
p_script->_base = nullptr;
Expand Down
7 changes: 7 additions & 0 deletions modules/gdscript/gdscript_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,13 @@ class GDScriptFunctionState : public RefCounted {
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();

Expand Down
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GDTEST_RUNTIME_ERROR
>> WARNING: Canceling suspended execution of "test" due to a script reload.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
static func test(a):
await a.finished
pass