Skip to content

OSD_ThreadPool/BOPTools_Parallel: root-cause the cross-caller corruption behind #367 #369

Description

@gsdali

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.

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, 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions