Skip to content

Commit 070ac8d

Browse files
committed
Merge pull request godotengine#85248 from RandomShaper/fix_gdlamb_doublefree_2
Fix lambda cross-thread dynamics (take 2)
2 parents bb63963 + bfe66ab commit 070ac8d

File tree

5 files changed

+118
-97
lines changed

5 files changed

+118
-97
lines changed

core/templates/list.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ class List {
132132
data->erase(this);
133133
}
134134

135+
void transfer_to_back(List<T, A> *p_dst_list);
136+
135137
_FORCE_INLINE_ Element() {}
136138
};
137139

@@ -762,4 +764,41 @@ class List {
762764
}
763765
};
764766

767+
template <class T, class A>
768+
void List<T, A>::Element::transfer_to_back(List<T, A> *p_dst_list) {
769+
// Detach from current.
770+
771+
if (data->first == this) {
772+
data->first = data->first->next_ptr;
773+
}
774+
if (data->last == this) {
775+
data->last = data->last->prev_ptr;
776+
}
777+
if (prev_ptr) {
778+
prev_ptr->next_ptr = next_ptr;
779+
}
780+
if (next_ptr) {
781+
next_ptr->prev_ptr = prev_ptr;
782+
}
783+
data->size_cache--;
784+
785+
// Attach to the back of the new one.
786+
787+
if (!p_dst_list->_data) {
788+
p_dst_list->_data = memnew_allocator(_Data, A);
789+
p_dst_list->_data->first = this;
790+
p_dst_list->_data->last = nullptr;
791+
p_dst_list->_data->size_cache = 0;
792+
prev_ptr = nullptr;
793+
} else {
794+
p_dst_list->_data->last->next_ptr = this;
795+
prev_ptr = p_dst_list->_data->last;
796+
}
797+
p_dst_list->_data->last = this;
798+
next_ptr = nullptr;
799+
800+
data = p_dst_list->_data;
801+
p_dst_list->_data->size_cache++;
802+
}
803+
765804
#endif // LIST_H

modules/gdscript/gdscript.cpp

Lines changed: 65 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,108 +1391,51 @@ String GDScript::debug_get_script_name(const Ref<Script> &p_script) {
13911391
}
13921392
#endif
13931393

1394-
thread_local GDScript::UpdatableFuncPtr GDScript::func_ptrs_to_update_thread_local;
1395-
GDScript::UpdatableFuncPtr *GDScript::func_ptrs_to_update_main_thread = &func_ptrs_to_update_thread_local;
1394+
GDScript::UpdatableFuncPtr GDScript::func_ptrs_to_update_main_thread;
1395+
thread_local GDScript::UpdatableFuncPtr *GDScript::func_ptrs_to_update_thread_local = nullptr;
13961396

1397-
List<GDScript::UpdatableFuncPtrElement>::Element *GDScript::_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr) {
1398-
MutexLock lock(func_ptrs_to_update_mutex);
1399-
1400-
List<UpdatableFuncPtrElement>::Element *result = func_ptrs_to_update_elems.push_back(UpdatableFuncPtrElement());
1397+
GDScript::UpdatableFuncPtrElement GDScript::_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr) {
1398+
UpdatableFuncPtrElement result = {};
14011399

14021400
{
1403-
MutexLock lock2(func_ptrs_to_update_thread_local.mutex);
1404-
result->get().element = func_ptrs_to_update_thread_local.ptrs.push_back(p_func_ptr_ptr);
1405-
result->get().mutex = &func_ptrs_to_update_thread_local.mutex;
1401+
MutexLock lock(func_ptrs_to_update_thread_local->mutex);
1402+
result.element = func_ptrs_to_update_thread_local->ptrs.push_back(p_func_ptr_ptr);
1403+
result.func_ptr = func_ptrs_to_update_thread_local;
14061404

1407-
if (likely(func_ptrs_to_update_thread_local.initialized)) {
1405+
if (likely(func_ptrs_to_update_thread_local->initialized)) {
14081406
return result;
14091407
}
14101408

1411-
func_ptrs_to_update_thread_local.initialized = true;
1409+
func_ptrs_to_update_thread_local->initialized = true;
14121410
}
14131411

1414-
func_ptrs_to_update.push_back(&func_ptrs_to_update_thread_local);
1412+
MutexLock lock(func_ptrs_to_update_mutex);
1413+
func_ptrs_to_update.push_back(func_ptrs_to_update_thread_local);
1414+
func_ptrs_to_update_thread_local->rc++;
14151415

14161416
return result;
14171417
}
14181418

1419-
void GDScript::_remove_func_ptr_to_update(List<UpdatableFuncPtrElement>::Element *p_func_ptr_element) {
1420-
// None of these checks should ever fail, unless there's a bug.
1421-
// They can be removed once we are sure they never catch anything.
1422-
// Left here now due to extra safety needs late in the release cycle.
1423-
ERR_FAIL_NULL(p_func_ptr_element);
1424-
MutexLock lock(func_ptrs_to_update_thread_local.mutex);
1425-
ERR_FAIL_NULL(p_func_ptr_element->get().element);
1426-
ERR_FAIL_NULL(p_func_ptr_element->get().mutex);
1427-
MutexLock lock2(*p_func_ptr_element->get().mutex);
1428-
p_func_ptr_element->get().element->erase();
1429-
p_func_ptr_element->erase();
1419+
void GDScript::_remove_func_ptr_to_update(const UpdatableFuncPtrElement &p_func_ptr_element) {
1420+
ERR_FAIL_NULL(p_func_ptr_element.element);
1421+
ERR_FAIL_NULL(p_func_ptr_element.func_ptr);
1422+
MutexLock lock(p_func_ptr_element.func_ptr->mutex);
1423+
p_func_ptr_element.element->erase();
14301424
}
14311425

14321426
void GDScript::_fixup_thread_function_bookkeeping() {
14331427
// Transfer the ownership of these update items to the main thread,
14341428
// because the current one is dying, leaving theirs orphan, dangling.
14351429

1436-
HashSet<GDScript *> scripts;
1437-
14381430
DEV_ASSERT(!Thread::is_main_thread());
1439-
MutexLock lock(func_ptrs_to_update_main_thread->mutex);
14401431

1441-
{
1442-
MutexLock lock2(func_ptrs_to_update_thread_local.mutex);
1443-
1444-
while (!func_ptrs_to_update_thread_local.ptrs.is_empty()) {
1445-
// Transfer the thread-to-script records from the dying thread to the main one.
1446-
1447-
List<GDScriptFunction **>::Element *E = func_ptrs_to_update_thread_local.ptrs.front();
1448-
List<GDScriptFunction **>::Element *new_E = func_ptrs_to_update_main_thread->ptrs.push_front(E->get());
1449-
1450-
GDScript *script = (*E->get())->get_script();
1451-
if (!scripts.has(script)) {
1452-
scripts.insert(script);
1453-
1454-
// Replace dying thread by the main thread in the script-to-thread records.
1455-
1456-
MutexLock lock3(script->func_ptrs_to_update_mutex);
1457-
DEV_ASSERT(script->func_ptrs_to_update.find(&func_ptrs_to_update_thread_local));
1458-
{
1459-
for (List<UpdatableFuncPtrElement>::Element *F = script->func_ptrs_to_update_elems.front(); F; F = F->next()) {
1460-
bool is_dying_thread_entry = F->get().mutex == &func_ptrs_to_update_thread_local.mutex;
1461-
if (is_dying_thread_entry) {
1462-
// This may lead to multiple main-thread entries, but that's not a problem
1463-
// and allows to reuse the element, which is needed, since it's tracked by pointer.
1464-
F->get().element = new_E;
1465-
F->get().mutex = &func_ptrs_to_update_main_thread->mutex;
1466-
}
1467-
}
1468-
}
1469-
}
1432+
MutexLock lock(func_ptrs_to_update_main_thread.mutex);
1433+
MutexLock lock2(func_ptrs_to_update_thread_local->mutex);
14701434

1471-
E->erase();
1472-
}
1473-
}
1474-
func_ptrs_to_update_main_thread->initialized = true;
1475-
1476-
{
1477-
// Remove orphan thread-to-script entries from every script.
1478-
// FIXME: This involves iterating through every script whenever a thread dies.
1479-
// While it's OK that thread creation/destruction are heavy operations,
1480-
// additional bookkeeping can be used to outperform this brute-force approach.
1481-
1482-
GDScriptLanguage *gd_lang = GDScriptLanguage::get_singleton();
1483-
1484-
MutexLock lock2(gd_lang->mutex);
1485-
1486-
for (SelfList<GDScript> *s = gd_lang->script_list.first(); s; s = s->next()) {
1487-
GDScript *script = s->self();
1488-
for (List<UpdatableFuncPtr *>::Element *E = script->func_ptrs_to_update.front(); E; E = E->next()) {
1489-
bool is_dying_thread_entry = &E->get()->mutex == &func_ptrs_to_update_thread_local.mutex;
1490-
if (is_dying_thread_entry) {
1491-
E->erase();
1492-
break;
1493-
}
1494-
}
1495-
}
1435+
while (!func_ptrs_to_update_thread_local->ptrs.is_empty()) {
1436+
List<GDScriptFunction **>::Element *E = func_ptrs_to_update_thread_local->ptrs.front();
1437+
E->transfer_to_back(&func_ptrs_to_update_main_thread.ptrs);
1438+
func_ptrs_to_update_thread_local->transferred = true;
14961439
}
14971440
}
14981441

@@ -1516,12 +1459,29 @@ void GDScript::clear(ClearData *p_clear_data) {
15161459
{
15171460
MutexLock outer_lock(func_ptrs_to_update_mutex);
15181461
for (UpdatableFuncPtr *updatable : func_ptrs_to_update) {
1519-
MutexLock inner_lock(updatable->mutex);
1520-
for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) {
1521-
*func_ptr_ptr = nullptr;
1462+
bool destroy = false;
1463+
{
1464+
MutexLock inner_lock(updatable->mutex);
1465+
if (updatable->transferred) {
1466+
func_ptrs_to_update_main_thread.mutex.lock();
1467+
}
1468+
for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) {
1469+
*func_ptr_ptr = nullptr;
1470+
}
1471+
DEV_ASSERT(updatable->rc != 0);
1472+
updatable->rc--;
1473+
if (updatable->rc == 0) {
1474+
destroy = true;
1475+
}
1476+
if (updatable->transferred) {
1477+
func_ptrs_to_update_main_thread.mutex.unlock();
1478+
}
1479+
}
1480+
if (destroy) {
1481+
DEV_ASSERT(updatable != &func_ptrs_to_update_main_thread);
1482+
memdelete(updatable);
15221483
}
15231484
}
1524-
func_ptrs_to_update_elems.clear();
15251485
}
15261486

15271487
RBSet<GDScript *> must_clear_dependencies = get_must_clear_dependencies();
@@ -2141,8 +2101,25 @@ void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) {
21412101
named_globals.erase(p_name);
21422102
}
21432103

2104+
void GDScriptLanguage::thread_enter() {
2105+
GDScript::func_ptrs_to_update_thread_local = memnew(GDScript::UpdatableFuncPtr);
2106+
}
2107+
21442108
void GDScriptLanguage::thread_exit() {
21452109
GDScript::_fixup_thread_function_bookkeeping();
2110+
2111+
bool destroy = false;
2112+
{
2113+
MutexLock lock(GDScript::func_ptrs_to_update_thread_local->mutex);
2114+
DEV_ASSERT(GDScript::func_ptrs_to_update_thread_local->rc != 0);
2115+
GDScript::func_ptrs_to_update_thread_local->rc--;
2116+
if (GDScript::func_ptrs_to_update_thread_local->rc == 0) {
2117+
destroy = true;
2118+
}
2119+
}
2120+
if (destroy) {
2121+
memdelete(GDScript::func_ptrs_to_update_thread_local);
2122+
}
21462123
}
21472124

21482125
void GDScriptLanguage::init() {
@@ -2177,6 +2154,8 @@ void GDScriptLanguage::init() {
21772154
_add_global(E.name, E.ptr);
21782155
}
21792156

2157+
GDScript::func_ptrs_to_update_thread_local = &GDScript::func_ptrs_to_update_main_thread;
2158+
21802159
#ifdef TESTS_ENABLED
21812160
GDScriptTests::GDScriptTestRunner::handle_cmdline();
21822161
#endif
@@ -2226,6 +2205,8 @@ void GDScriptLanguage::finish() {
22262205
}
22272206
script_list.clear();
22282207
function_list.clear();
2208+
2209+
DEV_ASSERT(GDScript::func_ptrs_to_update_main_thread.rc == 1);
22292210
}
22302211

22312212
void GDScriptLanguage::profiling_start() {

modules/gdscript/gdscript.h

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,21 +121,23 @@ class GDScript : public Script {
121121
struct UpdatableFuncPtr {
122122
List<GDScriptFunction **> ptrs;
123123
Mutex mutex;
124-
bool initialized = false;
124+
bool initialized : 1;
125+
bool transferred : 1;
126+
uint32_t rc = 1;
127+
UpdatableFuncPtr() :
128+
initialized(false), transferred(false) {}
125129
};
126130
struct UpdatableFuncPtrElement {
127131
List<GDScriptFunction **>::Element *element = nullptr;
128-
Mutex *mutex = nullptr;
132+
UpdatableFuncPtr *func_ptr = nullptr;
129133
};
130-
static thread_local UpdatableFuncPtr func_ptrs_to_update_thread_local;
131-
static thread_local LocalVector<List<UpdatableFuncPtr *>::Element> func_ptrs_to_update_entries_thread_local;
132-
static UpdatableFuncPtr *func_ptrs_to_update_main_thread;
134+
static UpdatableFuncPtr func_ptrs_to_update_main_thread;
135+
static thread_local UpdatableFuncPtr *func_ptrs_to_update_thread_local;
133136
List<UpdatableFuncPtr *> func_ptrs_to_update;
134-
List<UpdatableFuncPtrElement> func_ptrs_to_update_elems;
135137
Mutex func_ptrs_to_update_mutex;
136138

137-
List<UpdatableFuncPtrElement>::Element *_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr);
138-
static void _remove_func_ptr_to_update(List<UpdatableFuncPtrElement>::Element *p_func_ptr_element);
139+
UpdatableFuncPtrElement _add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr);
140+
static void _remove_func_ptr_to_update(const UpdatableFuncPtrElement &p_func_ptr_element);
139141

140142
static void _fixup_thread_function_bookkeeping();
141143

@@ -561,6 +563,7 @@ class GDScriptLanguage : public ScriptLanguage {
561563

562564
/* MULTITHREAD FUNCTIONS */
563565

566+
virtual void thread_enter() override;
564567
virtual void thread_exit() override;
565568

566569
/* DEBUGGER FUNCTIONS */

modules/gdscript/gdscript_lambda_callable.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,5 @@ GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptF
296296
}
297297

298298
GDScriptLambdaSelfCallable::~GDScriptLambdaSelfCallable() {
299-
if (updatable_func_ptr_element) {
300-
GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element);
301-
}
299+
GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element);
302300
}

modules/gdscript/gdscript_lambda_callable.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class GDScriptLambdaCallable : public CallableCustom {
4545
GDScriptFunction *function = nullptr;
4646
Ref<GDScript> script;
4747
uint32_t h;
48-
List<GDScript::UpdatableFuncPtrElement>::Element *updatable_func_ptr_element = nullptr;
48+
GDScript::UpdatableFuncPtrElement updatable_func_ptr_element;
4949

5050
Vector<Variant> captures;
5151

@@ -72,7 +72,7 @@ class GDScriptLambdaSelfCallable : public CallableCustom {
7272
Ref<RefCounted> reference; // For objects that are RefCounted, keep a reference.
7373
Object *object = nullptr; // For non RefCounted objects, use a direct pointer.
7474
uint32_t h;
75-
List<GDScript::UpdatableFuncPtrElement>::Element *updatable_func_ptr_element = nullptr;
75+
GDScript::UpdatableFuncPtrElement updatable_func_ptr_element;
7676

7777
Vector<Variant> captures;
7878

0 commit comments

Comments
 (0)