router: invalidate items freed by PNS::NODE in the clearance cache

This was causing intermittent shove hiccups/freezes as RULE_RESOLVER::GetClearance() could return a bogus clearance value, taking
a cached value for an item that does not exist anymore. If the allocator/stack accidentally reclaimed such item's address - we were getting
very nasty and difficult to reproduce misbehaviours of the shove algorithm. Hopefully this patch fixes this. More info in the comments.

Note to self - I'm still not fully convinced I want PNS::ITEM pointers as the cache key, probably a unique counter would be better here.
This commit is contained in:
Tomasz Wlostowski 2023-05-31 23:20:28 +02:00
parent 14f6e32c74
commit 7966126040
4 changed files with 52 additions and 11 deletions

View File

@ -124,7 +124,7 @@ public:
int ClearanceEpsilon() const override { return m_clearanceEpsilon; } int ClearanceEpsilon() const override { return m_clearanceEpsilon; }
void ClearCacheForItem( const PNS::ITEM* aItem ) override; void ClearCacheForItems( std::vector<const PNS::ITEM*>& aItems ) override;
void ClearCaches() override; void ClearCaches() override;
private: private:
@ -410,13 +410,30 @@ bool PNS_PCBNEW_RULE_RESOLVER::QueryConstraint( PNS::CONSTRAINT_TYPE aType,
} }
void PNS_PCBNEW_RULE_RESOLVER::ClearCacheForItem( const PNS::ITEM* aItem ) void PNS_PCBNEW_RULE_RESOLVER::ClearCacheForItems( std::vector<const PNS::ITEM*>& aItems )
{ {
CLEARANCE_CACHE_KEY key = { aItem, nullptr, false }; int n_pruned = 0;
m_clearanceCache.erase( key ); std::set<const PNS::ITEM*> remainingItems( aItems.begin(), aItems.end() );
key.Flag = true; /* We need to carefully check both A and B item pointers in the cache against dirty/invalidated
m_clearanceCache.erase( key ); items in the set, as the clearance relation is commutative ( CL[a,b] == CL[b,a] ). The code
below is a bit ugly, but works in O(n*log(m)) and is run once or twice during ROUTER::Move() call
- so I hope it still gets better performance than no cache at all */
for( auto it = m_clearanceCache.begin(); it != m_clearanceCache.end(); )
{
bool dirty = remainingItems.find( it->first.A ) != remainingItems.end();
dirty |= remainingItems.find( it->first.B) != remainingItems.end();
if( dirty )
{
it = m_clearanceCache.erase( it );
n_pruned++;
} else
it++;
}
#if 0
printf("ClearCache : n_pruned %d\n", n_pruned );
#endif
} }
@ -492,7 +509,15 @@ int PNS_PCBNEW_RULE_RESOLVER::Clearance( const PNS::ITEM* aA, const PNS::ITEM* a
if( aUseClearanceEpsilon && rv > 0 ) if( aUseClearanceEpsilon && rv > 0 )
rv = std::max( 0, rv - m_clearanceEpsilon ); rv = std::max( 0, rv - m_clearanceEpsilon );
/* It makes no sense to put items that have no owning NODE in the cache - they can be allocated on stack
and we can't really invalidate them in the cache when they are destroyed. Probably a better idea would be
to use a static unique counter in PNS::ITEM constructor to generate the cache keys. */
if( aA && aB && aA->Owner() && aB->Owner() )
{
m_clearanceCache[ key ] = rv; m_clearanceCache[ key ] = rv;
}
return rv; return rv;
} }

View File

@ -87,7 +87,7 @@ NODE::~NODE()
m_joints.clear(); m_joints.clear();
std::vector<ITEM*> toDelete; std::vector<const ITEM*> toDelete;
toDelete.reserve( m_index->Size() ); toDelete.reserve( m_index->Size() );
@ -97,12 +97,17 @@ NODE::~NODE()
toDelete.push_back( item ); toDelete.push_back( item );
} }
for( ITEM* item : toDelete ) for( const ITEM* item : toDelete )
{ {
wxLogTrace( wxT( "PNS" ), wxT( "del item %p type %s" ), item, item->KindStr().c_str() ); wxLogTrace( wxT( "PNS" ), wxT( "del item %p type %s" ), item, item->KindStr().c_str() );
delete item; delete item;
} }
if( m_ruleResolver )
{
m_ruleResolver->ClearCacheForItems( toDelete );
}
releaseGarbage(); releaseGarbage();
unlinkParent(); unlinkParent();
@ -1389,6 +1394,9 @@ void NODE::releaseGarbage()
if( !isRoot() ) if( !isRoot() )
return; return;
std::vector<const ITEM*> cacheCheckItems;
cacheCheckItems.reserve( m_garbageItems.size() );
for( ITEM* item : m_garbageItems ) for( ITEM* item : m_garbageItems )
{ {
if( !item->BelongsTo( this ) ) if( !item->BelongsTo( this ) )
@ -1396,6 +1404,11 @@ void NODE::releaseGarbage()
} }
m_garbageItems.clear(); m_garbageItems.clear();
if( m_ruleResolver )
{
m_ruleResolver->ClearCacheForItems( cacheCheckItems );
}
} }

View File

@ -155,7 +155,7 @@ public:
virtual wxString NetName( int aNet ) = 0; virtual wxString NetName( int aNet ) = 0;
virtual void ClearCacheForItem( const ITEM* aItem ) {} virtual void ClearCacheForItems( std::vector<const PNS::ITEM*>& aItems ) {}
virtual void ClearCaches() {} virtual void ClearCaches() {}
virtual int ClearanceEpsilon() const { return 0; } virtual int ClearanceEpsilon() const { return 0; }

View File

@ -103,6 +103,7 @@ void ROUTER::ClearWorld()
{ {
if( m_world ) if( m_world )
{ {
m_world->SetRuleResolver( nullptr );
m_world->KillChildren(); m_world->KillChildren();
m_world.reset(); m_world.reset();
} }
@ -716,9 +717,11 @@ void ROUTER::updateView( NODE* aNode, ITEM_SET& aCurrent, bool aDragging )
aNode->GetUpdatedItems( removed, added ); aNode->GetUpdatedItems( removed, added );
std::vector<const PNS::ITEM*> cacheCheckItems( added.begin(), added.end() );
GetRuleResolver()->ClearCacheForItems( cacheCheckItems );
for( ITEM* item : added ) for( ITEM* item : added )
{ {
GetRuleResolver()->ClearCacheForItem( item );
int clearance = GetRuleResolver()->Clearance( item, nullptr ); int clearance = GetRuleResolver()->Clearance( item, nullptr );
m_iface->DisplayItem( item, clearance, aDragging ); m_iface->DisplayItem( item, clearance, aDragging );
} }