Skip to content

Resource_Manager::Debug and Storage_Schema::ICurrentData() races surfaced by #371 (upstream OCCT#1398) #374

Description

@gsdali

Background

#371 replaced our bridge's use of the shared XCAFApp_Application::GetApplication() singleton
with a private TDocStd_Application per document, closing our exposure to the #341/#344/#349/#353
race family. A dedicated confirmation harness built to test that new pattern (private app per
thread, zero shared state, zero serialization, run against the real TSan-instrumented kernel)
found it was not a clean win: it surfaced two previously uncaught OCCT races that only become
reachable once application construction itself runs concurrently, something the old shared
singleton never allowed to happen.

Root cause

  1. Resource_Manager::Resource_Manager(const char*, bool) (Resource_Manager.cxx:107-109) writes
    a file-scope global on every construction, unsynchronized:

    OSD_Environment envDebug("ResourceDebug");
    Debug = (!envDebug.Value().IsEmpty());   // static bool Debug; at file scope, line 54

    Reached via TDocStd_Application::Resources() (called from DefineFormat()) constructing its
    lazily initialized Resource_Manager. Resources()'s own lazy init is already mutex guarded
    per instance (the Uncatchable SIGSEGV in parallel swift test run, right after concurrent OBJ imports — possibly related to #341, unconfirmed #344 fix), but that only protects one TDocStd_Application instance calling
    Resources() twice, not N different instances each constructing their own Resource_Manager
    concurrently, all writing the same global Debug.

  2. Storage_Schema::ICurrentData() (Storage_Schema.cxx:802-805) is a function-local static
    Handle(Storage_Data), thread-safe to initialize (C++11 magic statics), but every
    Storage_Schema instance mutates the handle it returns with no synchronization at all.
    Storage_Schema::Storage_Schema()'s constructor calls Clear(), which does
    Storage_Schema::ICurrentData().Nullify(), so constructing a new Storage_Schema on one
    thread can null out the handle another thread's in-flight (de)serialization is concurrently
    reading via ICurrentData()->InternalData()->.... Reached via
    PCDM_ReadWriter_1::ReadDocumentVersion during TDocStd_Application::Open().

Neither had ever surfaced in this project's prior TSan gates (#298/#319/#341/#344/#345/#348/#349/
#353), because every one of them, and all of production until #371, shared one application
instance. Resources()'s own per-instance lazy-init mutex accidentally serialized
Resource_Manager/Storage_Schema construction down to "runs once, ever, for the whole process."

Evidence

ThreadSanitizer (minimal-module build: FoundationClasses+ModelingData+ModelingAlgorithms+
DataExchange), 8 threads x 50 rounds, each round a brand new private app, DefineFormat,
SaveAs, Close, then a separate brand new private app, Open, verify, Close. No two threads
ever touch the same TDocStd_Application instance. 16 race warnings, 0 functional failures.

Upstream status

Filed as OCCT#1398, not yet fixed in the
kernel. Reproducer: Scripts/repro/371-getapplication-singleton-elimination/occt_371_private_app.cpp.

Fix scope

Same pattern as #341/#344/#349/#353: a minimal, surgical mutex guarding the shared global rather
than a structural redesign.

Needs its own TSan-driven investigation before a patch is proposed upstream, per this project's
usual protocol (docs/thread-safety.md).

Practical status in this repo right now

Not blocking. ocafStoreMutex()'s coverage was expanded in #371 to also wrap the six
OCCTDocumentDefineFormatBin/BinL/Xml/XmlL/BinXCAF/XmlXCAF functions and
OCCTDocumentCreateWithFormat, so our own bridge is already safe against both races (confirmed:
adding an equivalent mutex to a copy of the harness gives 0 TSan warnings). This issue tracks
proposing an actual kernel fix upstream, not an outstanding safety gap in OCCTSwift itself.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions