Skip to content

Modeling Algorithms - Create the BRepLib default plane under the magic-static lock - #1597

Open
gsdali wants to merge 2 commits into
Open-Cascade-SAS:IRfrom
SecondMouseAU:fix/3039-brep-lib-plane-lock
Open

gsdali wants to merge 2 commits into
Open-Cascade-SAS:IRfrom
SecondMouseAU:fix/3039-brep-lib-plane-lock

Conversation

@gsdali

@gsdali gsdali commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

BRepLib::Plane() creates its process-wide plane on first use:

static occ::handle<Geom_Plane> thePlane;
...
if (thePlane.IsNull()) { thePlane = new Geom_Plane(gp::XOY()); }
return thePlane;

BRepLib_MakeEdge2d reads every vertex it builds through that plane (Point, Project, UpdateEdge). Two threads making their first 2D edge together both see a null handle and both assign. The losing assignment releases the plane the other thread has already read through, so a vertex is read from freed memory: zero or denormal coordinates, or a crash (SIGSEGV, SIGBUS, SIGTRAP, abort in free).

The plane is now created by a function-local static, which C++11 initialises under a lock, as Message::DefaultMessenger() creates its messenger. The setter keeps its behaviour, including that a null argument restores the default plane. The setter itself is still unsynchronised, like BRepLib::Precision's.

  • Hold the plane in a function-local static in BRepLib.cxx
  • Add BRepLib_Test.Plane_ConcurrentFirstUse and Plane_NullRestoresDefault in TKTopAlgo/GTests

Plane_ConcurrentFirstUse clears the plane with BRepLib::Plane(null) to re-arm the first use, releases 16 threads together on a barrier, and checks that every thread was handed the same plane; 500 rounds, in a death-test child process because the unsynchronised race can corrupt the heap. It compares the plane pointers and never dereferences one.

Measured on a local macOS arm64 Release build (clang, -Werror -Wall -Wextra, BUILD_GTEST=ON, Foundation, Modeling Data and Modeling Algorithms modules) of IR at 0089343, on a heavily loaded machine:

  • IR without the change: Plane_ConcurrentFirstUse failed in 40 of 40 runs. Standalone, the same loop finds different planes in 21 to 44 of 500 rounds at 16 threads (6 runs), 14 to 21 at 8 threads, 13 to 20 at 4, 7 to 9 at 2.
  • IR with the change: 0 failures in 100 runs of the new tests, 0 mismatches in 5 standalone runs of 500 rounds, and the full OpenCascadeGTest passes (8361 tests, 7 skipped by the tests themselves, none failed).
  • Independently of the GTest, a pure-OCCT probe building a half circle with BRepLib_MakeEdge2d on 16 threads in a fresh process, 3000 processes, on a V8_0_1 build: 129 return a wrong vertex or die, none with the change.
  • clang-format 18.1.8 with the repository .clang-format reports no violation, and the changed files contain no non-ASCII character.

…c-static lock

BRepLib::Plane() created the process-wide plane on first use with no lock, so
concurrent first use of BRepLib_MakeEdge2d read a plane the losing thread had
released. Hold it in a function-local static, as Message::DefaultMessenger()
does, and keep the setter's behaviour that a null argument restores the default.

Add BRepLib_Test.Plane_ConcurrentFirstUse and Plane_NullRestoresDefault.
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