Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/DataExchange/TKXSBase/GTests/FILES.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
set(OCCT_TKXSBase_GTests_FILES_LOCATION "${CMAKE_CURRENT_LIST_DIR}")

set(OCCT_TKXSBase_GTests_FILES
Interface_InterfaceModel_Test.cxx
XSControl_Controller_Test.cxx
)
97 changes: 97 additions & 0 deletions src/DataExchange/TKXSBase/GTests/Interface_InterfaceModel_Test.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2026 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#include <Interface_InterfaceModel.hxx>
#include <NCollection_HSequence.hxx>
#include <TCollection_AsciiString.hxx>
#include <TCollection_HAsciiString.hxx>

#include <gtest/gtest.h>

#include <atomic>
#include <thread>
#include <vector>

namespace
{
class TestModel : public Interface_InterfaceModel
{
public:
void ClearLabels() override {}

void ClearHeader() override {}

void GetFromAnother(const occ::handle<Interface_InterfaceModel>&) override {}

occ::handle<Interface_InterfaceModel> NewEmptyModel() const override { return new TestModel; }

void DumpHeader(Standard_OStream&, const int) const override {}

void PrintLabel(const occ::handle<Standard_Transient>&, Standard_OStream&) const override {}

occ::handle<TCollection_HAsciiString> StringLabel(
const occ::handle<Standard_Transient>&) const override
{
return new TCollection_HAsciiString("");
}

DEFINE_STANDARD_RTTI_INLINE(TestModel, Interface_InterfaceModel)
};
} // namespace

// SetTemplate, HasTemplate and Template work on one process-wide map, so concurrent registrations
// must all survive.
TEST(Interface_InterfaceModelTest, ConcurrentTemplateRegistrationKeepsEveryTemplate)
{
constexpr int aNbThreads = 8;
constexpr int aNbPerThread = 250;

std::vector<TCollection_AsciiString> aNames;
for (int anIdx = 0; anIdx < aNbThreads * aNbPerThread; ++anIdx)
{
aNames.push_back(TCollection_AsciiString("imtest-") + anIdx);
}
const occ::handle<Interface_InterfaceModel> aModel = new TestModel;

std::atomic<bool> aStart(false);
std::vector<std::thread> aThreads;
for (int aThread = 0; aThread < aNbThreads; ++aThread)
{
aThreads.emplace_back([&, aThread]() {
while (!aStart.load())
{
}
for (int anIdx = aThread * aNbPerThread; anIdx < (aThread + 1) * aNbPerThread; ++anIdx)
{
Interface_InterfaceModel::SetTemplate(aNames[anIdx].ToCString(), aModel);
Interface_InterfaceModel::Template(aNames[anIdx].ToCString());
}
});
}
aStart.store(true);
for (std::thread& aThread : aThreads)
{
aThread.join();
}

int aMissing = 0;
for (const TCollection_AsciiString& aName : aNames)
{
if (!Interface_InterfaceModel::HasTemplate(aName.ToCString()))
{
++aMissing;
}
}
EXPECT_EQ(aMissing, 0);
EXPECT_GE(Interface_InterfaceModel::ListTemplates()->Length(), aNbThreads * aNbPerThread);
}
86 changes: 86 additions & 0 deletions src/DataExchange/TKXSBase/GTests/XSControl_Controller_Test.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) 2026 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#include <Interface_InterfaceModel.hxx>
#include <TCollection_AsciiString.hxx>
#include <XSControl_Controller.hxx>

#include <gtest/gtest.h>

#include <atomic>
#include <thread>
#include <vector>

namespace
{
class TestController : public XSControl_Controller
{
public:
TestController(const char* const theLongName, const char* const theShortName)
: XSControl_Controller(theLongName, theShortName)
{
}

occ::handle<Interface_InterfaceModel> NewModel() const override { return nullptr; }

DEFINE_STANDARD_RTTI_INLINE(TestController, XSControl_Controller)
};
} // namespace

// Record and Recorded work on one process-wide map, so concurrent recordings must all survive.
TEST(XSControl_ControllerTest, ConcurrentRecordKeepsEveryController)
{
constexpr int aNbThreads = 8;
constexpr int aNbPerThread = 250;

// The controller constructor initialises shared parameters, so build them up front.
std::vector<occ::handle<TestController>> aControllers;
std::vector<TCollection_AsciiString> aNames;
for (int anIdx = 0; anIdx < aNbThreads * aNbPerThread; ++anIdx)
{
const TCollection_AsciiString aName = TCollection_AsciiString("xsctest-") + anIdx;
aNames.push_back(aName);
aControllers.push_back(new TestController(aName.ToCString(), aName.ToCString()));
}

std::atomic<bool> aStart(false);
std::vector<std::thread> aThreads;
for (int aThread = 0; aThread < aNbThreads; ++aThread)
{
aThreads.emplace_back([&, aThread]() {
while (!aStart.load())
{
}
for (int anIdx = aThread * aNbPerThread; anIdx < (aThread + 1) * aNbPerThread; ++anIdx)
{
aControllers[anIdx]->Record(aNames[anIdx].ToCString());
XSControl_Controller::Recorded(aNames[anIdx].ToCString());
}
});
}
aStart.store(true);
for (std::thread& aThread : aThreads)
{
aThread.join();
}

int aMissing = 0;
for (size_t anIdx = 0; anIdx < aNames.size(); ++anIdx)
{
if (XSControl_Controller::Recorded(aNames[anIdx].ToCString()) != aControllers[anIdx])
{
++aMissing;
}
}
EXPECT_EQ(aMissing, 0);
}
15 changes: 15 additions & 0 deletions src/DataExchange/TKXSBase/Interface/Interface_InterfaceModel.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
#include <Standard_Integer.hxx>
#include <NCollection_DataMap.hxx>

#include <mutex>

IMPLEMENT_STANDARD_RTTIEXT(Interface_InterfaceModel, Standard_Transient)

// An Interface Model is a closed set of interface Entities: each one
Expand All @@ -43,6 +45,14 @@ IMPLEMENT_STANDARD_RTTIEXT(Interface_InterfaceModel, Standard_Transient)
// STATICS : the TEMPLATES
static NCollection_DataMap<TCollection_AsciiString, occ::handle<Standard_Transient>> atemp;

//! Guards atemp, the process-wide registry of template models. Recursive because Template() calls
//! HasTemplate().
static std::recursive_mutex& atempMutex()
{
static std::recursive_mutex THE_MUTEX;
return THE_MUTEX;
}

static const occ::handle<Standard_Type>& typerep()
{
return STANDARD_TYPE(Interface_ReportEntity);
Expand Down Expand Up @@ -1100,13 +1110,15 @@ int Interface_InterfaceModel::NextNumberForLabel(const char* const label,

bool Interface_InterfaceModel::HasTemplate(const char* const name)
{
std::lock_guard<std::recursive_mutex> aLock(atempMutex());
return atemp.IsBound(name);
}

//=================================================================================================

occ::handle<Interface_InterfaceModel> Interface_InterfaceModel::Template(const char* const name)
{
std::lock_guard<std::recursive_mutex> aLock(atempMutex());
occ::handle<Interface_InterfaceModel> model, newmod;
if (!HasTemplate(name))
{
Expand All @@ -1123,6 +1135,7 @@ occ::handle<Interface_InterfaceModel> Interface_InterfaceModel::Template(const c
bool Interface_InterfaceModel::SetTemplate(const char* const name,
const occ::handle<Interface_InterfaceModel>& model)
{
std::lock_guard<std::recursive_mutex> aLock(atempMutex());
return atemp.Bind(name, model);
}

Expand All @@ -1131,6 +1144,8 @@ bool Interface_InterfaceModel::SetTemplate(const char* const
occ::handle<NCollection_HSequence<occ::handle<TCollection_HAsciiString>>> Interface_InterfaceModel::
ListTemplates()
{
std::lock_guard<std::recursive_mutex> aLock(atempMutex());

occ::handle<NCollection_HSequence<occ::handle<TCollection_HAsciiString>>> list =
new NCollection_HSequence<occ::handle<TCollection_HAsciiString>>();
if (atemp.IsEmpty())
Expand Down
12 changes: 12 additions & 0 deletions src/DataExchange/TKXSBase/XSControl/XSControl_Controller.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,22 @@
#include <XSControl_SignTransferStatus.hxx>
#include <XSControl_WorkSession.hxx>

#include <mutex>

IMPLEMENT_STANDARD_RTTIEXT(XSControl_Controller, Standard_Transient)

// ParamEditor
// Transferts

static NCollection_DataMap<TCollection_AsciiString, occ::handle<Standard_Transient>> listad;

//! Guards listad, the process-wide registry of controllers by name.
static std::recursive_mutex& listadMutex()
{
static std::recursive_mutex THE_MUTEX;
return THE_MUTEX;
}

//=================================================================================================

XSControl_Controller::XSControl_Controller(const char* const theLongName,
Expand Down Expand Up @@ -106,6 +115,7 @@ void XSControl_Controller::SetNames(const char* const theLongName, const char* c

void XSControl_Controller::Record(const char* const theName) const
{
std::lock_guard<std::recursive_mutex> aLock(listadMutex());
if (listad.IsBound(theName))
{
occ::handle<Standard_Transient> thisadapt(this);
Expand All @@ -126,6 +136,8 @@ void XSControl_Controller::Record(const char* const theName) const

occ::handle<XSControl_Controller> XSControl_Controller::Recorded(const char* const theName)
{
std::lock_guard<std::recursive_mutex> aLock(listadMutex());

occ::handle<Standard_Transient> recorded;
return (listad.Find(theName, recorded) ? occ::down_cast<XSControl_Controller>(recorded)
: occ::handle<XSControl_Controller>());
Expand Down
Loading