Fix: SendCommand raced Tick against the command, corrupting state on WebPlayer#1837
Merged
Conversation
…WebPlayer
When elapsedTime > 0, SendCommand launched Tick(0) and HandleCommandAsyncInternal
concurrently (both fire-and-forget). Both read/mutate the same Elements/Fields
collections, so on WebPlayer's real thread pool this could corrupt a Dictionary
mid-turn ("Operations that change non-concurrent collections must have exclusive
access"), which then throws on every subsequent access until the process restarts.
WasmPlayer's single-threaded runtime can't manifest true corruption, so it looked
fine there.
This was reachable but rare until #1835 restored elapsed-time tracking after
wait/pause/ask/menu resolve, making elapsedTime > 0 the common case for any
wait-heavy game and turning a latent race into a reliably reproducible one.
Now Tick(0) runs to completion before the command starts, instead of racing it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SendCommandlaunchedTick(0)andHandleCommandAsyncInternalconcurrently (both fire-and-forget) wheneverelapsedTime > 0. Both read/mutate the sameElements/Fieldscollections, so on WebPlayer's real thread pool this could corrupt aDictionarymid-turn ("Operations that change non-concurrent collections must have exclusive access"), which then throws on every subsequent access until the process restarts. WasmPlayer's single-threaded runtime can't manifest true corruption, so it looked fine there.wait/pause/ask/menuresolve, soelapsedTime > 0became the common case for any wait-heavy game, turning a latent race into a reliably reproducible one (reported against the game "Oracle Feels Confused", which leans heavily onwait { }).Tick(0)now runs to completion before the command starts, instead of racing it.Test plan
SendCommand_ElapsedTimeTriggersTimer_TimerRunsBeforeCommand(tests/EngineTests/CallbackTests.cs), backed by a new timer/command pair incallbacktest.aslxdotnet test --configuration Release, 319/319)🤖 Generated with Claude Code