Make //base:singleton thread-safe.

# `Singleton<T>`
* Access to both `once_` and `instance_` must be synchronized.
  * Note that `absl::once_flag` itself doesn't need any sync, as it is a sync primitive itself.
  * But we wrap it in a `std::optional`.
* The `instance_` could be put behind an atomic, but we need a mutex for `once_`.
* Once we have a mutex, we actually don't need `once_` because `instance_ == nullptr` inside critical section is the exact condition to perform initialization.
* Finalizer array must be put behind a mutex too.
* Logging utilities no longer depends on `//base:singleton`. Let's just use `LOG(FATAL)` on error.

# `SingletonMockable<T>`
* Access to `mock_` must be synchronized.
* We can simply use an atomic here.
* Use `absl::NoDestructor` for storing the real impl.

PiperOrigin-RevId: 728982963
This commit is contained in:
Tomoki Nakagawa
2025-02-20 08:05:01 +00:00
committed by Hiroyuki Komatsu
parent c523a3b998
commit 6f0fbad9ac
5 changed files with 80 additions and 88 deletions

View File

@ -191,7 +191,12 @@ mozc_cc_library(
srcs = ["singleton.cc"], srcs = ["singleton.cc"],
hdrs = ["singleton.h"], hdrs = ["singleton.h"],
visibility = ["//:__subpackages__"], visibility = ["//:__subpackages__"],
deps = ["@com_google_absl//absl/base"], deps = [
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/base:no_destructor",
"@com_google_absl//absl/log",
"@com_google_absl//absl/synchronization",
],
) )
mozc_cc_test( mozc_cc_test(

View File

@ -29,59 +29,41 @@
#include "base/singleton.h" #include "base/singleton.h"
#ifdef _WIN32 #include <array>
#include <windows.h>
#else // _WIN32 #include "absl/base/attributes.h"
#include <cstdlib> #include "absl/base/const_init.h"
#endif // _WIN32 #include "absl/base/thread_annotations.h"
#include "absl/log/log.h"
#include "absl/synchronization/mutex.h"
namespace mozc { namespace mozc {
namespace internal {
namespace { namespace {
constexpr size_t kMaxFinalizersSize = 256; ABSL_CONST_INIT absl::Mutex mu(absl::kConstInit);
size_t g_finalizers_size = 0; ABSL_CONST_INIT std::array<void (*)(void), 256> finalizers
ABSL_GUARDED_BY(mu) = {};
SingletonFinalizer::FinalizerFunc g_finalizers[kMaxFinalizersSize]; ABSL_CONST_INIT int size ABSL_GUARDED_BY(mu) = 0;
// We can't use CHECK logic for Singleton because CHECK (and LOG)
// obtains the singleton LogStream by calling Singleton::get(). If
// something goes wrong during Singleton::get of the LogStream, it
// recursively calls Singleton::get again to report errors, which
// leads an inifinite wait loop because the first Singleton::get locks
// everything.
// ExitWithError() exits the program without reporting errors, which
// is not good but better than an inifinite loop.
void ExitWithError() {
// This logic is copied from logging.h
#ifdef _WIN32
::RaiseException(::GetLastError(), EXCEPTION_NONCONTINUABLE, 0, nullptr);
#else // _WIN32
exit(-1);
#endif // _WIN32
}
} // namespace } // namespace
void SingletonFinalizer::AddFinalizer(FinalizerFunc func) { void AddSingletonFinalizer(void (*finalizer)()) ABSL_LOCKS_EXCLUDED(mu) {
// When g_finalizers_size is equal to kMaxFinalizersSize, absl::MutexLock lock(&mu);
// SingletonFinalizer::Finalize is called already. if (size >= finalizers.size()) {
if (g_finalizers_size >= kMaxFinalizersSize) { LOG(FATAL) << "Too many singletons";
ExitWithError();
} }
// This part is not thread safe. finalizers[size++] = finalizer;
// When two different classes are instantiated at the same time,
// this code will raise an exception.
g_finalizers[g_finalizers_size++] = func;
} }
void SingletonFinalizer::Finalize() { } // namespace internal
// This part is not thread safe.
// When two different classes are instantiated at the same time, void FinalizeSingletons() ABSL_LOCKS_EXCLUDED(internal::mu) {
// this code will raise an exception. absl::MutexLock lock(&internal::mu);
for (int i = static_cast<int>(g_finalizers_size) - 1; i >= 0; --i) { for (auto func : internal::finalizers) {
(*g_finalizers[i])(); func();
} }
g_finalizers_size = 0; internal::size = 0;
} }
} // namespace mozc } // namespace mozc

View File

@ -30,29 +30,32 @@
#ifndef MOZC_BASE_SINGLETON_H_ #ifndef MOZC_BASE_SINGLETON_H_
#define MOZC_BASE_SINGLETON_H_ #define MOZC_BASE_SINGLETON_H_
#include <optional> #include <atomic>
#include <utility>
#include "absl/base/call_once.h" #include "absl/base/attributes.h"
#include "absl/base/const_init.h"
#include "absl/base/no_destructor.h"
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
namespace mozc { namespace mozc {
namespace internal {
class SingletonFinalizer { // Do not call this method directly. Use `Singleton<T>` instead.
public: void AddSingletonFinalizer(void (*finalizer)());
typedef void (*FinalizerFunc)();
// Do not call this method directly. } // namespace internal
// use Singleton<Typename> instead.
static void AddFinalizer(FinalizerFunc func);
// Call Finalize() if you want to finalize // Destructs all singletons created by `Singleton<T>`. The primary usage is to
// all instances created by Sigleton. // call this right before unloading the Mozc UI for Windows DLL to avoid memory
// // leaks.
// Mozc UI for Windows (DLL) can call //
// SigletonFinalizer::Finalize() // Generally speaking, you SHOULD try to avoid singletons by injecting
// at an appropriate timing. // dependencies instead.
static void Finalize(); //
}; // NOTE: This is a dangerous operation that can cause use-after-free when
// misused.
void FinalizeSingletons();
// Thread-safe Singleton class. // Thread-safe Singleton class.
// Usage: // Usage:
@ -63,34 +66,36 @@ class SingletonFinalizer {
template <typename T> template <typename T>
class Singleton { class Singleton {
public: public:
static T *get() { static T *get() ABSL_LOCKS_EXCLUDED(mutex_) {
absl::call_once(*once_, &Singleton<T>::Init); {
absl::ReaderMutexLock lock(&mutex_); // NOLINT: In the program's steady
// state there's no write lock.
if (instance_ != nullptr) {
return instance_;
}
}
absl::MutexLock lock(&mutex_);
if (instance_ == nullptr) {
instance_ = new T();
internal::AddSingletonFinalizer(&Singleton<T>::Delete);
}
return instance_; return instance_;
} }
// TEST ONLY! Do not call this method in production code. // TEST ONLY! Do not call this method in production code.
static void Delete() { static void Delete() ABSL_LOCKS_EXCLUDED(mutex_) {
absl::MutexLock lock(&mutex_);
delete instance_; delete instance_;
instance_ = nullptr; instance_ = nullptr;
once_.emplace(); // Reconstruct absl::once_flag in place.
} }
private: private:
static void Init() { ABSL_CONST_INIT static inline absl::Mutex mutex_ =
SingletonFinalizer::AddFinalizer(&Singleton<T>::Delete); absl::Mutex(absl::kConstInit);
instance_ = new T; ABSL_CONST_INIT static inline T *instance_ ABSL_GUARDED_BY(mutex_) = nullptr;
}
static std::optional<absl::once_flag> once_;
static T *instance_;
}; };
template <typename T>
std::optional<absl::once_flag> Singleton<T>::once_(std::in_place);
template <typename T>
T *Singleton<T>::instance_ = nullptr;
// SingletonMockable class. // SingletonMockable class.
// Usage: (quote from clock.cc) // Usage: (quote from clock.cc)
// //
@ -107,21 +112,21 @@ template <class Interface, class Impl>
class SingletonMockable { class SingletonMockable {
public: public:
static Interface *Get() { static Interface *Get() {
if (mock_) { if (Interface *mock = mock_.load(std::memory_order_acquire)) {
return mock_; return mock;
} }
static Impl *impl = new Impl(); static absl::NoDestructor<Impl> impl;
return impl; return impl.get();
}
static void SetMock(Interface *mock) {
mock_.store(mock, std::memory_order_release);
} }
static void SetMock(Interface *mock) { mock_ = mock; }
private: private:
static Interface *mock_; ABSL_CONST_INIT static inline std::atomic<Interface *> mock_ = nullptr;
}; };
template <class Interface, class Impl>
Interface *SingletonMockable<Interface, Impl>::mock_ = nullptr;
} // namespace mozc } // namespace mozc
#endif // MOZC_BASE_SINGLETON_H_ #endif // MOZC_BASE_SINGLETON_H_

View File

@ -134,7 +134,7 @@ int MozcServer::Run() {
} }
int MozcServer::Finalize() { int MozcServer::Finalize() {
mozc::SingletonFinalizer::Finalize(); mozc::FinalizeSingletons();
return 0; return 0;
} }

View File

@ -66,7 +66,7 @@ void TipDllModule::PrepareForShutdown() {
// Windows 8. Thus we must not shut down libraries that cannot be designed to // Windows 8. Thus we must not shut down libraries that cannot be designed to
// be re-initializable. For instance, we must not call following functions // be re-initializable. For instance, we must not call following functions
// here. // here.
// - SingletonFinalizer::Finalize() - b/10233768 // - mozc::FinalizeSingletons() - b/10233768
// - mozc::protobuf::ShutdownProtobufLibrary() - b/2126375 // - mozc::protobuf::ShutdownProtobufLibrary() - b/2126375
absl::call_once(uninitialize_once_, &TipShutdownCrashReportHandler); absl::call_once(uninitialize_once_, &TipShutdownCrashReportHandler);
} }