Summary
Follow-up investigation flagged in #367 (fixed, closed) — that issue removed the trigger
(OCCTShapeFuseMulti's SetRunParallel(true)) but did not root-cause the underlying mechanism.
Filing this to track that separately, since it's a materially different scale of investigation
than anything else in this project's TSan series so far.
What #367 established
Two independent, unrelated top-level BRepAlgoAPI_BuilderAlgo::Build() calls, each requesting
internal parallelism (SetRunParallel(true)), submit work to the same process-wide
OSD_ThreadPool::DefaultPool(). Under concurrent load this was 100% reproducible data
corruption, not a rare interleaving: 400/400 operations produced wrong results (27 faces instead
of the correct 13), plus 237 TSan race reports across foundational topology code (TopoDS_Builder::Add,
TopExp_Explorer, BRep_Tool::Range, BOPTools_AlgoTools::MakeSplitEdge).
The clearest captured trace: thread T1's own top-level Build() call allocates a vertex via its
own BOPAlgo_PaveFiller. Thread T9 — a worker spawned by a different, unrelated top-level caller
(T4)'s OSD_ThreadPool dispatch — reads and writes that same memory. Worker threads from one
caller's dispatch end up processing another caller's data.
Reproducer: Scripts/repro/342-boolean-ops/occt_342_boolean_stress.cpp, fuse_multi_parallel
scenario.
Every prior TSan finding in this project was a single unsynchronized static/global variable (or a
shared instance that should have been per-object) in a narrow, specific class — findable by reading
that one class's source and fixable by adding a mutex or relocating the state. This finding points
at OSD_ThreadPool/BOPTools_Parallel — OCCT's own shared-pool parallel-dispatch infrastructure,
used internally by many algorithms, not just booleans. Properly root-causing it likely needs
reading OSD_ThreadPool.cxx's actual job-queue/dispatch implementation (job submission, worker
wake-up, completion tracking) to find the real mechanism, not pattern-matching against the prior
fixes' template.
Open questions
- Is
OSD_ThreadPool::DefaultPool() fundamentally unsafe for concurrent independent submitters
(multiple unrelated top-level operations dispatching to it at the same time), or is this specific
to how BOPTools_Parallel/BOPAlgo_PaveFiller use it (e.g. a job-completion/context-scoping bug
in the caller, not the pool itself)?
- Does this affect anything else in the bridge that triggers internal OCCT parallelism —
BRepMesh_IncrementalMesh's InParallel option, BRepCheck_Analyzer::SetParallel, or any other
SetRunParallel-style opt-in — or is it specific to BRepAlgoAPI_BuilderAlgo/BOPAlgo_PaveFiller?
- Is this already-known/reported upstream (search Open-Cascade-SAS/OCCT's issue tracker before
assuming it's novel)?
Priority
Not blocking anything currently shipping (#367's immediate fix removed the only bridge call site
that triggered it). Pick up as a dedicated TSan investigation when there's time for a genuinely
open-ended one, following the established minimal-module protocol
(FoundationClasses+ModelingData+ModelingAlgorithms) plus direct reading of
OSD_ThreadPool.cxx/OSD_Parallel.hxx/BOPTools_Parallel.hxx.
Summary
Follow-up investigation flagged in #367 (fixed, closed) — that issue removed the trigger
(
OCCTShapeFuseMulti'sSetRunParallel(true)) but did not root-cause the underlying mechanism.Filing this to track that separately, since it's a materially different scale of investigation
than anything else in this project's TSan series so far.
What #367 established
Two independent, unrelated top-level
BRepAlgoAPI_BuilderAlgo::Build()calls, each requestinginternal parallelism (
SetRunParallel(true)), submit work to the same process-wideOSD_ThreadPool::DefaultPool(). Under concurrent load this was 100% reproducible datacorruption, not a rare interleaving: 400/400 operations produced wrong results (27 faces instead
of the correct 13), plus 237 TSan race reports across foundational topology code (
TopoDS_Builder::Add,TopExp_Explorer,BRep_Tool::Range,BOPTools_AlgoTools::MakeSplitEdge).The clearest captured trace: thread T1's own top-level
Build()call allocates a vertex via itsown
BOPAlgo_PaveFiller. Thread T9 — a worker spawned by a different, unrelated top-level caller(T4)'s
OSD_ThreadPooldispatch — reads and writes that same memory. Worker threads from onecaller's dispatch end up processing another caller's data.
Reproducer:
Scripts/repro/342-boolean-ops/occt_342_boolean_stress.cpp,fuse_multi_parallelscenario.
Why this is a bigger investigation than #298/#341/#344/#349/#353/#361
Every prior TSan finding in this project was a single unsynchronized static/global variable (or a
shared instance that should have been per-object) in a narrow, specific class — findable by reading
that one class's source and fixable by adding a mutex or relocating the state. This finding points
at
OSD_ThreadPool/BOPTools_Parallel— OCCT's own shared-pool parallel-dispatch infrastructure,used internally by many algorithms, not just booleans. Properly root-causing it likely needs
reading
OSD_ThreadPool.cxx's actual job-queue/dispatch implementation (job submission, workerwake-up, completion tracking) to find the real mechanism, not pattern-matching against the prior
fixes' template.
Open questions
OSD_ThreadPool::DefaultPool()fundamentally unsafe for concurrent independent submitters(multiple unrelated top-level operations dispatching to it at the same time), or is this specific
to how
BOPTools_Parallel/BOPAlgo_PaveFilleruse it (e.g. a job-completion/context-scoping bugin the caller, not the pool itself)?
BRepMesh_IncrementalMesh'sInParalleloption,BRepCheck_Analyzer::SetParallel, or any otherSetRunParallel-style opt-in — or is it specific toBRepAlgoAPI_BuilderAlgo/BOPAlgo_PaveFiller?assuming it's novel)?
Priority
Not blocking anything currently shipping (#367's immediate fix removed the only bridge call site
that triggered it). Pick up as a dedicated TSan investigation when there's time for a genuinely
open-ended one, following the established minimal-module protocol
(
FoundationClasses+ModelingData+ModelingAlgorithms) plus direct reading ofOSD_ThreadPool.cxx/OSD_Parallel.hxx/BOPTools_Parallel.hxx.