Skip to content

Data Exchange - Synchronise the XSControl_Controller and Interface_InterfaceModel registries - #1603

Open
gsdali wants to merge 1 commit into
Open-Cascade-SAS:IRfrom
SecondMouseAU:fix/de-registry-maps-lock
Open

gsdali wants to merge 1 commit into
Open-Cascade-SAS:IRfrom
SecondMouseAU:fix/de-registry-maps-lock

Conversation

@gsdali

@gsdali gsdali commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Pre-Submission Checks

  • I checked existing issues, pull requests, discussions, and forum topics for related work.
  • I followed the contribution guidance in .github/CONTRIBUTING.md.
  • I used a PR title in the Group - Summary format.

Problem / Motivation

Two file-scope maps in TKXSBase are process-wide registries that any thread can read and modify, and neither has any synchronisation:

  • listad in XSControl_Controller.cxx, the controllers recorded by name. Record does IsBound, then ChangeFind, then Bind; Recorded does Find. Every STEPControl_Controller::Init(), IGESControl_Controller::Init() and every AutoRecord() goes through it.
  • atemp in Interface_InterfaceModel.cxx, the template models by name. Template calls HasTemplate and then ChangeFind, a check-then-act over two lookups; SetTemplate binds and ListTemplates iterates.

Two threads recording into the same NCollection_DataMap rehash it under each other, or one reads a bucket chain while the other relinks it. The result is a lost entry, a hang, or a crash inside the map.

Proposed Solution

Guard each map with a std::recursive_mutex, taken in every function that touches the map. A lock fits these two because one registry per process is the design; the state is shared on purpose, so there is nothing to relocate into an instance. The mutex is recursive because Interface_InterfaceModel::Template takes it and then calls HasTemplate, which takes it again.

Interface_Static's parameter table (also a process-wide registry) is not touched here; it is the subject of #1553, and a second lock on the same data through another path would only invite a lock-order inversion.

Two tests in TKXSBase/GTests, which had an empty list waiting for a first entry:

  • XSControl_ControllerTest.ConcurrentRecordKeepsEveryController: eight threads record 250 controllers each under distinct names while reading them back, then every controller must be found.
  • Interface_InterfaceModelTest.ConcurrentTemplateRegistrationKeepsEveryTemplate: eight threads register and fetch 250 templates each, then every name must be present and ListTemplates must list all of them.

The controllers and models are built on the main thread first, so the tests exercise the registries and not whatever else a constructor initialises.

Validation

Local build of current IR (Release, BUILD_RELEASE_DISABLE_EXCEPTIONS on as CI builds it, macOS arm64, clang), OpenCascadeGTest. These are concurrency tests, so the result is a rate:

test IR without the source change with the change
XSControl_ControllerTest.ConcurrentRecordKeepsEveryController 15 of 15 runs failed 30 of 30 runs passed
Interface_InterfaceModelTest.ConcurrentTemplateRegistrationKeepsEveryTemplate 14 of 15 runs failed 30 of 30 runs passed

Across the 30 unpatched runs the failures were 17 SIGTRAP, 4 SIGABRT, 2 SIGSEGV, 4 hangs killed by a 60 s timeout, and 2 clean assertion failures (entries lost); the one passing run is the race not firing.

The local tree has TKXSBase and TKDE from DataExchange plus the modeling modules, so the other DataExchange suites (STEP, IGES, XCAF) were not built here; the neighbouring *Interface*, *XSControl*, *IFSelect*, *Transfer* tests that exist in that tree pass (16 of 16).

Checks performed:

  • Relevant tests were added or updated when applicable.
  • Relevant local tests were run.

clang-format 18.1.8 with the repository .clang-format, the license check and the include cleanup of the formatting job report no change on the touched files, and none contains a non-ASCII character.

Review Notes

The two source files are independent of each other; each change can be reviewed on its own.

…terfaceModel registries

XSControl_Controller keeps every recorded controller in the file-scope map
listad, and Interface_InterfaceModel keeps its template models in the file-scope
map atemp. Both are process-wide by design and both are read and modified with no
synchronisation: Record does IsBound, ChangeFind and Bind, Template does
HasTemplate and then ChangeFind, so two threads rehash or read the map under each
other.

Guard each with a recursive mutex. Recursive is needed for atemp, since Template
calls HasTemplate while holding the lock.

Add GTests that register and read many entries from several threads and check
that none is lost.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant