InvalidOperationException when ticking quests during async time due to modification during enumeration
Label: Multifaction, Async, Bug
Bug in class: MultiplayerAsyncQuest System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
Description
An InvalidOperationException is thrown during .TickQuests() because the underlying quest collection is modified during enumeration. This occurs when a quest ends during QuestTick(), triggering a Harmony postfix on Quest.End(). The postfix removes the quest from the cache via TryRemoveCachedQuest, which modifies the list currently being iterated.
Context
This issue occurred while testing #508. In that PR, ancient exoremains are spawned on maps for each faction. After destroying the remains and defeating the threat from the ship, players can extract a mecha link. Implanting the link triggers a hidden quest. Once the quest finishes, the exception is thrown. (Quest: ..\RimWorld\Data\Biotech\Defs\QuestScriptDefs\Script_StartingMech.xml)
While this was triggered by a specific Biotech quest, it’s likely that similar behavior can occur with other quests that complete during a tick.
Steps to Reproduce
- Host a multiplayer game with async time, multifaction, and Biotech enabled.
- (Optional) Use dev tools to give pawns strong gear and high shooting skills.
- Destroy the ancient exorider remains.
- Decrypt the mech transponder.
- Accept the resulting quest.
- Defeat the threat that arrives via ship.
- Extract the mecha chip.
- Implant the chip.
- After a few ticks, a hidden quest finishes.
- You receive the reward and the error is thrown
Potential fix
Modify the RemoveQuestFromCacheOnQuestEnd Harmony postfix on Quest.End() so that it adds the quest to a pendingRemovals list instead of removing it immediately. Then, after all quests are ticked in TickQuests, iterate over pendingRemovals and remove them safely.