Add LEAK_AT_EXIT wrapper to skip destructor calls at program exit

GlobalLibraries and PreloadedFootprints each have deep object
hierarchies that take noticeable time to destroy during shutdown.
Since the OS reclaims all process memory anyway, running these
destructors is wasted work. The new LEAK_AT_EXIT template wrapper
heap-allocates the wrapped object on first access and never frees it,
speeding up program exit.

When built with address sanitizer, it uses __lsan_ignore_object to
prevent false positive leak reports.  Also added a valgrind
suppression file for the same purpose.
This commit is contained in:
Seth Hillbrand
2026-01-23 17:13:35 -08:00
parent 05c8666ee9
commit ca7259ab95
8 changed files with 178 additions and 20 deletions
+1 -1
View File
@@ -31,7 +31,7 @@
#include <wx/log.h>
std::map<wxString, LIB_DATA> DESIGN_BLOCK_LIBRARY_ADAPTER::GlobalLibraries;
LEAK_AT_EXIT<std::map<wxString, LIB_DATA>> DESIGN_BLOCK_LIBRARY_ADAPTER::GlobalLibraries;
std::shared_mutex DESIGN_BLOCK_LIBRARY_ADAPTER::GlobalLibraryMutex;
+6 -4
View File
@@ -24,6 +24,7 @@
#include <kicommon.h>
#include <lib_id.h>
#include <core/leak_at_exit.h>
#include <libraries/library_manager.h>
class DESIGN_BLOCK;
@@ -145,8 +146,8 @@ public:
protected:
std::map<wxString, LIB_DATA>& globalLibs() override { return GlobalLibraries; }
std::map<wxString, LIB_DATA>& globalLibs() const override { return GlobalLibraries; }
std::map<wxString, LIB_DATA>& globalLibs() override { return GlobalLibraries.Get(); }
std::map<wxString, LIB_DATA>& globalLibs() const override { return GlobalLibraries.Get(); }
std::shared_mutex& globalLibsMutex() override { return GlobalLibraryMutex; }
std::shared_mutex& globalLibsMutex() const override { return GlobalLibraryMutex; }
@@ -162,8 +163,9 @@ private:
static DESIGN_BLOCK_IO* dbplugin( const LIB_DATA* aRow );
// The global libraries, potentially shared between multiple different open
// projects, each of which has their own instance of this adapter class
static std::map<wxString, LIB_DATA> GlobalLibraries;
// projects, each of which has their own instance of this adapter class.
// Wrapped in LEAK_AT_EXIT to skip destruction at program exit for faster shutdown.
static LEAK_AT_EXIT<std::map<wxString, LIB_DATA>> GlobalLibraries;
static std::shared_mutex GlobalLibraryMutex;
};
@@ -43,7 +43,7 @@ using namespace std::chrono_literals;
const char* SYMBOL_LIBRARY_ADAPTER::PropPowerSymsOnly = "pwr_sym_only";
const char* SYMBOL_LIBRARY_ADAPTER::PropNonPowerSymsOnly = "non_pwr_sym_only";
std::map<wxString, LIB_DATA> SYMBOL_LIBRARY_ADAPTER::GlobalLibraries;
LEAK_AT_EXIT<std::map<wxString, LIB_DATA>> SYMBOL_LIBRARY_ADAPTER::GlobalLibraries;
std::shared_mutex SYMBOL_LIBRARY_ADAPTER::GlobalLibraryMutex;
@@ -303,7 +303,7 @@ bool SYMBOL_LIBRARY_ADAPTER::IsSymbolLibWritable( const wxString& aLib )
{
std::shared_lock lock( GlobalLibraryMutex );
if( auto it = GlobalLibraries.find( aLib ); it != GlobalLibraries.end() )
if( auto it = GlobalLibraries.Get().find( aLib ); it != GlobalLibraries.Get().end() )
return it->second.plugin->IsLibraryWritable( getUri( it->second.row ) );
}
+4 -3
View File
@@ -24,6 +24,7 @@
#include <future>
#include <lib_id.h>
#include <core/leak_at_exit.h>
#include <libraries/library_manager.h>
#include <sch_io/sch_io.h>
@@ -154,8 +155,8 @@ public:
int GetModifyHash() const;
protected:
std::map<wxString, LIB_DATA>& globalLibs() override { return GlobalLibraries; }
std::map<wxString, LIB_DATA>& globalLibs() const override { return GlobalLibraries; }
std::map<wxString, LIB_DATA>& globalLibs() override { return GlobalLibraries.Get(); }
std::map<wxString, LIB_DATA>& globalLibs() const override { return GlobalLibraries.Get(); }
std::shared_mutex& globalLibsMutex() override { return GlobalLibraryMutex; }
std::shared_mutex& globalLibsMutex() const override { return GlobalLibraryMutex; }
@@ -168,7 +169,7 @@ protected:
private:
static SCH_IO* schplugin( const LIB_DATA* aRow );
static std::map<wxString, LIB_DATA> GlobalLibraries;
static LEAK_AT_EXIT<std::map<wxString, LIB_DATA>> GlobalLibraries;
static std::shared_mutex GlobalLibraryMutex;
};
+105
View File
@@ -0,0 +1,105 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* This program 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.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KICAD_LEAK_AT_EXIT_H
#define KICAD_LEAK_AT_EXIT_H
#include <mutex>
/**
* @file leak_at_exit.h
* @brief Utilities for intentionally "leaking" memory at program exit.
*
* When a program exits, the OS reclaims all process memory anyway. Running
* destructors that only free memory is wasted work that slows down shutdown.
* This is especially noticeable for large caches like library symbol/footprint
* data structures with deep object hierarchies.
*
* When built with address sanitizer (KICAD_SANITIZE_ADDRESS), these utilities
* use __lsan_ignore_object() to mark the memory as intentionally leaked,
* preventing false positive leak reports.
*/
#ifdef KICAD_SANITIZE_ADDRESS
#include <sanitizer/lsan_interface.h>
#define LSAN_IGNORE( ptr ) __lsan_ignore_object( ptr )
#else
#define LSAN_IGNORE( ptr ) (void)( ptr )
#endif
/**
* A wrapper for static data that should not be destroyed at program exit.
*
* Usage:
* // Instead of:
* static std::map<wxString, Data> GlobalData;
*
* // Use:
* static LEAK_AT_EXIT<std::map<wxString, Data>> GlobalData;
*
* The wrapped object is heap-allocated on first access and never freed.
* This avoids running potentially expensive destructors during static
* destruction, speeding up program exit.
*
* Each LEAK_AT_EXIT instance has its own storage, so multiple static
* variables of the same type will each get their own heap allocation.
*
* Thread-safe for concurrent first access (uses std::call_once).
*/
template <typename T>
class LEAK_AT_EXIT
{
public:
LEAK_AT_EXIT() : m_instance( nullptr ) {}
// Non-copyable, non-movable
LEAK_AT_EXIT( const LEAK_AT_EXIT& ) = delete;
LEAK_AT_EXIT& operator=( const LEAK_AT_EXIT& ) = delete;
LEAK_AT_EXIT( LEAK_AT_EXIT&& ) = delete;
LEAK_AT_EXIT& operator=( LEAK_AT_EXIT&& ) = delete;
/**
* Access the underlying object, creating it on first call.
* The object is intentionally never destroyed.
*/
T& Get()
{
std::call_once( m_initFlag, [this]()
{
m_instance = new T();
LSAN_IGNORE( m_instance );
} );
return *m_instance;
}
T& operator*() { return Get(); }
T* operator->() { return &Get(); }
// Allow implicit conversion to reference for compatibility
operator T&() { return Get(); }
private:
T* m_instance;
std::once_flag m_initFlag;
};
#endif // KICAD_LEAK_AT_EXIT_H
+6 -6
View File
@@ -34,11 +34,11 @@
using namespace std::chrono_literals;
std::map<wxString, LIB_DATA> FOOTPRINT_LIBRARY_ADAPTER::GlobalLibraries;
LEAK_AT_EXIT<std::map<wxString, LIB_DATA>> FOOTPRINT_LIBRARY_ADAPTER::GlobalLibraries;
std::shared_mutex FOOTPRINT_LIBRARY_ADAPTER::GlobalLibraryMutex;
std::map<wxString, std::vector<std::unique_ptr<FOOTPRINT>>> FOOTPRINT_LIBRARY_ADAPTER::PreloadedFootprints;
LEAK_AT_EXIT<std::map<wxString, std::vector<std::unique_ptr<FOOTPRINT>>>> FOOTPRINT_LIBRARY_ADAPTER::PreloadedFootprints;
std::shared_mutex FOOTPRINT_LIBRARY_ADAPTER::PreloadedFootprintsMutex;
@@ -94,7 +94,7 @@ void FOOTPRINT_LIBRARY_ADAPTER::enumerateLibrary( LIB_DATA* aLib )
{
std::unique_lock lock( PreloadedFootprintsMutex );
PreloadedFootprints[nickname] = std::move( footprints );
PreloadedFootprints.Get()[nickname] = std::move( footprints );
}
}
@@ -141,9 +141,9 @@ std::vector<FOOTPRINT*> FOOTPRINT_LIBRARY_ADAPTER::GetFootprints( const wxString
std::vector<FOOTPRINT*> footprints;
std::shared_lock lock( PreloadedFootprintsMutex );
auto it = PreloadedFootprints.find( aNickname );
auto it = PreloadedFootprints.Get().find( aNickname );
if( it == PreloadedFootprints.end() )
if( it == PreloadedFootprints.Get().end() )
return footprints;
footprints.reserve( it->second.size() );
@@ -361,7 +361,7 @@ bool FOOTPRINT_LIBRARY_ADAPTER::IsFootprintLibWritable( const wxString& aLib )
{
std::shared_lock lock( GlobalLibraryMutex );
if( auto it = GlobalLibraries.find( aLib ); it != GlobalLibraries.end() )
if( auto it = GlobalLibraries.Get().find( aLib ); it != GlobalLibraries.Get().end() )
return it->second.plugin->IsLibraryWritable( getUri( it->second.row ) );
}
+5 -4
View File
@@ -23,6 +23,7 @@
#define FOOTPRINT_LIBRARY_ADAPTER_H
#include <lib_id.h>
#include <core/leak_at_exit.h>
#include <libraries/library_manager.h>
#include <pcb_io/pcb_io.h>
#include <project.h>
@@ -162,8 +163,8 @@ public:
bool IsFootprintLibWritable( const wxString& aNickname );
protected:
std::map<wxString, LIB_DATA>& globalLibs() override { return GlobalLibraries; }
std::map<wxString, LIB_DATA>& globalLibs() const override { return GlobalLibraries; }
std::map<wxString, LIB_DATA>& globalLibs() override { return GlobalLibraries.Get(); }
std::map<wxString, LIB_DATA>& globalLibs() const override { return GlobalLibraries.Get(); }
std::shared_mutex& globalLibsMutex() override { return GlobalLibraryMutex; }
std::shared_mutex& globalLibsMutex() const override { return GlobalLibraryMutex; }
@@ -176,12 +177,12 @@ protected:
private:
static PCB_IO* pcbplugin( const LIB_DATA* aRow );
static std::map<wxString, LIB_DATA> GlobalLibraries;
static LEAK_AT_EXIT<std::map<wxString, LIB_DATA>> GlobalLibraries;
static std::shared_mutex GlobalLibraryMutex;
/// Storage for preloaded footprints, indexed by library nickname.
/// These are cloned during library enumeration so GetFootprints() returns instantly.
static std::map<wxString, std::vector<std::unique_ptr<FOOTPRINT>>> PreloadedFootprints;
static LEAK_AT_EXIT<std::map<wxString, std::vector<std::unique_ptr<FOOTPRINT>>>> PreloadedFootprints;
static std::shared_mutex PreloadedFootprintsMutex;
};
+49
View File
@@ -0,0 +1,49 @@
# Valgrind suppression file for KiCad
#
# Usage: valgrind --suppressions=tools/valgrind.supp ./build/bin/kicad
#
# These suppressions address intentional memory leaks and known issues
# in external libraries.
# Intentional memory leaks at shutdown for faster exit
# The GlobalLibraries maps in library adapters are intentionally leaked at
# exit using LEAK_AT_EXIT<> wrapper. The OS reclaims all process memory
# anyway, so skipping these destructors speeds up shutdown significantly.
# See include/core/leak_at_exit.h for details.
{
SYMBOL_LIBRARY_ADAPTER_GlobalLibraries_intentional_leak
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*SYMBOL_LIBRARY_ADAPTER*GlobalLibraries*
}
{
FOOTPRINT_LIBRARY_ADAPTER_GlobalLibraries_intentional_leak
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*FOOTPRINT_LIBRARY_ADAPTER*GlobalLibraries*
}
{
FOOTPRINT_LIBRARY_ADAPTER_PreloadedFootprints_intentional_leak
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*FOOTPRINT_LIBRARY_ADAPTER*PreloadedFootprints*
}
{
DESIGN_BLOCK_LIBRARY_ADAPTER_GlobalLibraries_intentional_leak
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*DESIGN_BLOCK_LIBRARY_ADAPTER*GlobalLibraries*
}
# Alternative pattern matching for LEAK_AT_EXIT wrapper
{
LEAK_AT_EXIT_intentional_leak
Memcheck:Leak
match-leak-kinds: reachable
...
fun:*LEAK_AT_EXIT*Get*
}