Lock all ScriptCompilationSystem mutators and snapshot readers#150
Open
JustinMDotNet wants to merge 1 commit into
Open
Lock all ScriptCompilationSystem mutators and snapshot readers#150JustinMDotNet wants to merge 1 commit into
JustinMDotNet wants to merge 1 commit into
Conversation
Previously only the private Add(plugin, path) overload took m_mutex. Every other mutator (SetScriptsBlob, SetModdedScriptsBlob, RegisterNeverRefType, RegisterMixedRefType) and every reader (HasXxx, GetXxx, GetCompilationArgs, GetScriptPaths) ran with no synchronization. The v1 ABI lets plugin worker threads call the mutators while the script-compile hook on the game thread iterates the same containers, which can rehash an unordered_multimap or realloc a vector mid-read. Take m_mutex in every public mutator and every public accessor. Make m_mutex mutable so the const accessors can lock it. Switch the list/map getters from returning const T& to returning T by value so callers can safely use the result after the lock is released. Take the lock for the full iteration in GetCompilationArgs as well. The two consumers in Hooks/ExecuteProcess.cpp and Hooks/LoadScripts.cpp still compile unchanged: range-for over an rvalue container extends the temporary's lifetime, and the ternaries deduce the same common type as before. Fixes wopss#140 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.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.
Closes #140.
What this changes
Three pieces:
ScriptCompilationSystemnow takesm_mutex. Previously only the privateAdd(plugin, path)overload was locked;SetScriptsBlob,SetModdedScriptsBlob,RegisterNeverRefType,RegisterMixedRefType, and everyHasXxx/GetXxxran with no synchronization.const T&.GetScriptPaths,GetNeverRefTypes,GetMixedRefTypes,GetScriptsBlob,GetModdedScriptsBlobtake the lock, snapshot the container, and return the copy. This makes the returned object safe to use after the lock is released.GetCompilationArgstakes the lock for its full iteration ofm_scriptPaths. Previously it iterated unlocked while plugin workers could be inserting viaAdd.m_mutexis nowmutableso the const accessors can lock it.Why
v1 plugins can call these from any thread, but the script-compile hook runs on the game thread. Concrete races the previous code allowed:
RegisterNeverRefTypewhile the compile hook iteratesm_neverRefTypesinExecuteProcess.cpp:117.vector::emplace_backcan reallocate mid-iteration.m_scriptPathsinGetCompilationArgswhile another worker is inside the lockedAdd(plugin, path)— that worker's mutation could triggerunordered_multimaprehash mid-iteration.RegisterMixedRefTypecorrupt the vector because the inner write was never locked.Call site impact
Two callers were touched:
ExecuteProcess.cpp:96, 102, 112, 117, 122— keeps compiling because the range-forover an rvalueMap_t/vectorextends the temporary's lifetime for the duration of the loop, and the ternary in:96now produces an rvaluepaththat decays into the existingconst auto blobPathbinding identically.LoadScripts.cpp:22-24— unchanged in shape;.string()is called on the rvaluepathinstead of an lvalue reference and produces the samestd::stringeither way.Design choices
sccinvocation, not per frame), so the simplicity wins. If profiling ever flags this, the alternative is to expose aWithScriptPaths(callback)style scoped accessor.HasScriptsBlob()+GetScriptsBlob()accessor. There is a remaining TOCTOU between those two calls — a plugin worker setting the blob between the test and the get would still produce a "consistent but stale" read. The fix for that is API shape, not locking. Worth a follow-up issue if you want me to file one.std::stringby value now lock around the move-into-vector; the move is cheap so the critical section stays small.What I tested
Local rebuild and a read-through of the diff against both call sites. No runtime tests — the race is hard to repro on demand. The new lock placement matches the existing
Add(plugin, path)overload's pattern exactly.