DataExchange, XCAF - AutoNamingScope guards XCAFDoc_ShapeTool::theAutoNaming#1388
DataExchange, XCAF - AutoNamingScope guards XCAFDoc_ShapeTool::theAutoNaming#1388gsdali wants to merge 1 commit into
Conversation
62f9c59 to
d0312c2
Compare
|
I think that a mutex is not the right tool here and I don't quite understand how current patch fixes concurrency issue as What I would suggest is to add local property overriding global value as a ---------------------- src/XCAFDoc/XCAFDoc_ShapeTool.hxx
Standard_EXPORT static Standard_Boolean AutoNaming();
-
+
+ //! Return autonaming flag for this tool instance.
+ Standard_Boolean OwnAutoNaming() const
+ {
+ return myOwnAutonaming == -1 ? XCAFDoc_ShapeTool::AutoNaming() : myOwnAutonaming == 1;
+ }
+
+ //! Set autonaming flag for this tool instance.
+ void SetOwnAutoNaming (Standard_Boolean theOwnFlag)
+ {
+ myOwnAutonaming = theOwnFlag ? 1 : 0;
+ }
+
+ //! Reset autonaming flag for this tool instance to global state.
+ void UnsetOwnAutoNaming()
+ {
+ myOwnAutonaming = -1;
+ }
...
//! Makes a shape on label L to be a reference to shape refL
//! with location loc
- Standard_EXPORT static void MakeReference (const TDF_Label& L, const TDF_Label& refL, const TopLoc_Location& loc);
+ Standard_EXPORT void MakeReference (const TDF_Label& L, const TDF_Label& refL, const TopLoc_Location& loc);
...
- Standard_Boolean hasSimpleShapes;
-
+ Standard_Boolean hasSimpleShapes = false;
+ //! Define own autonaming property; -1 by default which means to use global flag
+ Standard_Integer myOwnAutonaming = -1;
};
...
--------------------- src/XCAFDoc/XCAFDoc_ShapeTool.cxx ----------------------
index 3b36233880..b3357c9d65 100644
@@ -454,7 +454,7 @@ void XCAFDoc_ShapeTool::MakeReference (const TDF_Label &L,
refNode->Remove(); // abv: fix against bug in TreeNode::Append()
mainNode->Append(refNode);
- if (theAutoNaming)
+ if (OwnAutoNaming())
SetLabelNameByLink(L);
}
@@ -529,7 +529,7 @@ TDF_Label XCAFDoc_ShapeTool::addShape (const TopoDS_Shape& S, const Standard_Boo
// }
A->SetShape(S);
- if (theAutoNaming)
+ if (OwnAutoNaming())
SetLabelNameByShape(ShapeLabel);
// if shape is Compound and flag is set, create assembly
@@ -537,7 +537,7 @@ TDF_Label XCAFDoc_ShapeTool::addShape (const TopoDS_Shape& S, const Standard_Boo
// mark assembly by assigning UAttribute
Handle(TDataStd_UAttribute) Uattr;
Uattr = TDataStd_UAttribute::Set ( ShapeLabel, XCAFDoc::AssemblyGUID() );
- if (theAutoNaming)
+ if (OwnAutoNaming())
TDataStd_Name::Set(ShapeLabel, TCollection_ExtendedString("ASSEMBLY"));
// iterate on components
@@ -1564,7 +1564,7 @@ Standard_Boolean XCAFDoc_ShapeTool::SetSHUO (const TDF_LabelSequence& labels,
TDF_TagSource aTag;
TDF_Label UpperSubL = aTag.NewChild( labels( 1 ) );
- if (theAutoNaming) {
+ if (OwnAutoNaming()) {
TCollection_ExtendedString Entry("SHUO");
TDataStd_Name::Set(UpperSubL, TCollection_ExtendedString( Entry ));
}
@@ -1575,7 +1575,7 @@ Standard_Boolean XCAFDoc_ShapeTool::SetSHUO (const TDF_LabelSequence& labels,
// add other next_usage occurrences.
for (i = 2; i <= labels.Length(); i++) {
TDF_Label NextSubL = aTag.NewChild( labels( i ) );
- if (theAutoNaming) {
+ if (OwnAutoNaming()) {
------------------------ src/XCAFDoc/XCAFDoc_Editor.cxx ------------------------
index 6897238bc3..b18adf6217 100644
@@ -68,8 +68,7 @@ Standard_Boolean XCAFDoc_Editor::Expand (const TDF_Label& theDoc,
return Standard_False;
}
Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool(theDoc);
- Standard_Boolean isAutoNaming = aShapeTool->AutoNaming();
- aShapeTool->SetAutoNaming(Standard_False);
+ aShapeTool->SetOwnAutoNaming(false);
TDF_Label aCompoundPartL = theShape;
if (aShapeTool->IsReference(theShape))
@@ -137,10 +136,10 @@ Standard_Boolean XCAFDoc_Editor::Expand (const TDF_Label& theDoc,
}
}
}
- aShapeTool->SetAutoNaming(isAutoNaming);
+ aShapeTool->UnsetOwnAutoNaming();
return Standard_True;
}
- aShapeTool->SetAutoNaming(isAutoNaming);
+ aShapeTool->UnsetOwnAutoNaming();
return Standard_False;
}
----------------------- src/RWMesh/RWMesh_CafReader.cxx -----------------------
index 4164e4496f..d021a51043 100644
@@ -178,11 +178,10 @@ void RWMesh_CafReader::fillDocument()
Message::SendWarning("Warning: Length unit of document not equal to the system length unit");
}
- const Standard_Boolean wasAutoNaming = XCAFDoc_ShapeTool::AutoNaming();
- XCAFDoc_ShapeTool::SetAutoNaming (Standard_False);
const TCollection_AsciiString aRootName; // = generateRootName (theFile);
CafDocumentTools aTools;
aTools.ShapeTool = XCAFDoc_DocumentTool::ShapeTool (myXdeDoc->Main());
+ aTools.ShapeTool->SetOwnAutoNaming (false);
aTools.ColorTool = XCAFDoc_DocumentTool::ColorTool (myXdeDoc->Main());
aTools.VisMaterialTool = XCAFDoc_DocumentTool::VisMaterialTool (myXdeDoc->Main());
for (TopTools_SequenceOfShape::Iterator aRootIter (myRootShapes); aRootIter.More(); aRootIter.Next())
@@ -190,7 +189,7 @@ void RWMesh_CafReader::fillDocument()
addShapeIntoDoc (aTools, aRootIter.Value(), TDF_Label(), aRootName);
}
XCAFDoc_DocumentTool::ShapeTool (myXdeDoc->Main())->UpdateAssemblies();
- XCAFDoc_ShapeTool::SetAutoNaming (wasAutoNaming);
+ aTools.ShapeTool->UnsetOwnAutoNaming();
}In this way, the API with the global setting will remain working, while allowing to disable auto-naming locally within specific algorithms. |
…rride Follow-up to review feedback on the AutoNamingScope fix (a std::recursive_mutex-guarded save/restore of the single process-wide theAutoNaming flag): the mutex serialized the three known override call sites against each other, but every other read of theAutoNaming in XCAFDoc_ShapeTool.cxx (AddShape, MakeReference, SetSHUO) stayed outside any scope, so an unrelated, unscoped caller on another thread could still observe another thread's temporary override. Locking made the raw flag access memory-safe; it did not make the override behavior correct for callers outside the three scoped sites. theAutoNaming was never meant to express per-document intent -- the three call sites that override it (RWMesh_CafReader::fillDocument(), RWGltf_CafReader::fillDocument(), XCAFDoc_Editor::Expand()) all want to suppress naming for one document's build, not change a process-wide setting. XCAFDoc_ShapeTool is already one instance per document, so the override belongs there instead of on a shared global. XCAFDoc_ShapeTool gets an own-override field (myOwnAutonaming: -1 inherits the process-wide default, 0/1 is a local override) and OwnAutoNaming()/SetOwnAutoNaming()/UnsetOwnAutoNaming() accessors. AddShape, MakeReference and SetSHUO read OwnAutoNaming() instead of theAutoNaming directly. MakeReference is no longer static, since it now reads instance state. OwnAutoNamingScope replaces AutoNamingScope as the RAII helper, saving and restoring the instance's own override (not a shared flag) -- no locking needed, since two threads working on two different documents never touch each other's state, and nested scopes on the same instance (Expand() recurses into itself) compose correctly because each one restores exactly the override state it observed on entry, not an unconditional "off". Verified with the same TSan stress as the prior fix (10 threads x 200 iterations, obj_roundtrip_unique): zero theAutoNaming races, matching the previous result. Added a second stress scenario exercising the property the mutex fix couldn't guarantee: half the threads locally force auto-naming off via OwnAutoNamingScope on their own document, concurrently with the other half doing plain unscoped AddShape() on independent documents relying on the process-wide default -- 3000 operations, zero instances of the unscoped threads observing another thread's override. theAutoNaming itself stays std::atomic<bool>: SetAutoNaming()/ AutoNaming() remain callable concurrently from any thread at any time, independent of any instance's own override. Reported as issue Open-Cascade-SAS#1387.
d0312c2 to
7673779
Compare
|
@gkv311 Thank you for the review — and you're right, a mutex isn't the right tool here. We'd used a mutex-guarded save/restore successfully for a couple of similar unsynchronized-global-state issues elsewhere in this codebase, so that was the pattern we defaulted to without stopping to ask whether the state should have been global in the first place. In hindsight this is a clearly better fix: Pushed an update using your suggested design: Re-verified with the same ThreadSanitizer stress as before (10 threads x 200 iterations) — zero |
Summary
XCAFDoc_ShapeTool::theAutoNamingis a process-globalstatic bool(documented as such — "Thissetting is global; it cannot be made a member function") that three independent call sites each
save/modify/restore around their own work with no locking:
RWMesh_CafReader::fillDocument()— shared base ofRWObj_CafReaderandRWGltf_CafReader, soreachable from both OBJ and glTF import
RWGltf_CafReader::fillDocument()— a separate, near-duplicate override, not a call into thebase class's version
XCAFDoc_Editor::Expand()— additionally recurses into itself while the save/restore is in flightXCAFDoc_ShapeTool::AddShape/addShapereads the same flag internally to decide whether toauto-generate a name.
ThreadSanitizer on a concurrent-OBJ-import stress (8-10 threads, each writing/reading its own
uniquely-named file — not a file-path collision) reports 9-17 races per run on
theAutoNaming,resolving to two distinct problems:
stomps another's still-in-progress override — a logical bug (wrong auto-naming mode active for
part of a build), independent of memory safety.
theAutoNamingitself is a plainbool, read and written with no synchronization from anycaller — including ordinary
AddShapecalls made outside all three save/restore sites above — agenuine data race on every access.
Fix
Two parts, both needed:
XCAFDoc_ShapeTool::AutoNamingScope— a new RAII helper (AutoNamingScope(bool)constructor /destructor) that holds a
std::recursive_mutexfor its entire lifetime, not just around theindividual get/set calls, so two threads' save-modify-restore sequences serialize instead of
interleaving. Recursive because
XCAFDoc_Editor::Expand()reenters it on the same thread. Allthree call sites now use it;
Expand()'s two duplicate manual-restore-before-return sitescollapse into one destructor-driven restore that fires on every exit path.
theAutoNamingis nowstd::atomic<bool>instead of a plainbool, so every access anywhere inthe file — including
AddShape's internal read, which participates in none of the three scopedsections — is well-defined, closing the gap the mutex alone doesn't reach.
Testing
Minimal-module ThreadSanitizer build (
FoundationClasses+ModelingData+ModelingAlgorithms+DataExchange,RelWithDebInfo,-fsanitize=thread). A pure-C++ harness runs N threads eachbuilding a box, meshing it, writing it to its own uniquely-named
.objfile (RWObj_CafWriter),and reading it back (
RWObj_CafReader) into a freshTDocStd_Document.theAutoNamingraces per run (4 runs).theAutoNamingraces across the same stress, 4 separate runs — only an unrelated,pre-existing
Message_PrinterOStream/std::coutconsole-write race remains (OCCT's defaultmessenger has no internal locking; out of scope for this PR).
RWGltf_CafReader's copy of the fix compiles cleanly (mechanically identical to theRWMesh_CafReaderpath that was exercised under TSan) but wasn't run under TSan directly in thissession — the minimal-module build excludes
TKDEGLTF(needs RapidJSON, disabled for build speed).No behavior change for the existing (documented) semantics of
SetAutoNaming/AutoNamingas asingle process-wide setting — this only serializes the internal save/modify/restore idiom and makes
the underlying storage well-defined.
Fixes #1387.