Skip to content

Commit 42d9c36

Browse files
authored
Data Exchange - Make STEP write and STEP/IGES read pipelines thread-safe (#1259)
Concurrent STEPControl_Writer::Transfer calls crashed inside TCollection_AsciiString::Move with a libmalloc double-free, and concurrent STEP/IGES readers occasionally crashed inside Interface_FileReaderTool::RecognizeByLib. Both originated in process-shared mutable state that every per-thread reader/writer silently mutated. STEP write actor (writer-side race): - Pre-populate the shared STEPControl_ActorWrite parameter map and processing flags inside STEPControl_Controller::Init under the existing one-time mutex, so concurrent STEPControl_Writer::Transfer calls no longer mutate the shared actor in the default path. - Drop the per-Transfer call to STEPControl_Writer::InitializeMissingParameters; defaults are now in place from controller construction time. - Make XSAlgo_ShapeProcessor::SetParameter idempotent: skip the destructive Bind when the existing entry already matches the new value, eliminating the TCollection_AsciiString::Move/Free path that was the immediate double-free site under concurrent writers. - Stop clearing the target ParameterMap in XSAlgo_ShapeProcessor::SetShapeFixParameters before refilling it; rely on the idempotent SetParameter for in-place replacement. LibCtl_Library (reader-side race): - Drop the file-scope "last protocol" optimization cache (theprotocol/thelast) that raced between concurrent STEP/IGES readers in Interface_FileReaderTool::RecognizeByLib. - Treat the global registry as immutable after one-time controller initialization: writers (SetGlobal) serialize against each other, readers traverse without locking. Move internal helper logic into an anonymous namespace.
1 parent 4674df1 commit 42d9c36

4 files changed

Lines changed: 87 additions & 95 deletions

File tree

src/DataExchange/TKDESTEP/STEPControl/STEPControl_Controller.cxx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include "../RWStepAP214/RWStepAP214.pxx"
2727
#include <Standard_Type.hxx>
2828
#include <Standard_Version.hxx>
29+
#include <DESTEP_Parameters.hxx>
30+
#include <ShapeProcess.hxx>
2931
#include <STEPControl_ActorRead.hxx>
3032
#include <STEPControl_ActorWrite.hxx>
3133
#include <STEPControl_Controller.hxx>
@@ -343,6 +345,13 @@ STEPControl_Controller::STEPControl_Controller()
343345
occ::handle<STEPControl_ActorWrite> ActWrite = new STEPControl_ActorWrite;
344346
myAdaptorWrite = ActWrite;
345347

348+
ActWrite->SetShapeFixParameters(DESTEP_Parameters::GetDefaultShapeFixParameters(),
349+
XSAlgo_ShapeProcessor::ParameterMap{});
350+
ShapeProcess::OperationsFlags aDefaultProcFlags;
351+
aDefaultProcFlags.set(ShapeProcess::Operation::SplitCommonVertex);
352+
aDefaultProcFlags.set(ShapeProcess::Operation::DirectFaces);
353+
ActWrite->SetShapeProcessFlags(aDefaultProcFlags);
354+
346355
occ::handle<StepSelect_WorkLibrary> swl = new StepSelect_WorkLibrary;
347356
swl->SetDumpLabel(1);
348357
myAdaptorLibrary = swl;

src/DataExchange/TKDESTEP/STEPControl/STEPControl_Writer.cxx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ IFSelect_ReturnStatus STEPControl_Writer::Transfer(const TopoDS_Shape&
155155
occ::down_cast<STEPControl_ActorWrite>(WS()->NormAdaptor()->ActorWrite());
156156
ActWrite->SetGroupMode(
157157
occ::down_cast<StepData_StepModel>(thesession->Model())->InternalParameters.WriteAssembly);
158-
InitializeMissingParameters();
159158
return thesession->TransferWriteShape(sh, compgraph, theProgress);
160159
}
161160

src/DataExchange/TKXSBase/LibCtl/LibCtl_Library.gxx

Lines changed: 72 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -15,86 +15,79 @@
1515
// #include <LibCtl_Library.ixx>
1616
#include <Standard_NoSuchObject.hxx>
1717

18-
// Global List of Modules, from which we will be served
18+
#include <mutex>
1919

20-
static occ::handle<LibCtl_GlobalNode> theglobal;
20+
namespace
21+
{
22+
// Global registry of (Module, Protocol) pairs. SetGlobal / AddProtocol are
23+
// expected to run only during one-time controller initialization, before any
24+
// parallel work. Writers serialize against each other; readers traverse
25+
// without locking and rely on this register-before-parallel-use contract.
26+
static occ::handle<LibCtl_GlobalNode> THE_GLOBAL;
2127

22-
// Data for optimization (last Protocol requested)
28+
static std::mutex& LibCtl_TheWriterLock()
29+
{
30+
static std::mutex aMutex;
31+
return aMutex;
32+
}
2333

24-
static occ::handle<TheProtocol> theprotocol;
25-
static occ::handle<LibCtl_Node> thelast;
34+
// Build/extend `theTargetList` for `theProtocol`, recursing into Resources.
35+
static void LibCtl_AddProtocolImpl(occ::handle<LibCtl_Node>& theTargetList,
36+
const occ::handle<Standard_Transient>& theProtocol)
37+
{
38+
occ::handle<TheProtocol> aProtocol = occ::down_cast<TheProtocol>(theProtocol);
39+
if (aProtocol.IsNull())
40+
return;
41+
42+
for (occ::handle<LibCtl_GlobalNode> aCurrent = THE_GLOBAL; !aCurrent.IsNull();)
43+
{
44+
const occ::handle<TheProtocol>& aNodeProto = aCurrent->Protocol();
45+
if (!aNodeProto.IsNull() && aNodeProto->DynamicType() == aProtocol->DynamicType())
46+
{
47+
if (theTargetList.IsNull())
48+
theTargetList = new LibCtl_Node;
49+
theTargetList->AddNode(aCurrent);
50+
break;
51+
}
52+
aCurrent = aCurrent->Next();
53+
}
54+
55+
const int aNbResources = aProtocol->NbResources();
56+
for (int anInd = 1; anInd <= aNbResources; anInd++)
57+
{
58+
LibCtl_AddProtocolImpl(theTargetList, aProtocol->Resource(anInd));
59+
}
60+
}
61+
} // namespace
2662

2763
// Feeding the global list
2864
// WARNING: SetGlobal performs substitution, i.e. it's the last one
2965
// that is right for a given Protocol
30-
void LibCtl_Library::SetGlobal(const occ::handle<TheModule>& amodule,
31-
const occ::handle<TheProtocol>& aprotocol)
66+
void LibCtl_Library::SetGlobal(const occ::handle<TheModule>& theModule,
67+
const occ::handle<TheProtocol>& theProtocol)
3268
{
33-
if (theglobal.IsNull())
34-
theglobal = new LibCtl_GlobalNode;
35-
theglobal->Add(amodule, aprotocol);
69+
std::lock_guard<std::mutex> aLock(LibCtl_TheWriterLock());
70+
if (THE_GLOBAL.IsNull())
71+
THE_GLOBAL = new LibCtl_GlobalNode;
72+
THE_GLOBAL->Add(theModule, theProtocol);
3673
}
3774

3875
// Constructor from Protocol
39-
LibCtl_Library::LibCtl_Library(const occ::handle<TheProtocol>& aprotocol)
76+
LibCtl_Library::LibCtl_Library(const occ::handle<TheProtocol>& theProtocol)
4077
{
41-
bool last = false;
42-
if (aprotocol.IsNull())
78+
if (theProtocol.IsNull())
4379
return; // NO protocol = EMPTY Lib
44-
if (!theprotocol.IsNull())
45-
last = (theprotocol == aprotocol);
4680

47-
if (last)
48-
thelist = thelast;
49-
// If no optimization available: build the list
50-
else
51-
{
52-
AddProtocol(aprotocol);
53-
// This defines the optimization (for the next time)
54-
thelast = thelist;
55-
theprotocol = aprotocol;
56-
}
81+
LibCtl_AddProtocolImpl(thelist, theProtocol);
5782
}
5883

5984
// Constructeur vide
6085
LibCtl_Library::LibCtl_Library() {}
6186

62-
// Adding a Protocol: beware, deoptimizes (otherwise risk of confusion!)
63-
void LibCtl_Library::AddProtocol(const occ::handle<Standard_Transient>& aprotocol)
87+
// Adds a Protocol (and its Resources) to this Library's list.
88+
void LibCtl_Library::AddProtocol(const occ::handle<Standard_Transient>& theProtocol)
6489
{
65-
// DownCast because Protocol->Resources, even redefined and used in other
66-
// libraries, must always return the highest type
67-
occ::handle<TheProtocol> aproto = occ::down_cast<TheProtocol>(aprotocol);
68-
if (aproto.IsNull())
69-
return;
70-
71-
// First, add this one to the list: search for the Node
72-
occ::handle<LibCtl_GlobalNode> curr;
73-
for (curr = theglobal; !curr.IsNull();)
74-
{ // curr->Next : plus loin
75-
const occ::handle<TheProtocol>& protocol = curr->Protocol();
76-
if (!protocol.IsNull())
77-
{
78-
// Match Protocol ?
79-
if (protocol->DynamicType() == aprotocol->DynamicType())
80-
{
81-
if (thelist.IsNull())
82-
thelist = new LibCtl_Node;
83-
thelist->AddNode(curr);
84-
break; // UN SEUL MODULE PAR PROTOCOLE
85-
}
86-
}
87-
curr = curr->Next(); // this formula is refused in "for"
88-
}
89-
// Then, process the resources
90-
int nb = aproto->NbResources();
91-
for (int i = 1; i <= nb; i++)
92-
{
93-
AddProtocol(aproto->Resource(i));
94-
}
95-
// Don't forget to deoptimize
96-
theprotocol.Nullify();
97-
thelast.Nullify();
90+
LibCtl_AddProtocolImpl(thelist, theProtocol);
9891
}
9992

10093
void LibCtl_Library::Clear()
@@ -105,47 +98,44 @@ void LibCtl_Library::Clear()
10598
void LibCtl_Library::SetComplete()
10699
{
107100
thelist = new LibCtl_Node;
108-
// We take each of the Protocols from the Global List and add it
109-
occ::handle<LibCtl_GlobalNode> curr;
110-
for (curr = theglobal; !curr.IsNull();)
111-
{ // curr->Next : plus loin
112-
const occ::handle<TheProtocol>& protocol = curr->Protocol();
113-
// Since we take everything, we don't worry about Resources!
114-
if (!protocol.IsNull())
115-
thelist->AddNode(curr);
116-
curr = curr->Next(); // this formula is refused in "for"
101+
for (occ::handle<LibCtl_GlobalNode> aCurrent = THE_GLOBAL; !aCurrent.IsNull();)
102+
{
103+
const occ::handle<TheProtocol>& aNodeProto = aCurrent->Protocol();
104+
if (!aNodeProto.IsNull())
105+
thelist->AddNode(aCurrent);
106+
aCurrent = aCurrent->Next();
117107
}
118108
}
119109

120110
// Selection: Very powerful, we return the Module corresponding to a Type
121111
// (as well as the CaseNumber returned by the corresponding protocol)
122112

123-
bool LibCtl_Library::Select(const TheObject& obj, occ::handle<TheModule>& module, int& CN) const
113+
bool LibCtl_Library::Select(const TheObject& theObj,
114+
occ::handle<TheModule>& theModule,
115+
int& theCN) const
124116
{
125-
module.Nullify();
126-
CN = 0; // Response "not found"
117+
theModule.Nullify();
118+
theCN = 0; // Response "not found"
127119
if (thelist.IsNull())
128120
return false;
129-
occ::handle<LibCtl_Node> curr = thelist;
130-
for (curr = thelist; !curr.IsNull();)
131-
{ // curr->Next : plus loin
132-
const occ::handle<TheProtocol>& protocol = curr->Protocol();
121+
occ::handle<LibCtl_Node> aCurrent = thelist;
122+
for (aCurrent = thelist; !aCurrent.IsNull();)
123+
{ // aCurrent->Next : plus loin
124+
const occ::handle<TheProtocol>& protocol = aCurrent->Protocol();
133125
if (!protocol.IsNull())
134126
{
135-
CN = protocol->CaseNumber(obj);
136-
if (CN > 0)
127+
theCN = protocol->CaseNumber(theObj);
128+
if (theCN > 0)
137129
{
138-
module = curr->Module();
130+
theModule = aCurrent->Module();
139131
return true;
140132
}
141133
}
142-
curr = curr->Next(); // this formula is refused in "for"
134+
aCurrent = aCurrent->Next(); // this formula is refused in "for"
143135
}
144136
return false; // here, not found
145137
}
146138

147-
// .... Iteration ....
148-
149139
void LibCtl_Library::Start()
150140
{
151141
thecurr = thelist;

src/DataExchange/TKXSBase/XSAlgo/XSAlgo_ShapeProcessor.cxx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -718,16 +718,12 @@ void XSAlgo_ShapeProcessor::SetShapeFixParameters(
718718
const XSAlgo_ShapeProcessor::ParameterMap& theAdditionalParameters,
719719
XSAlgo_ShapeProcessor::ParameterMap& theTargetParameterMap)
720720
{
721-
theTargetParameterMap.Clear();
722721
XSAlgo_ShapeProcessor::FillParameterMap(theParameters, true, theTargetParameterMap);
723722
for (XSAlgo_ShapeProcessor::ParameterMap::Iterator aParamIter(theAdditionalParameters);
724723
aParamIter.More();
725724
aParamIter.Next())
726725
{
727-
if (!theTargetParameterMap.IsBound(aParamIter.Key()))
728-
{
729-
theTargetParameterMap.Bind(aParamIter.Key(), aParamIter.Value());
730-
}
726+
SetParameter(aParamIter.Key().ToCString(), aParamIter.Value(), false, theTargetParameterMap);
731727
}
732728
}
733729

@@ -763,17 +759,15 @@ void XSAlgo_ShapeProcessor::SetParameter(const char* th
763759
const bool theIsReplace,
764760
XSAlgo_ShapeProcessor::ParameterMap& theMap)
765761
{
766-
if (theIsReplace)
767-
{
768-
theMap.Bind(theKey, theValue);
769-
}
770-
else
762+
const TCollection_AsciiString* anExisting = theMap.Seek(theKey);
763+
if (anExisting != nullptr)
771764
{
772-
if (!theMap.IsBound(theKey))
765+
if (!theIsReplace || *anExisting == theValue)
773766
{
774-
theMap.Bind(theKey, theValue);
767+
return;
775768
}
776769
}
770+
theMap.Bind(theKey, theValue);
777771
}
778772

779773
//=============================================================================

0 commit comments

Comments
 (0)