diff --git a/common/design_block_library_adapter.cpp b/common/design_block_library_adapter.cpp index 00f641c914..074969b492 100644 --- a/common/design_block_library_adapter.cpp +++ b/common/design_block_library_adapter.cpp @@ -31,7 +31,7 @@ #include -std::map DESIGN_BLOCK_LIBRARY_ADAPTER::GlobalLibraries; +LEAK_AT_EXIT> DESIGN_BLOCK_LIBRARY_ADAPTER::GlobalLibraries; std::shared_mutex DESIGN_BLOCK_LIBRARY_ADAPTER::GlobalLibraryMutex; diff --git a/common/design_block_library_adapter.h b/common/design_block_library_adapter.h index c80e01963d..8010b1293c 100644 --- a/common/design_block_library_adapter.h +++ b/common/design_block_library_adapter.h @@ -24,6 +24,7 @@ #include #include +#include #include class DESIGN_BLOCK; @@ -145,8 +146,8 @@ public: protected: - std::map& globalLibs() override { return GlobalLibraries; } - std::map& globalLibs() const override { return GlobalLibraries; } + std::map& globalLibs() override { return GlobalLibraries.Get(); } + std::map& 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 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> GlobalLibraries; static std::shared_mutex GlobalLibraryMutex; }; diff --git a/eeschema/libraries/symbol_library_adapter.cpp b/eeschema/libraries/symbol_library_adapter.cpp index 4477a74a38..b2ac2b000a 100644 --- a/eeschema/libraries/symbol_library_adapter.cpp +++ b/eeschema/libraries/symbol_library_adapter.cpp @@ -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 SYMBOL_LIBRARY_ADAPTER::GlobalLibraries; +LEAK_AT_EXIT> 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 ) ); } diff --git a/eeschema/libraries/symbol_library_adapter.h b/eeschema/libraries/symbol_library_adapter.h index 1434d00d42..40db9b2297 100644 --- a/eeschema/libraries/symbol_library_adapter.h +++ b/eeschema/libraries/symbol_library_adapter.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -154,8 +155,8 @@ public: int GetModifyHash() const; protected: - std::map& globalLibs() override { return GlobalLibraries; } - std::map& globalLibs() const override { return GlobalLibraries; } + std::map& globalLibs() override { return GlobalLibraries.Get(); } + std::map& 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 GlobalLibraries; + static LEAK_AT_EXIT> GlobalLibraries; static std::shared_mutex GlobalLibraryMutex; }; diff --git a/include/core/leak_at_exit.h b/include/core/leak_at_exit.h new file mode 100644 index 0000000000..48909292d9 --- /dev/null +++ b/include/core/leak_at_exit.h @@ -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 . + */ + +#ifndef KICAD_LEAK_AT_EXIT_H +#define KICAD_LEAK_AT_EXIT_H + +#include + +/** + * @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 +#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 GlobalData; + * + * // Use: + * static LEAK_AT_EXIT> 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 +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 diff --git a/pcbnew/footprint_library_adapter.cpp b/pcbnew/footprint_library_adapter.cpp index a77b4137d5..116c9993bf 100644 --- a/pcbnew/footprint_library_adapter.cpp +++ b/pcbnew/footprint_library_adapter.cpp @@ -34,11 +34,11 @@ using namespace std::chrono_literals; -std::map FOOTPRINT_LIBRARY_ADAPTER::GlobalLibraries; +LEAK_AT_EXIT> FOOTPRINT_LIBRARY_ADAPTER::GlobalLibraries; std::shared_mutex FOOTPRINT_LIBRARY_ADAPTER::GlobalLibraryMutex; -std::map>> FOOTPRINT_LIBRARY_ADAPTER::PreloadedFootprints; +LEAK_AT_EXIT>>> 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_LIBRARY_ADAPTER::GetFootprints( const wxString std::vector 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 ) ); } diff --git a/pcbnew/footprint_library_adapter.h b/pcbnew/footprint_library_adapter.h index b4d9ab6dcd..6144a98f44 100644 --- a/pcbnew/footprint_library_adapter.h +++ b/pcbnew/footprint_library_adapter.h @@ -23,6 +23,7 @@ #define FOOTPRINT_LIBRARY_ADAPTER_H #include +#include #include #include #include @@ -162,8 +163,8 @@ public: bool IsFootprintLibWritable( const wxString& aNickname ); protected: - std::map& globalLibs() override { return GlobalLibraries; } - std::map& globalLibs() const override { return GlobalLibraries; } + std::map& globalLibs() override { return GlobalLibraries.Get(); } + std::map& 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 GlobalLibraries; + static LEAK_AT_EXIT> 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>> PreloadedFootprints; + static LEAK_AT_EXIT>>> PreloadedFootprints; static std::shared_mutex PreloadedFootprintsMutex; }; diff --git a/tools/valgrind.supp b/tools/valgrind.supp new file mode 100644 index 0000000000..7abc359b94 --- /dev/null +++ b/tools/valgrind.supp @@ -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* +}