Skip to content

Prevent re-entrant script class update mutation - #5

Open
dsarno wants to merge 710 commits into
masterfrom
fix/editor-filesystem-script-class-reentry
Open

Prevent re-entrant script class update mutation#5
dsarno wants to merge 710 commits into
masterfrom
fix/editor-filesystem-script-class-reentry

Conversation

@dsarno

@dsarno dsarno commented May 16, 2026

Copy link
Copy Markdown
Owner

Problem

This is related to the Task 'update_scripts_classes' already exists symptom reported in godotengine#115460, but fixes the issue from the EditorFileSystem side. It does not change ProgressDialog behavior globally.

This was found while developing and stress testing godot-ai, which exercises editor automation paths that repeatedly update tool scripts during editor startup. The minimal repro reduces that workload to a small @tool script that calls EditorFileSystem.update_file() during editor processing:

https://github.com/dsarno/godot-115460-gdscript-repro

EditorFileSystem::_update_script_classes() and _update_script_documentation() create EditorProgress tasks when more than one script is pending. In the GUI editor, EditorProgress can pump Main::iteration() through ProgressDialog::_update_ui().

That nested editor frame can run @tool script code and recursively enter pending script update processing while the outer pass is still active. For script class updates, this lets an inner _update_script_classes() mutate or clear update_script_paths while an outer _update_script_classes() range loop is still iterating it.

The crash can surface later while registering/removing global classes, but the corrupting invariant is the re-entrant mutation of the live pending container. The same re-entry path can also duplicate the documentation progress task.

Fix

  • Move-drain update_script_paths into a local HashMap before creating EditorProgress or iterating script class updates.
  • Move-drain update_script_paths_documentation into a local HashSet before creating its progress task.
  • Add an _process_update_pending() re-entry guard with a pending-rerun bit, so updates queued by nested editor frames are preserved for a later pass instead of being processed recursively.
  • Route the pre-reimport script class update path through the same guard while preserving the existing class-before-documentation ordering.

If a tool script continuously queues new script class updates during every pumped editor frame, documentation updates can still be delayed until that update stream settles. This PR keeps the fix scoped to pending script update processing instead of changing progress dialog behavior globally.

Testing

Built locally:

scons platform=macos target=editor dev_build=yes arch=arm64 vulkan=no tests=yes -j8

Focused doctest suites:

bin/godot.macos.editor.dev.arm64 --headless --test --test-suite='*[GDScript]*,*[Editor]*'

Result: 6 test cases passed, 58,688 assertions passed.

Regression repro verification with the MRP linked above:

  • GENERATED_SCRIPT_COUNT := 2, MAX_TICKS := 80: finished without crash, no duplicate update_scripts_classes, no duplicate update_script_paths_documentation, no !tasks.has assertion.
  • GENERATED_SCRIPT_COUNT := 2, MAX_TICKS := 0: smooth editor load, finished without crash, max_re_entry=1.
  • GENERATED_SCRIPT_COUNT := 1, headless control: finished without crash, max_re_entry=1.

No automated regression test is included because the proven failure mechanism requires GUI ProgressDialog pumping Main::iteration() during editor filesystem processing. The current headless doctest harness does not directly exercise that GUI progress re-entry path without a synthetic editor integration harness.

mihe and others added 30 commits April 30, 2026 10:47
SCons: Drop VS2017 support, use C17 universally
…ion-preview

Import: Fix generated collision preview
…inding-refresh-on-rename

Fix missing name refresh on bindings when changing the OpenXR action map.
…gative-timescale

Fix negative time scale regression in BlendSpace1D and BlendSpace2D
Fix undo crash when animation editor is not active
Update layer selector when modifying the `TileMap` in the inspector
Add error to error messages in `FileAccessPack`
…shift_enter

Add Shift+Enter and Shift+KpEnter as default shortcuts for newline
…andling-reprojected

Filter and sanitize volumetric fog to stop invalid values flooding the screen with black pixels through temporal reprojection.
…scription

Fix item focused signal description
…t-textures

Fix GPU validation errors due to area light LUT format
…invert-mess-det-something-something

Fix error when CPUParticles3D has `scale` set to `(0, 0, 0)`
Animation: Only erase properties for a specific node
Fixes an issue with not being able to set overrun behaviour on tags
without the button size zeroing out.
…-clear-button

Improve 'Clear Output' button position
Dependency editor: fix label, it's the owners, not the dependencies of files to delete
Add cleanup of `EditorInspector` clipboard contents
…pestring_typo

Fix typo in C# string interpolation in `TYPE_STRING` doc
Repiteo and others added 23 commits May 14, 2026 09:06
…tor-tooltips

Fix inspector tooltips after focus changes
Fix `PopupMenu` submenu positioning not accounting for margins
…l_params

Fix shader crash when using parameter name identical to uniform name
…be-interpolation-at-lightmap-edges

Fix LightmapGI probe interpolation at lightmap AABB edges
Fix ordering of notes in the description of the `seed()` function in `RandomNumberGenerator`
The `Engine::Singleton` struct stores a raw `Object *`, so registering
a `RefCounted` only works if some external `Ref<>` keeps it alive. This
pattern is fragile and can lead to UB, especially with GDExtension.
…ror_fix

Fix error spam when resizing a control in a zero size parent with anchors mode enabled
…aught_me_offguard_in_my_own_project

Fix wrong path in `EditorExportPlugin._export_begin()` when zipping
…u_got_there

Fix various editor state related regressions
…_fix

Fix misaligned timeline indicator on the Bezier editor
Wayland: Fix incorrect fallback value for height in _xdg_popup_on_configure
[Android] Fix reported crashes from the Play store
…t3d-throwing-!is_inside_tree()

Fix `BoneAttachment3D` throws `!is_inside_tree()`
…ine-redesign

Improve inline shader preview layout
Phase out `RefCounted` singletons as UB pitfalls
…lete

Suppress GCC 16's `sfinae-incomplete` warning
[Accessibility] Do not set duplicate values for editor inspector elements.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a crash class of re-entrant mutations in EditorFileSystem pending script update containers by preventing recursive processing while an outer update pass is still iterating, which can occur when EditorProgress pumps Main::iteration() via the editor UI.

Changes:

  • Drain update_script_paths / update_script_paths_documentation into local containers before creating EditorProgress and iterating, preventing re-entrant mutation of the live pending containers.
  • Add _process_update_pending() re-entry guarding with a queued-rerun mechanism to defer nested update requests to a later pass instead of recursing.
  • Route the pre-reimport “update script classes first” path through the same guarded mechanism while preserving ordering.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
editor/file_system/editor_file_system.h Adds re-entry guard state and updates _process_update_pending signature to support class-only processing.
editor/file_system/editor_file_system.cpp Drains pending containers before iteration and introduces guarded, rerunnable pending-update processing to prevent re-entrant mutation crashes.
Comments suppressed due to low confidence (1)

editor/file_system/editor_file_system.cpp:2217

  • Similar to the script class updates, script_paths_to_update_documentation = update_script_paths_documentation; update_script_paths_documentation.clear(); copies the whole HashSet and then clears the original. If this set can get large, consider draining it via move/swap under the mutex to avoid O(n) copying/allocation churn (HashSet has move assignment available in core/templates/hash_set.h).
	HashSet<String> script_paths_to_update_documentation;
	{
		MutexLock update_script_lock(update_script_mutex);
		script_paths_to_update_documentation = update_script_paths_documentation;
		update_script_paths_documentation.clear();
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2166 to +2167
script_paths_to_update = update_script_paths;
update_script_paths.clear();
@dsarno
dsarno force-pushed the fix/editor-filesystem-script-class-reentry branch 2 times, most recently from c2f378b to 9e47ae9 Compare May 16, 2026 05:19
@dsarno
dsarno force-pushed the fix/editor-filesystem-script-class-reentry branch from 9e47ae9 to e2daef8 Compare May 16, 2026 05:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.