#14424 stopped the crash in #14423 by rewriting the caller's case list with the survivors. That fixes one call site, but the hazard is general: RimDeltaSummaryEnsemble deleted its derived cases inline, from inside a loop the caller was still walking. Any caller holding a case list across removeCase() had the same bug.
Fixed structurally on lifetime-delta-ensemble-batch. The band-aid is removed as part of it.
Detach never destroys. rebuildDerivedCases() replaces the m_inUse pooling. It diffs the derived cases against the desired source pairs, keyed on the (case1, case2) pointer pair, reuses matches, creates what is missing, and returns the surplus instead of deleting it. Keying on the pointer pair makes the rebuild idempotent, including right after project load.
Destruction happens at a batch flush. RimSummaryCaseUpdateBatch is a plain scope object, ambient for its scope. Detached cases are destroyed when the outermost scope ends, after the dirty delta ensembles have regenerated in dependency order. Orphans are held as caf::PdmPointer, so a case the caller destroyed itself is skipped rather than destroyed twice. With no batch active both contribution points fall back to immediate execution, so unconverted call sites keep working. RicCloseSummaryCaseFeature::deleteSummaryCases opens the outermost batch, because it deletes the case list after removeCases returns.
Dependency traversal by PDM back-reference. RimSummaryEnsembleTools gains dependentDeltaEnsembles(), deltaEnsemblesInUpdateOrder() and wouldCreateDependencyCycle() — deduplicated, iterative, topologically ordered, back edges logged rather than traversed. Replaces findReferringEnsembles(), which recursed with no visited set and ran forever on a dependency cycle.
Verification
1033 unit tests pass. RemoveCases_NoDanglingInCallerVector asserts that every entry the caller handed over is alive inside the batch scope and destroyed exactly once after it. Removing just the batch line from that test reproduces the original signature: cases.size() 6 against aliveCount() 4, followed by SEH exception with code 0xc0000005.
Known follow-up
A dependency cycle stack-overflows in auto-name generation, independently of this fix: RimSummaryEnsemble::updateName ends in caseNameChanged.send(), which reaches back into updateSummaryEnsembleNames(). It terminates only because names converge, and auto-generated names never converge when A's name derives from B's and B's from A's. Cycle prevention in setEnsemble1()/setEnsemble2() is the fix.
Related: #14423, #14424, #14491, #14493. Branch and staged plan: magnesj#1042.
#14424 stopped the crash in #14423 by rewriting the caller's case list with the survivors. That fixes one call site, but the hazard is general:
RimDeltaSummaryEnsembledeleted its derived cases inline, from inside a loop the caller was still walking. Any caller holding a case list acrossremoveCase()had the same bug.Fixed structurally on
lifetime-delta-ensemble-batch. The band-aid is removed as part of it.Detach never destroys.
rebuildDerivedCases()replaces them_inUsepooling. It diffs the derived cases against the desired source pairs, keyed on the(case1, case2)pointer pair, reuses matches, creates what is missing, and returns the surplus instead of deleting it. Keying on the pointer pair makes the rebuild idempotent, including right after project load.Destruction happens at a batch flush.
RimSummaryCaseUpdateBatchis a plain scope object, ambient for its scope. Detached cases are destroyed when the outermost scope ends, after the dirty delta ensembles have regenerated in dependency order. Orphans are held ascaf::PdmPointer, so a case the caller destroyed itself is skipped rather than destroyed twice. With no batch active both contribution points fall back to immediate execution, so unconverted call sites keep working.RicCloseSummaryCaseFeature::deleteSummaryCasesopens the outermost batch, because it deletes the case list afterremoveCasesreturns.Dependency traversal by PDM back-reference.
RimSummaryEnsembleToolsgainsdependentDeltaEnsembles(),deltaEnsemblesInUpdateOrder()andwouldCreateDependencyCycle()— deduplicated, iterative, topologically ordered, back edges logged rather than traversed. ReplacesfindReferringEnsembles(), which recursed with no visited set and ran forever on a dependency cycle.Verification
1033 unit tests pass.
RemoveCases_NoDanglingInCallerVectorasserts that every entry the caller handed over is alive inside the batch scope and destroyed exactly once after it. Removing just the batch line from that test reproduces the original signature:cases.size()6 againstaliveCount()4, followed bySEH exception with code 0xc0000005.Known follow-up
A dependency cycle stack-overflows in auto-name generation, independently of this fix:
RimSummaryEnsemble::updateNameends incaseNameChanged.send(), which reaches back intoupdateSummaryEnsembleNames(). It terminates only because names converge, and auto-generated names never converge when A's name derives from B's and B's from A's. Cycle prevention insetEnsemble1()/setEnsemble2()is the fix.Related: #14423, #14424, #14491, #14493. Branch and staged plan: magnesj#1042.