Skip to content

Stop using XCAFApp_Application::GetApplication() singleton — per-instance TDocStd_Application instead #371

Description

@gsdali

Background

OCCT#1396 is our repro issue for #353. On
2026-07-23, maintainer gkv311 commented:

I see no (good) reason to use a single global instance of TDocStd_Application or
XCAFApp_Application across concurrent threads. The method returning
XCAFApp_Application::GetApplication() exists solely for compatibility reasons - probably it
makes sense to add Standard_DEPRECATED attribute to it (you may find that it is not used in
OCCT code!).

Since OCCT 7.1 it is preferred to create TDocStd_Application instances at application side...

His sample constructs new TDocStd_Application() directly and registers only the formats it
needs (BinXCAFDrivers::DefineFormat(anApp) etc.), rather than going through the shared
singleton.

What this means for us

Our whole theAutoNaming/GetApplication()/CDF_Directory/PCDM_StorageDriver/
myMetaDataLookUpTable race cluster (#341, #344, #349, #353) traces back to every document in
the process sharing one XCAFApp_Application::GetApplication() singleton. The kernel patches
we carry for those are legitimate and worth keeping upstream (other OCCT consumers do use the
sanctioned singleton pattern) — but our own exposure to #344, #349, and #353 specifically looks
self-inflicted
, avoidable by not sharing the singleton in the first place.

Verified against the actual headers (not assumed):

  • XCAFApp_Application's constructor is protectedGetApplication() is deliberately the
    only way to build that class.
  • TDocStd_Application (the base class) has a public constructor and is directly
    instantiable — exactly what gkv311's sample does.
  • Our bridge already does the manual DefineFormat calls he shows —
    Sources/OCCTBridge/src/OCCTBridge_Document.mm:2449-2617 (OCCTDocumentDefineFormatBin/BinL/Xml/XmlL/BinXCAF/XmlXCAF,
    plus the inline block at 2502-2505 and 2614-2617) already call BinDrivers::DefineFormat,
    XmlDrivers::DefineFormat, BinXCAFDrivers::DefineFormat, XmlXCAFDrivers::DefineFormat on
    the app handle. The only thing tying us to the singleton is where that handle comes from.
  • CDF_Application::myDirectory/myReaders/myWriters and CDM_Application::myMetaDataLookUpTable
    are all per-instance fields, not statics (checked CDF_Application.hxx, CDF_Directory.hxx).
    So a private TDocStd_Application per document would make those fields exclusive to that one
    document by construction — no cross-thread contention possible, no lock needed.

Scope

Singleton acquisition (Handle(XCAFApp_Application) app = XCAFApp_Application::GetApplication();)
appears at:

  • Sources/OCCTBridge/src/OCCTBridge_Internal.h:107OCCTDocument's constructor (the one that
    matters most: every Document the Swift API hands out goes through this)
  • Sources/OCCTBridge/src/OCCTBridge_Document.mm:2499
  • Sources/OCCTBridge/src/OCCTBridge_IO.mm:897,928,967,1393,2854,2874,2889 (OBJ/PLY/GLTF
    import/export scratch documents)

Proposed change: swap each for Handle(TDocStd_Application) app = new TDocStd_Application(); and
keep the existing DefineFormat calls (already present, just need to run on the new instance
instead of the shared one). OCCTDocument::app's declared type
(Sources/OCCTBridge/src/OCCTBridge_Internal.h:92) changes from Handle(XCAFApp_Application) to
Handle(TDocStd_Application).

Open questions to resolve before shipping (ground-truth C++ test first, per release process):

  • XCAFApp_Application::InitDocument() sets the XCAFDoc_DocumentTool attribute on new
    documents; base TDocStd_Application::InitDocument() doesn't. We already explicitly call
    XCAFDoc_DocumentTool::ShapeTool(doc->Main()) etc. everywhere (get-or-create), so this is
    likely a no-op difference — confirm with a ground-truth test before relying on it.
  • Whether ocafStoreMutex() (OCCTBridge_Document.mm, shipped v1.15.6 for Concurrent Save/SaveAs to the same format corrupts a shared, cached storage driver instance (SIGSEGV) #349) becomes
    unnecessary once documents no longer share a driver cache — don't remove it without a TSan pass
    confirming it's safe to drop.
  • Full TSan stress (Scripts/tsan-stress.sh all) + full swift test before shipping, per
    docs/thread-safety.md.

Relationship to other issues

Scoping fallout from #342 (bridge-level thread-handling contract), same family as #359/#361/#367.
Kernel patches for #341/#344/#349/#353 stay — they're correct fixes for the sanctioned singleton
pattern other OCCT consumers use. This issue is about our bridge no longer needing to hit those
code paths at all.

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