You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 oneXCAFApp_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 protected — GetApplication() 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.
Sources/OCCTBridge/src/OCCTBridge_Internal.h:107 — OCCTDocument's constructor (the one that
matters most: every Document the Swift API hands out goes through this)
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.
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.
Background
OCCT#1396 is our repro issue for #353. On
2026-07-23, maintainer gkv311 commented:
His sample constructs
new TDocStd_Application()directly and registers only the formats itneeds (
BinXCAFDrivers::DefineFormat(anApp)etc.), rather than going through the sharedsingleton.
What this means for us
Our whole
theAutoNaming/GetApplication()/CDF_Directory/PCDM_StorageDriver/myMetaDataLookUpTablerace cluster (#341, #344, #349, #353) traces back to every document inthe process sharing one
XCAFApp_Application::GetApplication()singleton. The kernel patcheswe 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 protected —GetApplication()is deliberately theonly way to build that class.
TDocStd_Application(the base class) has a public constructor and is directlyinstantiable — exactly what gkv311's sample does.
DefineFormatcalls 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::DefineFormatonthe app handle. The only thing tying us to the singleton is where that handle comes from.
CDF_Application::myDirectory/myReaders/myWritersandCDM_Application::myMetaDataLookUpTableare all per-instance fields, not statics (checked
CDF_Application.hxx,CDF_Directory.hxx).So a private
TDocStd_Applicationper document would make those fields exclusive to that onedocument 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:107—OCCTDocument's constructor (the one thatmatters most: every
Documentthe Swift API hands out goes through this)Sources/OCCTBridge/src/OCCTBridge_Document.mm:2499Sources/OCCTBridge/src/OCCTBridge_IO.mm:897,928,967,1393,2854,2874,2889(OBJ/PLY/GLTFimport/export scratch documents)
Proposed change: swap each for
Handle(TDocStd_Application) app = new TDocStd_Application();andkeep the existing
DefineFormatcalls (already present, just need to run on the new instanceinstead of the shared one).
OCCTDocument::app's declared type(
Sources/OCCTBridge/src/OCCTBridge_Internal.h:92) changes fromHandle(XCAFApp_Application)toHandle(TDocStd_Application).Open questions to resolve before shipping (ground-truth C++ test first, per release process):
XCAFApp_Application::InitDocument()sets theXCAFDoc_DocumentToolattribute on newdocuments; base
TDocStd_Application::InitDocument()doesn't. We already explicitly callXCAFDoc_DocumentTool::ShapeTool(doc->Main())etc. everywhere (get-or-create), so this islikely a no-op difference — confirm with a ground-truth test before relying on it.
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) becomesunnecessary once documents no longer share a driver cache — don't remove it without a TSan pass
confirming it's safe to drop.
Scripts/tsan-stress.sh all) + fullswift testbefore shipping, perdocs/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.