Skip to content

Modeling - Make fillet solid reconstruction reentrant across threads#1374

Open
gsdali wants to merge 1 commit into
Open-Cascade-SAS:masterfrom
gsdali:fix/fillet-thread-safety-topopebrep-298
Open

Modeling - Make fillet solid reconstruction reentrant across threads#1374
gsdali wants to merge 1 commit into
Open-Cascade-SAS:masterfrom
gsdali:fix/fillet-thread-safety-topopebrep-298

Conversation

@gsdali

@gsdali gsdali commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Problem

BRepFilletAPI_MakeFillet is not reentrant: two builds on independent shapes running on separate threads — the concurrency model OCCT documents as supported ("algorithms on different threads in different contexts") — corrupt each other and return a wrong-but-plausible solid that fails BRepCheck. Silent bad geometry: no crash, no thrown error.

The fillet reconstructs its result solid through the legacy TopOpeBRepBuild engine:

ChFi3d_Builder::Compute()
 → TopOpeBRepBuild_HBuilder::MergeSolid()
 → TopOpeBRepBuild_Builder::SplitSolid()   // writes STATIC_SOLIDINDEX

STATIC_SOLIDINDEX (TopOpeBRepBuild_Builder.cxx) is a file-scope static used as a mode flag: SplitSolid sets it to 1/2 to tell FillSolid which operand it is currently splitting, and FillSolid reads it back (if (STATIC_SOLIDINDEX == 1)) to pick the operand shape. Two concurrent reconstructions interleave the writes, so FillSolid reads the wrong value and mis-classifies faces — dropping material.

Evidence (ThreadSanitizer + behaviour)

Isolated pure-C++ repro: fuse three prisms into a U-channel, fillet the seam, 8 threads. Built RelWithDebInfo + -fsanitize=thread, MMGT_OPT=0.

  • Stock master: an 8-thread stress returns BRepCheck-invalid solids across several distinct wrong volumes (~15–20% of builds); the correct part is a single volume. TSan reports a data race on STATIC_SOLIDINDEX (write/write in SplitSolid from two threads), plus benign scratch races in the blend solver.
  • After this change: 1600 concurrent fillet builds return correct geometry every time (0 invalid, single volume), and the fillet path is TSan-clean.

Change

Converts the fillet-path shared statics to static thread_local — each thread keeps its own copy of the cross-call state, so single-thread behaviour is identical while independent instances on separate threads become reentrant:

File Variable Kind
TopOpeBRepBuild_Builder.cxx STATIC_SOLIDINDEX Functional — causes the corruption
TopOpeBRep_kpart.cxx STATIC_lastVPind Functional — same cross-call-cache pattern ("the vp on which was computed the last CPI")
BlendFunc_ConstRad.cxx, BlendFunc_EvolRad.cxx ComputeValues scratch Benign data race (no corruption, but TSan-flagged)
ChFi3d_Builder_6.cxx checkcurve Benign data race

This is the same class of fix, in the same engine, as the earlier TKBool thread_local conversion (#1180, 19 globals across 8 files); STATIC_SOLIDINDEX and these were not covered by it.

Reported downstream as SecondMouseAU/OCCTSwift#298.

Draft while I confirm CI; happy to split the benign scratch conversions into a separate change if you'd prefer the functional STATIC_SOLIDINDEX fix on its own.

gsdali added a commit to SecondMouseAU/OCCTSwift that referenced this pull request Jul 18, 2026
…n lock (#308)

The real fix for #298 lands in the pinned OCCT, replacing the v1.12.1 bridge lock.

Root cause (found with ThreadSanitizer): BRepFilletAPI_MakeFillet reconstructs its
result solid via OCCT's legacy TopOpeBRepBuild engine, which passed state between
methods through a file-scope static, STATIC_SOLIDINDEX (SplitSolid sets 1/2,
FillSolid reads it back to pick the operand). Concurrent fillets on independent
shapes clobbered each other's flag, mis-classifying faces -> wrong-but-plausible
solid that fails BRepCheck. NOT the BlendFunc scratch the v1.12.1 notes suspected
(those race benignly).

- Scripts/patches/0003: converts the fillet-path statics to thread_local
  (STATIC_SOLIDINDEX + STATIC_lastVPind functional; BlendFunc_ConstRad/EvolRad and
  ChFi3d_Builder checkcurve scratch benign, for TSan-clean). Upstreamed as
  Open-Cascade-SAS/OCCT#1374.
- xcframework rebuilt with 0003; Package.swift url+checksum bumped to v1.12.3.
- occtFilletMutex and its 16 bridge guard sites removed — fillet/chamfer are
  reentrant in the kernel now, so concurrent builds run in parallel again.
- Issue298FilletThreadSafetyTests kept; passes with the lock removed.

Verified: pure-C++ 8-thread stress 0/1600 invalid (was ~15-20%), TSan-clean on the
fillet path; the Swift regression + Modeling/Thread suites pass against the patched
binary with the lock gone.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gsdali
gsdali force-pushed the fix/fillet-thread-safety-topopebrep-298 branch from c585c6a to 7729e7b Compare July 18, 2026 02:58
@gsdali
gsdali marked this pull request as ready for review July 18, 2026 02:59
BRepFilletAPI_MakeFillet reconstructs its result solid through the legacy
TopOpeBRepBuild engine (ChFi3d_Builder::Compute -> TopOpeBRepBuild_HBuilder::
MergeSolid -> TopOpeBRepBuild_Builder::SplitSolid), which passes state between
methods through file-scope static variables. That makes it non-reentrant: two
BRepFilletAPI_MakeFillet builds on independent shapes on separate threads corrupt
each other and yield a wrong-but-plausible solid that fails BRepCheck.

ThreadSanitizer on an 8-thread fuse+fillet stress pinpoints the functional cause as
STATIC_SOLIDINDEX (TopOpeBRepBuild_Builder.cxx): SplitSolid sets it to 1/2 to tell
FillSolid which operand it is splitting, FillSolid reads it back. Concurrent
reconstructions interleave the writes, so FillSolid mis-classifies faces and drops
material. Converting that one variable to thread_local makes a 1600-build concurrent
stress return correct geometry every time (was ~15-20% corrupt).

Converts the fillet-path shared statics to thread_local (each thread keeps its own
copy of the cross-call state; single-thread behaviour is unchanged):

- Functional: TopOpeBRepBuild_Builder STATIC_SOLIDINDEX; TopOpeBRep_kpart
  STATIC_lastVPind (same cross-call-cache pattern).
- Benign data races on the same path (no corruption, but flagged by TSan):
  BlendFunc_ConstRad / BlendFunc_EvolRad ComputeValues scratch; ChFi3d_Builder_6
  checkcurve.

Same class of fix, same engine, as the earlier TKBool thread_local conversion
(Open-Cascade-SAS#1180); these were not covered by it. Reported downstream as SecondMouseAU/
OCCTSwift#298.
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.

2 participants