You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Companion to #341 (NCollection race characterisation), from OCCTReconstruct's contention audit (OCCTReconstruct#175/#309, 2026-07-21). Today the thread-safety surface is UNKNOWABLE from the API: OCCTSerial exists but is opt-in, callers discover unsafe calls one crash at a time (#298 was exactly this — the documented 'independent shapes are thread-safe' guarantee was wrong for fillet/chamfer), and the practical downstream response is blanket serialization (measured cost: 4213s vs 2066s for one consumer's suite). A defined and enforced contract at the bridge is both the safety fix and the speed-up.
Measured starting points (from the audit)
Bridge calls are not auto-locked; OCCTSerialQueue.swift:6-18 names BSpline adaptor caches, topology mutations, 'various algorithms' as racy — but no per-call classification exists.
OCCTBridge_Modeling.mm:1562 hard-codes builder.SetRunParallel(Standard_True): every boolean fans onto the process-shared OSD_ThreadPool::DefaultPool (~ncores workers). N concurrent callers (parallel test tasks, multi-session hosts) oversubscribe by design, and the caller has no knob. Meshing InParallel IS caller-controlled (OCCTBridge_Mesh.mm:211) — the asymmetry is incidental, not designed.
The bundled kernel is built WITHOUT TBB (890 OSD_ThreadPool symbols; OSD_Parallel_TBB.cxx.o compiled empty) — internal parallelism is OSD_ThreadPool-only; worth stating in docs since consumers assume TBB.
STEP writer mutates Interface_Static process globals (upstream OCCT#1179); downstream wraps its own calls in OCCTSerial (AssemblyExport.swift:290) but every consumer must independently know to.
Enforce: auto-acquire OCCTSerial inside the bridge for exclusive/global-state calls (correct-by-default), with a documented fast-path opt-out for callers that already hold the lock. Encapsulate Interface_Static snapshot/restore inside the export calls so no consumer needs to know.
Control internal parallelism: make SetRunParallel/pool sizing caller-controllable (per-call parameter or a process knob), so a consumer running N concurrent pipelines can bound total workers instead of N×ncores. Scoped OSD_ThreadPool instances per heavy call are worth evaluating against the shared default pool.
Throughput payoff: with a trustworthy concurrent-safe surface, consumers stop blanket-serializing — OCCTReconstruct's ~61 pure/independent-shape suites and its per-part sandbox children can use the cores. The speed-up comes from safety, not despite it.
Precedents: #298 + patch 0003 (the classify-then-fix loop works), patch 0010 (#319 — measured-runaway → bounded). Nothing here requires TBB or upstream OCCT changes except where #341's characterisation says so.
Companion to #341 (NCollection race characterisation), from OCCTReconstruct's contention audit (OCCTReconstruct#175/#309, 2026-07-21). Today the thread-safety surface is UNKNOWABLE from the API:
OCCTSerialexists but is opt-in, callers discover unsafe calls one crash at a time (#298 was exactly this — the documented 'independent shapes are thread-safe' guarantee was wrong for fillet/chamfer), and the practical downstream response is blanket serialization (measured cost: 4213s vs 2066s for one consumer's suite). A defined and enforced contract at the bridge is both the safety fix and the speed-up.Measured starting points (from the audit)
OCCTSerialQueue.swift:6-18names BSpline adaptor caches, topology mutations, 'various algorithms' as racy — but no per-call classification exists.OCCTBridge_Modeling.mm:1562hard-codesbuilder.SetRunParallel(Standard_True): every boolean fans onto the process-sharedOSD_ThreadPool::DefaultPool(~ncores workers). N concurrent callers (parallel test tasks, multi-session hosts) oversubscribe by design, and the caller has no knob. MeshingInParallelIS caller-controlled (OCCTBridge_Mesh.mm:211) — the asymmetry is incidental, not designed.OSD_ThreadPoolsymbols;OSD_Parallel_TBB.cxx.ocompiled empty) — internal parallelism is OSD_ThreadPool-only; worth stating in docs since consumers assume TBB.Interface_Staticprocess globals (upstream OCCT#1179); downstream wraps its own calls inOCCTSerial(AssemblyExport.swift:290) but every consumer must independently know to.Proposal (staged)
concurrent-safe(independent shapes, no shared caches) /exclusive(must hold the global lock) /global-state(mutates process globals). Publish the table in docs; back it with the NCollection race under parallel execution: documented downstream as 'run --no-parallel', never filed or characterised upstream — needs the #298 TSan protocol #341/SheetMetal.Builder.build silently returns an empty solid under parallel test execution (8/10 runs) — contradicts the documented "independent shapes are thread-safe" guarantee #298 TSan protocol rather than assertion.OCCTSerialinside the bridge forexclusive/global-statecalls (correct-by-default), with a documented fast-path opt-out for callers that already hold the lock. EncapsulateInterface_Staticsnapshot/restore inside the export calls so no consumer needs to know.SetRunParallel/pool sizing caller-controllable (per-call parameter or a process knob), so a consumer running N concurrent pipelines can bound total workers instead of N×ncores. ScopedOSD_ThreadPoolinstances per heavy call are worth evaluating against the shared default pool.concurrent-safesurface, consumers stop blanket-serializing — OCCTReconstruct's ~61 pure/independent-shape suites and its per-part sandbox children can use the cores. The speed-up comes from safety, not despite it.Precedents: #298 + patch 0003 (the classify-then-fix loop works), patch 0010 (#319 — measured-runaway → bounded). Nothing here requires TBB or upstream OCCT changes except where #341's characterisation says so.