diff --git a/include/eepp/system/singleton.hpp b/include/eepp/system/singleton.hpp index 663ec457b..f0843916e 100644 --- a/include/eepp/system/singleton.hpp +++ b/include/eepp/system/singleton.hpp @@ -1,98 +1,128 @@ -#ifndef EE_SYSTEMSINGLETON_H -#define EE_SYSTEMSINGLETON_H - -#include -#include -#include - -/** Internally we gonna use the macro singleton because it works with the engine compiled as dynamic - * libraries */ - -#define SINGLETON_DECLARE_HEADERS( T ) \ - public: \ - static T* createSingleton(); \ - \ - static T* existsSingleton(); \ - \ - static bool isShuttingDown(); \ - \ - static T* instance(); \ - \ - static void destroySingleton(); \ - \ - static void detachSingleton(); - -#define SINGLETON_DECLARE_IMPLEMENTATION( T ) \ - \ - static T* ms_singleton = NULL; \ - static bool ms_is_shutting_down = false; \ - static Mutex ms_mutex; \ - \ - T* T::createSingleton() { \ - Lock l( ms_mutex ); \ - if ( NULL == ms_singleton ) \ - ms_singleton = eeNew( T, () ); \ - return ms_singleton; \ - } \ - \ - T* T::existsSingleton() { \ - return ms_singleton; \ - } \ - \ - bool T::isShuttingDown() { \ - return ms_is_shutting_down; \ - } \ - \ - T* T::instance() { \ - return createSingleton(); \ - } \ - \ - void T::destroySingleton() { \ - ms_is_shutting_down = true; \ - Lock l( ms_mutex ); \ - eeSAFE_DELETE( ms_singleton ); \ - ms_is_shutting_down = false; \ - } \ - \ - void T::detachSingleton() { \ - Lock l( ms_mutex ); \ - ms_singleton = nullptr; \ - } - -namespace EE { namespace System { - -/** @brief Template class for only one instance classes. */ -template class Singleton { - protected: - static T* ms_singleton; - static Mutex ms_mutex; - - public: - /** Get the singleton pointer (without instance verification) */ - static T* existsSingleton() { - return ms_singleton; - } - - /** Get the singleton pointer */ - static T* instance() { - Lock l( ms_mutex ); - if ( NULL == ms_singleton ) - ms_singleton = eeNew( T, () ); - return ms_singleton; - } - - /** Destroy the singleton instance */ - static void destroySingleton() { - Lock l( ms_mutex ); - eeSAFE_DELETE( ms_singleton ); - } - - /** Detaches the existing singleton. Instance will keep existing but not associated */ - static void detachSingleton() { - Lock l( ms_mutex ); - ms_singleton = nullptr; - } -}; - -}} // namespace EE::System -#endif +#ifndef EE_SYSTEMSINGLETON_H +#define EE_SYSTEMSINGLETON_H + +#include +#include +#include +#include + +/** Internally we gonna use the macro singleton because it works with the engine compiled as dynamic + * libraries. + * + * Lifecycle contract: + * - Creation is thread-safe. The first caller creates the singleton under ms_mutex, publishes it + * with release semantics, and later callers use an acquire-load fast path without locking. + * - Destruction and detach are teardown operations only. They must not run concurrently with + * instance(), existsSingleton(), or normal use of a previously returned raw pointer. The atomic + * only protects pointer publication; it does not protect pointee lifetime after return. + * - destroySingleton() intentionally keeps the pointer visible while T::~T() runs because several + * eepp teardown paths query singleton state while ms_is_shutting_down is true. + * - Expand SINGLETON_DECLARE_IMPLEMENTATION exactly once in a .cpp file. Expanding it in a header + * creates one internal-linkage singleton per translation unit. + */ + +#define SINGLETON_DECLARE_HEADERS( T ) \ + public: \ + static T* createSingleton(); \ + \ + static T* existsSingleton(); \ + \ + static bool isShuttingDown(); \ + \ + static T* instance(); \ + \ + static void destroySingleton(); \ + \ + static void detachSingleton(); + +#define SINGLETON_DECLARE_IMPLEMENTATION( T ) \ + \ + static std::atomic ms_singleton{ NULL }; \ + static std::atomic ms_is_shutting_down{ false }; \ + static Mutex ms_mutex; \ + \ + T* T::createSingleton() { \ + T* singleton = ms_singleton.load( std::memory_order_acquire ); \ + if ( NULL != singleton ) \ + return singleton; \ + Lock l( ms_mutex ); \ + singleton = ms_singleton.load( std::memory_order_acquire ); \ + if ( NULL == singleton ) { \ + singleton = eeNew( T, () ); \ + ms_singleton.store( singleton, std::memory_order_release ); \ + } \ + return singleton; \ + } \ + \ + T* T::existsSingleton() { \ + return ms_singleton.load( std::memory_order_acquire ); \ + } \ + \ + bool T::isShuttingDown() { \ + return ms_is_shutting_down.load( std::memory_order_acquire ); \ + } \ + \ + T* T::instance() { \ + return createSingleton(); \ + } \ + \ + void T::destroySingleton() { \ + ms_is_shutting_down.store( true, std::memory_order_release ); \ + Lock l( ms_mutex ); \ + T* singleton = ms_singleton.load( std::memory_order_acquire ); \ + eeSAFE_DELETE( singleton ); \ + ms_singleton.store( NULL, std::memory_order_release ); \ + ms_is_shutting_down.store( false, std::memory_order_release ); \ + } \ + \ + void T::detachSingleton() { \ + Lock l( ms_mutex ); \ + ms_singleton.store( nullptr, std::memory_order_release ); \ + } + +namespace EE { namespace System { + +/** @brief Template class for only one instance classes. + * Creation is thread-safe, but destroySingleton() and detachSingleton() have the same teardown-only + * raw-pointer lifetime contract documented above for SINGLETON_DECLARE_IMPLEMENTATION. + */ +template class Singleton { + protected: + static std::atomic ms_singleton; + static Mutex ms_mutex; + + public: + /** Get the singleton pointer (without instance verification) */ + static T* existsSingleton() { return ms_singleton.load( std::memory_order_acquire ); } + + /** Get the singleton pointer */ + static T* instance() { + T* singleton = ms_singleton.load( std::memory_order_acquire ); + if ( NULL != singleton ) + return singleton; + Lock l( ms_mutex ); + singleton = ms_singleton.load( std::memory_order_acquire ); + if ( NULL == singleton ) { + singleton = eeNew( T, () ); + ms_singleton.store( singleton, std::memory_order_release ); + } + return singleton; + } + + /** Destroy the singleton instance */ + static void destroySingleton() { + Lock l( ms_mutex ); + T* singleton = ms_singleton.load( std::memory_order_acquire ); + eeSAFE_DELETE( singleton ); + ms_singleton.store( NULL, std::memory_order_release ); + } + + /** Detaches the existing singleton. Instance will keep existing but not associated */ + static void detachSingleton() { + Lock l( ms_mutex ); + ms_singleton.store( nullptr, std::memory_order_release ); + } +}; + +}} // namespace EE::System +#endif diff --git a/src/eepp/system/log.cpp b/src/eepp/system/log.cpp index 7e08fb8af..55099914d 100644 --- a/src/eepp/system/log.cpp +++ b/src/eepp/system/log.cpp @@ -32,25 +32,37 @@ std::unordered_map Log::getMapFlag() { Log* Log::create( const std::string& logPath, const LogLevel& level, bool stdOutLog, bool liveWrite ) { - if ( NULL == ms_singleton ) { - ms_singleton = eeNew( Log, ( logPath, level, stdOutLog, liveWrite ) ); + Log* singleton = ms_singleton.load( std::memory_order_acquire ); + if ( NULL == singleton ) { + Lock l( ms_mutex ); + singleton = ms_singleton.load( std::memory_order_acquire ); + if ( NULL == singleton ) { + singleton = eeNew( Log, ( logPath, level, stdOutLog, liveWrite ) ); + ms_singleton.store( singleton, std::memory_order_release ); + } } else { - ms_singleton->setLogLevelThreshold( level ); - ms_singleton->setLogToStdOut( stdOutLog ); - ms_singleton->setLiveWrite( liveWrite ); + singleton->setLogLevelThreshold( level ); + singleton->setLogToStdOut( stdOutLog ); + singleton->setLiveWrite( liveWrite ); } - return ms_singleton; + return singleton; } Log* Log::create( const LogLevel& level, bool stdOutLog, bool liveWrite ) { - if ( NULL == ms_singleton ) { - ms_singleton = eeNew( Log, ( "", level, stdOutLog, liveWrite ) ); + Log* singleton = ms_singleton.load( std::memory_order_acquire ); + if ( NULL == singleton ) { + Lock l( ms_mutex ); + singleton = ms_singleton.load( std::memory_order_acquire ); + if ( NULL == singleton ) { + singleton = eeNew( Log, ( "", level, stdOutLog, liveWrite ) ); + ms_singleton.store( singleton, std::memory_order_release ); + } } else { - ms_singleton->setLogLevelThreshold( level ); - ms_singleton->setLogToStdOut( stdOutLog ); - ms_singleton->setLiveWrite( liveWrite ); + singleton->setLogLevelThreshold( level ); + singleton->setLogToStdOut( stdOutLog ); + singleton->setLiveWrite( liveWrite ); } - return ms_singleton; + return singleton; } Log::Log() : mSave( false ), mStdOutEnabled( false ), mLiveWrite( false ), mFS( NULL ) { diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index 9900f8af7..83190c12a 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -163,11 +163,17 @@ SINGLETON_DECLARE_IMPLEMENTATION( SyntaxDefinitionManager ) SyntaxDefinitionManager* SyntaxDefinitionManager::createSingleton( std::size_t reserveSpaceForLanguages ) { - if ( NULL == ms_singleton ) { - ms_singleton = eeNew( SyntaxDefinitionManager, ( reserveSpaceForLanguages ) ); + SyntaxDefinitionManager* singleton = ms_singleton.load( std::memory_order_acquire ); + if ( NULL == singleton ) { + Lock l( ms_mutex ); + singleton = ms_singleton.load( std::memory_order_acquire ); + if ( NULL == singleton ) { + singleton = eeNew( SyntaxDefinitionManager, ( reserveSpaceForLanguages ) ); + ms_singleton.store( singleton, std::memory_order_release ); + } } - return ms_singleton; + return singleton; } static void addPlainText() { @@ -179,8 +185,8 @@ static void addPlainText() { // lite-plugins (https://github.com/rxi/lite-plugins) supported languages. SyntaxDefinitionManager::SyntaxDefinitionManager( std::size_t reserveSpaceForLanguages ) { - if ( ms_singleton == nullptr ) - ms_singleton = this; + if ( ms_singleton.load( std::memory_order_acquire ) == nullptr ) + ms_singleton.store( this, std::memory_order_release ); { Lock l( mMutex );