Skip to content

Add lock to RegisterVariantCollection - #7077

Merged
DrSmugleaf merged 6 commits into
space-wizards:masterfrom
Mackamuir:proto_load_race
Nov 9, 2026
Merged

DrSmugleaf merged 6 commits into
space-wizards:masterfrom
Mackamuir:proto_load_race

Conversation

@Mackamuir

@Mackamuir Mackamuir commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Adds a lock around RegisterVariantCollection so that parallel prototype loading can't corrupt KindData.UnfrozenVariants.

Exception has occurred: CLR/System.ArgumentNullException
An exception of type 'System.ArgumentNullException' occurred in System.Private.CoreLib.dll but was not handled in user code: 'Value cannot be null.'
   at System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
   at System.Collections.Frozen.FrozenDictionary.ToFrozenDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
   at Robust.Shared.Prototypes.PrototypeManager.KindData.Freeze() in path\RobustToolbox\Robust.Shared\Prototypes\PrototypeManager.cs:line 1247
   at Robust.Shared.Prototypes.PrototypeManager.Freeze(IEnumerable`1 kinds) in path\RobustToolbox\Robust.Shared\Prototypes\PrototypeManager.cs:line 534
   at Robust.Shared.Prototypes.PrototypeManager.InstantiatePrototypes(KindData[] kinds, ValueTuple`4[] results, Channel`1 hooks) in path\RobustToolbox\Robust.Shared\Prototypes\PrototypeManager.cs:line 647
   at Robust.Shared.Prototypes.PrototypeManager.<>c__DisplayClass52_0.<InstantiateKinds>b__2() in path\RobustToolbox\Robust.Shared\Prototypes\PrototypeManager.cs:line 605
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)

// Freeze prototype variants associated with this kind
if (UnfrozenVariants != null)
{
Variants = UnfrozenVariants.ToFrozenDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<string>)kvp.Value);
UnfrozenVariants = null;
}

The issue is with this dict getting a null key, which it can't have, so thats bad.

Resolves #7199

Was a race condition before, but obviously if you run a million races it will hit eventually.

@metalgearsloth

Copy link
Copy Markdown
Contributor

This looks pretty suss as any number of methods above it will explode without thread-safety?

@Mackamuir

Copy link
Copy Markdown
Contributor Author

This looks pretty suss as any number of methods above it will explode without thread-safety?

Doubt it, each worker only edits node from the file it parsed, kindData is shared and unfrozenVariants is the only shared thing extractMapping writes to.

Honestly the chances of this happening are pretty low anyways, I was more curious and wanted to look into it. will just close and if it comes up again later for whatever reason someone can see here.

@Mackamuir Mackamuir closed this Sep 17, 2026
@Mackamuir Mackamuir reopened this Oct 13, 2026
@Mackamuir

Copy link
Copy Markdown
Contributor Author

Reopening this, since it's an issue

@metalgearsloth metalgearsloth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also needs release note.

@DrSmugleaf DrSmugleaf left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Might be better to use a concurrent dictionary for this, at least from my experience in serialization code they are faster
Either way note below if you choose not to

@Mackamuir

Mackamuir commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Might be better to use a concurrent dictionary for this, at least from my experience in serialization code they are faster Either way note below if you choose not to

Not sure if concurrent would be faster after initialization and freezing are included. If you want I can spend some time trying to get some benchmarks.

Would also mean making the initalization support threading but w/e

@DrSmugleaf

Copy link
Copy Markdown
Contributor

Might be better to use a concurrent dictionary for this, at least from my experience in serialization code they are faster Either way note below if you choose not to

Not sure if concurrent would be faster after initialization and freezing are included. If you want I can spend some time trying to get some benchmarks.

Would also mean making the initalization support threading but w/e

Probably fine to leave it be, either way it will come up in a future profile if it's an issue

@DrSmugleaf
DrSmugleaf merged commit 57d04e7 into space-wizards:master Nov 9, 2026
20 checks passed
@Mackamuir
Mackamuir deleted the proto_load_race branch November 9, 2026 22:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Race condition in PrototypeManager.RegisterVariantCollection during parallel LoadDirectory

3 participants