CNA-BUG-119: The XNB type-reader registry is process-wide and unsynchronised, and every reader construction writes to it

CNA snapshot 009d40f5  ·  Known Issues › Current bugs  ·  source links pinned to 009d40f5

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

Every ContentTypeReader constructor writes the unlocked process-wide TargetTypeNames map during each .xnb load, so two XNB loads on different threads race unless a GL renderer's context lease happens to serialise them; XNA and FNA lock this registry.

Identifier
CNA-BUG-119
Category
Bug
Subsystem
Content & XNB/CNB/CNJ
Status
Open
Verified against
CNA 009d40f5 (009d40f5dd085c4e674d3479675fac84b12b3e0a)
Severity
Medium (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
Microsoft::Xna::Framework::Content::ContentManager::Load<T>() / ReadAsset<T>() for .xnb assets on more than one thread; ContentTypeReaderManager

Expected behaviour

XNA 4.0 serialises its process-wide reader tables (Monitor.Enter in ContentTypeReaderManager) and FNA does the same (lock (locker)), so separate ContentManagers may load on separate threads. CNA intends the same for its own format: the CNB loader registry is guarded by a std::shared_mutex, and CnbHardeningTests states that programs build content managers on more than one thread.

Actual behaviour at TARGET

ContentTypeReaderManager keeps TypeCreators() and TargetTypeNames() as function-local static unordered_maps with no lock. ContentReader::InitializeTypeReaders constructs a fresh reader for every entry of every .xnb file, and each ContentTypeReader<T> constructor writes TargetTypeNames()[typeid(T)] through AssociateTargetType. Two threads loading .xnb files at once therefore write the same map concurrently: undefined behaviour, including a rehash during another thread's insertion. The only incidental serialisation is the renderer-thread context lease a ContentReader takes when its manager has a direct device pointer, which only the GL renderer families return. The per-manager asset cache is unsynchronised too, but that matches XNA's own ContentManager and is not part of this entry.

Source locations

Evidence

Checked by reading ContentTypeReaderManager.cpp, ContentTypeReader.hpp and ContentReader.cpp at 009d40f5; XNA's locking was read from the IL of Microsoft.Xna.Framework.Content.ContentTypeReaderManager and FNA's from its source. The race was not provoked and no ThreadSanitizer run was made.

Focused reproduction

Illustrative, not compiled: two threads, each with its own device-less ContentManager, each loading a different .xnb asset of the same reader type in a loop, under ThreadSanitizer; the expected report is a write/write race inside ContentTypeReaderManager::AssociateTargetType.

Current tests

ContentTypeReaderManagerTests.cpp is single-threaded. CnbHardeningTests.cpp (ConcurrentRegistrationLookupAndContentManagerConstructionAreRaceFree) covers only the CNB registry.

Regression test

The XNB counterpart of the CNB hardening test: several threads constructing managers and loading small .xnb fixtures concurrently, run under ThreadSanitizer, asserting only interleaving-independent outcomes.

Blast radius

Programs that load .xnb content on more than one thread at a time (a loading screen whose worker loads while the main thread also loads, or separate managers on worker threads) on renderer families that return no context lease, or with managers that reach their device only through the service provider. Single-threaded loading, CNB and loose-file loading, and GL renderers with a directly bound device are unaffected.

Workaround

Load .xnb content from one thread at a time (for example behind one application-level mutex around Load).

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index