mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 06:38:13 -05:00
#14423 Defer destruction of orphaned derived cases to a batch flush
Closing all summary cases while a delta ensemble is present crashed with a use-after-free. RicCloseSummaryCaseFeature::deleteSummaryCases holds a case list across removeCases and deletes it afterwards, while removeCases made the delta ensemble rebuild and destroy derived cases that are themselves part of that list. The previous fix rewrote the caller list with the surviving cases, which stopped the crash but left the hazard in place for any other caller holding a case list across a removal. Add RimSummaryCaseUpdateBatch, a plain scope object that is ambient for the duration of its scope. Removal now only detaches, and hands the detached cases to the batch. The outermost scope flushes, regenerating the dirty delta ensembles in dependency order first so a chained delta ensemble sees the final state of its source, then destroying the orphans with caf::PdmObjectHandleTools::deleteObjects. A nested batch contributes to the outermost one and never flushes. Orphans are held as guarded pointers, so a case the caller destroyed itself is skipped instead of being destroyed twice. Both contribution points fall back to immediate execution when no batch is active, so call sites that do not open one keep behaving as before. Open a batch in RimSummaryCaseMainCollection::removeCases and in RicCloseSummaryCaseFeature::deleteSummaryCases, which is the outermost of the two and therefore keeps the detached cases alive across its own deleteObjects call. removeCases no longer rewrites the caller list, so it now takes it by const reference.
This commit is contained in:
1 parent
6c0cc9c363
commit
8ac2255832
9 files changed
+334
-43
No files matched your search
@@ -28,6 +28,7 @@
|
||||
#include "RimProject.h"
|
||||
#include "RimSummaryCase.h"
|
||||
#include "RimSummaryCaseMainCollection.h"
|
||||
#include "RimSummaryCaseUpdateBatch.h"
|
||||
#include "RimSummaryMultiPlot.h"
|
||||
#include "RimSummaryMultiPlotCollection.h"
|
||||
#include "RimSummaryPlot.h"
|
||||
@@ -61,6 +62,10 @@ void RicCloseSummaryCaseFeature::setupActionLook( QAction* actionToSetup )
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RicCloseSummaryCaseFeature::deleteSummaryCases( std::vector<RimSummaryCase*> cases )
|
||||
{
|
||||
// The case list is used all the way down to the delete below. Keep a batch open for that whole span, so a derived
|
||||
// case detached by the removal is not destroyed while this list still refers to it.
|
||||
RimSummaryCaseUpdateBatch updateBatch;
|
||||
|
||||
RimSummaryMultiPlotCollection* summaryPlotColl = RiaSummaryTools::summaryMultiPlotCollection();
|
||||
RimSummaryCaseMainCollection* summaryCaseMainCollection = RiaSummaryTools::summaryCaseMainCollection();
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryAddressSelector.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimEnsembleCrossPlotStatisticsCase.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryEnsembleTools.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryCaseUpdateBatch.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/RimSummaryPlotReadOut.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "RimDeltaSummaryEnsemble.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimSummaryCaseMainCollection.h"
|
||||
#include "RimSummaryCaseUpdateBatch.h"
|
||||
#include "RimSummaryEnsemble.h"
|
||||
#include "RimSummaryEnsembleTools.h"
|
||||
|
||||
@@ -161,10 +162,7 @@ void RimDeltaSummaryEnsemble::createDerivedEnsembleCases()
|
||||
referring->createDerivedEnsembleCases();
|
||||
}
|
||||
|
||||
for ( auto orphanedCase : orphanedCases )
|
||||
{
|
||||
delete orphanedCase;
|
||||
}
|
||||
RimSummaryCaseUpdateBatch::orphan( orphanedCases );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "RimProject.h"
|
||||
#include "RimRftPlotCollection.h"
|
||||
#include "RimSummaryCase.h"
|
||||
#include "RimSummaryCaseUpdateBatch.h"
|
||||
#include "RimSummaryCurve.h"
|
||||
#include "RimSummaryEnsemble.h"
|
||||
#include "RimSummaryMultiPlotCollection.h"
|
||||
@@ -227,16 +228,16 @@ void RimSummaryCaseMainCollection::removeCase( RimSummaryCase* summaryCase, bool
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryCaseMainCollection::removeCases( std::vector<RimSummaryCase*>& cases )
|
||||
void RimSummaryCaseMainCollection::removeCases( const std::vector<RimSummaryCase*>& cases )
|
||||
{
|
||||
// Removing one case can delete other cases in the list. A delta ensemble recreates its derived cases when a source
|
||||
// case is removed, and deletes the derived cases no longer in use. Use guarded pointers to avoid touching deleted
|
||||
// cases, and return only the cases that are still alive.
|
||||
std::vector<caf::PdmPointer<RimSummaryCase>> guardedCases( cases.begin(), cases.end() );
|
||||
// Removing one case makes a delta ensemble rebuild its derived cases, and some of those derived cases can be part
|
||||
// of this very list. The batch detaches them now and destroys them when the outermost batch scope ends, so a caller
|
||||
// holding on to the list across this call never sees a freed case, as long as it opens a batch of its own.
|
||||
RimSummaryCaseUpdateBatch updateBatch;
|
||||
|
||||
for ( const auto& sumCase : guardedCases )
|
||||
for ( auto sumCase : cases )
|
||||
{
|
||||
if ( sumCase.notNull() ) removeCase( sumCase, false );
|
||||
removeCase( sumCase, false );
|
||||
}
|
||||
|
||||
for ( RimSummaryEnsemble* ensemble : m_ensembles )
|
||||
@@ -244,12 +245,6 @@ void RimSummaryCaseMainCollection::removeCases( std::vector<RimSummaryCase*>& ca
|
||||
ensemble->updateReferringCurveSetsZoomAll();
|
||||
}
|
||||
|
||||
cases.clear();
|
||||
for ( const auto& sumCase : guardedCases )
|
||||
{
|
||||
if ( sumCase.notNull() ) cases.push_back( sumCase );
|
||||
}
|
||||
|
||||
dataSourceHasChanged.send();
|
||||
}
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ public:
|
||||
void addCases( const std::vector<RimSummaryCase*> cases );
|
||||
void addCase( RimSummaryCase* summaryCase );
|
||||
void removeCase( RimSummaryCase* summaryCase, bool notifyChange = true );
|
||||
// Cases deleted as a side effect of the removal are erased from the input vector
|
||||
void removeCases( std::vector<RimSummaryCase*>& cases );
|
||||
// Open a RimSummaryCaseUpdateBatch around this call if the case list is used afterwards, see removeCases()
|
||||
void removeCases( const std::vector<RimSummaryCase*>& cases );
|
||||
void moveCase( RimSummaryCase* summaryCase, int destinationIndex );
|
||||
|
||||
RimSummaryEnsemble* addEnsemble( const std::vector<RimSummaryCase*>& summaryCases,
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026- Equinor ASA
|
||||
//
|
||||
// ResInsight is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "RimSummaryCaseUpdateBatch.h"
|
||||
|
||||
#include "RimDeltaSummaryCase.h"
|
||||
#include "RimDeltaSummaryEnsemble.h"
|
||||
#include "RimSummaryEnsembleTools.h"
|
||||
|
||||
#include "cafPdmObjectHandleTools.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
|
||||
RimSummaryCaseUpdateBatch* RimSummaryCaseUpdateBatch::sm_current = nullptr;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RimSummaryCaseUpdateBatch::RimSummaryCaseUpdateBatch()
|
||||
{
|
||||
// A nested batch contributes to the outermost one and never flushes
|
||||
if ( !sm_current ) sm_current = this;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RimSummaryCaseUpdateBatch::~RimSummaryCaseUpdateBatch()
|
||||
{
|
||||
if ( sm_current != this ) return;
|
||||
|
||||
// Clear the ambient batch before flushing. Work triggered by the flush itself is executed immediately, which is
|
||||
// safe here, as the outermost scope is ending and no caller is holding a case list across this point.
|
||||
sm_current = nullptr;
|
||||
|
||||
flush();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
bool RimSummaryCaseUpdateBatch::isActive()
|
||||
{
|
||||
return sm_current != nullptr;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryCaseUpdateBatch::orphan( const std::vector<RimDeltaSummaryCase*>& orphanedCases )
|
||||
{
|
||||
if ( orphanedCases.empty() ) return;
|
||||
|
||||
if ( sm_current )
|
||||
{
|
||||
sm_current->m_orphanedCases.insert( sm_current->m_orphanedCases.end(), orphanedCases.begin(), orphanedCases.end() );
|
||||
return;
|
||||
}
|
||||
|
||||
auto casesToDelete = orphanedCases;
|
||||
caf::PdmObjectHandleTools::deleteObjects( casesToDelete );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryCaseUpdateBatch::markDeltaEnsembleDirty( RimDeltaSummaryEnsemble* deltaEnsemble )
|
||||
{
|
||||
if ( !deltaEnsemble ) return;
|
||||
|
||||
if ( sm_current )
|
||||
{
|
||||
auto& dirtyEnsembles = sm_current->m_dirtyEnsembles;
|
||||
|
||||
auto isSameEnsemble = [deltaEnsemble]( const caf::PdmPointer<RimDeltaSummaryEnsemble>& candidate )
|
||||
{ return candidate.p() == deltaEnsemble; };
|
||||
|
||||
if ( std::none_of( dirtyEnsembles.begin(), dirtyEnsembles.end(), isSameEnsemble ) )
|
||||
{
|
||||
dirtyEnsembles.push_back( deltaEnsemble );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
deltaEnsemble->onSourceEnsembleChanged();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
std::vector<RimDeltaSummaryEnsemble*> RimSummaryCaseUpdateBatch::ensemblesToRegenerate() const
|
||||
{
|
||||
std::vector<RimSummaryEnsemble*> dirtyEnsembles;
|
||||
for ( const auto& dirtyEnsemble : m_dirtyEnsembles )
|
||||
{
|
||||
// An ensemble marked dirty can have been deleted before the flush
|
||||
if ( dirtyEnsemble.notNull() ) dirtyEnsembles.push_back( dirtyEnsemble.p() );
|
||||
}
|
||||
|
||||
// Everything depending on the dirty ensembles, in dependency order. A dirty ensemble depending on another dirty
|
||||
// ensemble shows up here, and must be regenerated in this order rather than as a root.
|
||||
auto dependents = RimSummaryEnsembleTools::deltaEnsemblesInUpdateOrder( dirtyEnsembles );
|
||||
|
||||
std::set<const RimSummaryEnsemble*> alreadyOrdered( dependents.begin(), dependents.end() );
|
||||
|
||||
std::vector<RimDeltaSummaryEnsemble*> ordered;
|
||||
for ( const auto& dirtyEnsemble : m_dirtyEnsembles )
|
||||
{
|
||||
if ( dirtyEnsemble.isNull() ) continue;
|
||||
if ( !alreadyOrdered.insert( dirtyEnsemble.p() ).second ) continue;
|
||||
|
||||
ordered.push_back( dirtyEnsemble.p() );
|
||||
}
|
||||
|
||||
ordered.insert( ordered.end(), dependents.begin(), dependents.end() );
|
||||
|
||||
return ordered;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Regenerate before destroying, so a chained delta ensemble sees the final state of its source
|
||||
/// before anything is freed.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimSummaryCaseUpdateBatch::flush()
|
||||
{
|
||||
for ( auto deltaEnsemble : ensemblesToRegenerate() )
|
||||
{
|
||||
deltaEnsemble->onSourceEnsembleChanged();
|
||||
}
|
||||
m_dirtyEnsembles.clear();
|
||||
|
||||
std::vector<RimDeltaSummaryCase*> casesToDelete;
|
||||
for ( const auto& orphanedCase : m_orphanedCases )
|
||||
{
|
||||
// An orphaned case can already have been destroyed by the code that detached it
|
||||
if ( orphanedCase.notNull() ) casesToDelete.push_back( orphanedCase.p() );
|
||||
}
|
||||
m_orphanedCases.clear();
|
||||
|
||||
caf::PdmObjectHandleTools::deleteObjects( casesToDelete );
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2026- Equinor ASA
|
||||
//
|
||||
// ResInsight is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE.
|
||||
//
|
||||
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
||||
// for more details.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cafPdmPointer.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
class RimDeltaSummaryCase;
|
||||
class RimDeltaSummaryEnsemble;
|
||||
|
||||
//==================================================================================================
|
||||
/// Collects the work that must happen once a set of summary case mutations has settled. Removing a
|
||||
/// case only detaches it and hands it to the batch, destruction happens when the batch flushes.
|
||||
///
|
||||
/// This is a plain stack object, ambient for the duration of its scope. A nested batch contributes to
|
||||
/// the outermost one, which is the only one that flushes. Open a batch wherever a case list is held
|
||||
/// across a mutation, so no object in that list is freed while the list is still in use.
|
||||
///
|
||||
/// The contribution points fall back to immediate execution when no batch is active, so call sites
|
||||
/// that do not open one keep behaving as before.
|
||||
//==================================================================================================
|
||||
class RimSummaryCaseUpdateBatch
|
||||
{
|
||||
public:
|
||||
RimSummaryCaseUpdateBatch();
|
||||
~RimSummaryCaseUpdateBatch();
|
||||
|
||||
RimSummaryCaseUpdateBatch( const RimSummaryCaseUpdateBatch& ) = delete;
|
||||
RimSummaryCaseUpdateBatch& operator=( const RimSummaryCaseUpdateBatch& ) = delete;
|
||||
|
||||
// Hand over detached cases for destruction at the flush. Destroys them immediately if no batch is active.
|
||||
static void orphan( const std::vector<RimDeltaSummaryCase*>& orphanedCases );
|
||||
|
||||
// Schedule a regeneration of the derived cases of a delta ensemble. Regenerates immediately if no batch is active.
|
||||
static void markDeltaEnsembleDirty( RimDeltaSummaryEnsemble* deltaEnsemble );
|
||||
|
||||
static bool isActive();
|
||||
|
||||
private:
|
||||
void flush();
|
||||
|
||||
// The dirty ensembles and everything depending on them, ordered so a delta ensemble is regenerated before the delta
|
||||
// ensembles using it as a source
|
||||
std::vector<RimDeltaSummaryEnsemble*> ensemblesToRegenerate() const;
|
||||
|
||||
private:
|
||||
std::vector<caf::PdmPointer<RimDeltaSummaryCase>> m_orphanedCases;
|
||||
std::vector<caf::PdmPointer<RimDeltaSummaryEnsemble>> m_dirtyEnsembles;
|
||||
|
||||
static RimSummaryCaseUpdateBatch* sm_current;
|
||||
};
|
||||
@@ -7,9 +7,11 @@
|
||||
#include "RimDeltaSummaryEnsemble.h"
|
||||
#include "RimMockSummaryCase.h"
|
||||
#include "RimSummaryCaseMainCollection.h"
|
||||
#include "RimSummaryCaseUpdateBatch.h"
|
||||
#include "RimSummaryEnsemble.h"
|
||||
#include "RimSummaryEnsembleTools.h"
|
||||
|
||||
#include "cafPdmPointer.h"
|
||||
#include "cafPdmPtrField.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -239,6 +241,69 @@ TEST_F( RimDeltaSummaryEnsembleTest, Rebuild_NoMatchingRealizations )
|
||||
EXPECT_TRUE( deltaEnsemble->allDerivedCases().empty() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST_F( RimDeltaSummaryEnsembleTest, Batch_DeletionDeferredToFlush )
|
||||
{
|
||||
auto* ensemble1 = createEnsemble( "Ensemble 1", { 0, 1 } );
|
||||
auto* ensemble2 = createEnsemble( "Ensemble 2", { 0, 1 } );
|
||||
|
||||
auto* deltaEnsemble = createDeltaEnsemble( ensemble1, ensemble2 );
|
||||
deltaEnsemble->createDerivedEnsembleCases();
|
||||
ASSERT_EQ( size_t( 2 ), deltaEnsemble->allDerivedCases().size() );
|
||||
|
||||
caf::PdmPointer<RimDeltaSummaryCase> guardedCase = deltaEnsemble->allDerivedCases().front();
|
||||
|
||||
{
|
||||
RimSummaryCaseUpdateBatch updateBatch;
|
||||
EXPECT_TRUE( RimSummaryCaseUpdateBatch::isActive() );
|
||||
|
||||
auto* sourceCase = ensemble1->allSummaryCases().front();
|
||||
mainCollection()->removeCase( sourceCase, false );
|
||||
delete sourceCase;
|
||||
|
||||
// The derived case is detached, but still alive
|
||||
EXPECT_EQ( size_t( 1 ), deltaEnsemble->allDerivedCases().size() );
|
||||
EXPECT_TRUE( guardedCase.notNull() );
|
||||
}
|
||||
|
||||
EXPECT_FALSE( RimSummaryCaseUpdateBatch::isActive() );
|
||||
EXPECT_TRUE( guardedCase.isNull() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST_F( RimDeltaSummaryEnsembleTest, Batch_NestingFlushesOnce )
|
||||
{
|
||||
auto* ensemble1 = createEnsemble( "Ensemble 1", { 0, 1 } );
|
||||
auto* ensemble2 = createEnsemble( "Ensemble 2", { 0, 1 } );
|
||||
|
||||
auto* deltaEnsemble = createDeltaEnsemble( ensemble1, ensemble2 );
|
||||
deltaEnsemble->createDerivedEnsembleCases();
|
||||
ASSERT_EQ( size_t( 2 ), deltaEnsemble->allDerivedCases().size() );
|
||||
|
||||
caf::PdmPointer<RimDeltaSummaryCase> guardedCase = deltaEnsemble->allDerivedCases().front();
|
||||
|
||||
{
|
||||
RimSummaryCaseUpdateBatch outerBatch;
|
||||
|
||||
{
|
||||
RimSummaryCaseUpdateBatch innerBatch;
|
||||
|
||||
auto* sourceCase = ensemble1->allSummaryCases().front();
|
||||
mainCollection()->removeCase( sourceCase, false );
|
||||
delete sourceCase;
|
||||
}
|
||||
|
||||
// The inner scope contributes to the outer batch and must not flush
|
||||
EXPECT_TRUE( guardedCase.notNull() );
|
||||
}
|
||||
|
||||
EXPECT_TRUE( guardedCase.isNull() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "RimDeltaSummaryEnsemble.h"
|
||||
#include "RimMockSummaryCase.h"
|
||||
#include "RimSummaryCaseMainCollection.h"
|
||||
#include "RimSummaryCaseUpdateBatch.h"
|
||||
#include "RimSummaryEnsemble.h"
|
||||
|
||||
#include "cafPdmPointer.h"
|
||||
@@ -30,10 +31,11 @@ RimSummaryCase* createMockCase( int realizationNumber )
|
||||
} // namespace
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Removing a source case makes a delta ensemble recreate and delete derived cases. The derived cases
|
||||
/// are part of the list of cases to remove, and must not be left as dangling pointers in that list.
|
||||
/// Removing a source case makes a delta ensemble rebuild its derived cases. Those derived cases are
|
||||
/// part of the list of cases to remove, and must stay alive for as long as the caller holds that
|
||||
/// list. The caller states that span by opening a RimSummaryCaseUpdateBatch.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RimSummaryCaseMainCollection, RemoveCasesReferredByDeltaEnsemble )
|
||||
TEST( RimSummaryCaseMainCollection, RemoveCases_NoDanglingInCallerVector )
|
||||
{
|
||||
RimSummaryCaseMainCollection* mainCollection = RiaSummaryTools::summaryCaseMainCollection();
|
||||
|
||||
@@ -54,30 +56,30 @@ TEST( RimSummaryCaseMainCollection, RemoveCasesReferredByDeltaEnsemble )
|
||||
// Guarded pointers are set to null when the object is deleted
|
||||
std::vector<caf::PdmPointer<RimSummaryCase>> guardedCases( cases.begin(), cases.end() );
|
||||
|
||||
mainCollection->removeCases( cases );
|
||||
|
||||
size_t aliveCount = 0;
|
||||
for ( const auto& guardedCase : guardedCases )
|
||||
auto aliveCount = [&guardedCases]()
|
||||
{
|
||||
if ( guardedCase.notNull() ) aliveCount++;
|
||||
return static_cast<size_t>( std::count_if( guardedCases.begin(),
|
||||
guardedCases.end(),
|
||||
[]( const caf::PdmPointer<RimSummaryCase>& guardedCase )
|
||||
{ return guardedCase.notNull(); } ) );
|
||||
};
|
||||
|
||||
{
|
||||
RimSummaryCaseUpdateBatch updateBatch;
|
||||
|
||||
mainCollection->removeCases( cases );
|
||||
|
||||
// The two derived cases are detached by the delta ensemble, but not destroyed while the batch is open
|
||||
EXPECT_EQ( cases.size(), aliveCount() );
|
||||
|
||||
for ( auto* summaryCase : cases )
|
||||
{
|
||||
delete summaryCase;
|
||||
}
|
||||
}
|
||||
|
||||
// The two derived cases are deleted by the delta ensemble during removal
|
||||
EXPECT_EQ( size_t( 4 ), aliveCount );
|
||||
EXPECT_EQ( aliveCount, cases.size() );
|
||||
|
||||
// No deleted case is left behind in the list
|
||||
for ( auto* summaryCase : cases )
|
||||
{
|
||||
auto isAlive = [summaryCase]( const caf::PdmPointer<RimSummaryCase>& guardedCase )
|
||||
{ return guardedCase.notNull() && guardedCase.p() == summaryCase; };
|
||||
EXPECT_TRUE( std::any_of( guardedCases.begin(), guardedCases.end(), isAlive ) );
|
||||
}
|
||||
|
||||
for ( auto* summaryCase : cases )
|
||||
{
|
||||
delete summaryCase;
|
||||
}
|
||||
// Everything the caller handed over has been destroyed exactly once
|
||||
EXPECT_EQ( size_t( 0 ), aliveCount() );
|
||||
|
||||
mainCollection->removeEnsemble( deltaEnsemble );
|
||||
mainCollection->removeEnsemble( ensemble1 );
|
||||
|
||||
Reference in new issue
Block a user