mirror of
https://github.com/mii443/mozc.git
synced 2025-08-22 16:15:46 +00:00
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:
committed by
Hiroyuki Komatsu
parent
c523a3b998
commit
6f0fbad9ac
@ -191,7 +191,12 @@ mozc_cc_library(
|
||||
srcs = ["singleton.cc"],
|
||||
hdrs = ["singleton.h"],
|
||||
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(
|
||||
|
@ -29,59 +29,41 @@
|
||||
|
||||
#include "base/singleton.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else // _WIN32
|
||||
#include <cstdlib>
|
||||
#endif // _WIN32
|
||||
#include <array>
|
||||
|
||||
#include "absl/base/attributes.h"
|
||||
#include "absl/base/const_init.h"
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/log/log.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
|
||||
namespace mozc {
|
||||
namespace internal {
|
||||
namespace {
|
||||
|
||||
constexpr size_t kMaxFinalizersSize = 256;
|
||||
size_t g_finalizers_size = 0;
|
||||
|
||||
SingletonFinalizer::FinalizerFunc g_finalizers[kMaxFinalizersSize];
|
||||
|
||||
// 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
|
||||
}
|
||||
ABSL_CONST_INIT absl::Mutex mu(absl::kConstInit);
|
||||
ABSL_CONST_INIT std::array<void (*)(void), 256> finalizers
|
||||
ABSL_GUARDED_BY(mu) = {};
|
||||
ABSL_CONST_INIT int size ABSL_GUARDED_BY(mu) = 0;
|
||||
|
||||
} // namespace
|
||||
|
||||
void SingletonFinalizer::AddFinalizer(FinalizerFunc func) {
|
||||
// When g_finalizers_size is equal to kMaxFinalizersSize,
|
||||
// SingletonFinalizer::Finalize is called already.
|
||||
if (g_finalizers_size >= kMaxFinalizersSize) {
|
||||
ExitWithError();
|
||||
void AddSingletonFinalizer(void (*finalizer)()) ABSL_LOCKS_EXCLUDED(mu) {
|
||||
absl::MutexLock lock(&mu);
|
||||
if (size >= finalizers.size()) {
|
||||
LOG(FATAL) << "Too many singletons";
|
||||
}
|
||||
// This part is not thread safe.
|
||||
// When two different classes are instantiated at the same time,
|
||||
// this code will raise an exception.
|
||||
g_finalizers[g_finalizers_size++] = func;
|
||||
finalizers[size++] = finalizer;
|
||||
}
|
||||
|
||||
void SingletonFinalizer::Finalize() {
|
||||
// This part is not thread safe.
|
||||
// When two different classes are instantiated at the same time,
|
||||
// this code will raise an exception.
|
||||
for (int i = static_cast<int>(g_finalizers_size) - 1; i >= 0; --i) {
|
||||
(*g_finalizers[i])();
|
||||
} // namespace internal
|
||||
|
||||
void FinalizeSingletons() ABSL_LOCKS_EXCLUDED(internal::mu) {
|
||||
absl::MutexLock lock(&internal::mu);
|
||||
for (auto func : internal::finalizers) {
|
||||
func();
|
||||
}
|
||||
g_finalizers_size = 0;
|
||||
internal::size = 0;
|
||||
}
|
||||
|
||||
} // namespace mozc
|
||||
|
@ -30,29 +30,32 @@
|
||||
#ifndef MOZC_BASE_SINGLETON_H_
|
||||
#define MOZC_BASE_SINGLETON_H_
|
||||
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <atomic>
|
||||
|
||||
#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 internal {
|
||||
|
||||
class SingletonFinalizer {
|
||||
public:
|
||||
typedef void (*FinalizerFunc)();
|
||||
// Do not call this method directly. Use `Singleton<T>` instead.
|
||||
void AddSingletonFinalizer(void (*finalizer)());
|
||||
|
||||
// Do not call this method directly.
|
||||
// use Singleton<Typename> instead.
|
||||
static void AddFinalizer(FinalizerFunc func);
|
||||
} // namespace internal
|
||||
|
||||
// Call Finalize() if you want to finalize
|
||||
// all instances created by Sigleton.
|
||||
// Destructs all singletons created by `Singleton<T>`. The primary usage is to
|
||||
// call this right before unloading the Mozc UI for Windows DLL to avoid memory
|
||||
// leaks.
|
||||
//
|
||||
// Mozc UI for Windows (DLL) can call
|
||||
// SigletonFinalizer::Finalize()
|
||||
// at an appropriate timing.
|
||||
static void Finalize();
|
||||
};
|
||||
// Generally speaking, you SHOULD try to avoid singletons by injecting
|
||||
// dependencies instead.
|
||||
//
|
||||
// NOTE: This is a dangerous operation that can cause use-after-free when
|
||||
// misused.
|
||||
void FinalizeSingletons();
|
||||
|
||||
// Thread-safe Singleton class.
|
||||
// Usage:
|
||||
@ -63,34 +66,36 @@ class SingletonFinalizer {
|
||||
template <typename T>
|
||||
class Singleton {
|
||||
public:
|
||||
static T *get() {
|
||||
absl::call_once(*once_, &Singleton<T>::Init);
|
||||
static T *get() ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
{
|
||||
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_;
|
||||
}
|
||||
|
||||
// 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_;
|
||||
instance_ = nullptr;
|
||||
once_.emplace(); // Reconstruct absl::once_flag in place.
|
||||
}
|
||||
|
||||
private:
|
||||
static void Init() {
|
||||
SingletonFinalizer::AddFinalizer(&Singleton<T>::Delete);
|
||||
instance_ = new T;
|
||||
}
|
||||
|
||||
static std::optional<absl::once_flag> once_;
|
||||
static T *instance_;
|
||||
ABSL_CONST_INIT static inline absl::Mutex mutex_ =
|
||||
absl::Mutex(absl::kConstInit);
|
||||
ABSL_CONST_INIT static inline T *instance_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::optional<absl::once_flag> Singleton<T>::once_(std::in_place);
|
||||
|
||||
template <typename T>
|
||||
T *Singleton<T>::instance_ = nullptr;
|
||||
|
||||
// SingletonMockable class.
|
||||
// Usage: (quote from clock.cc)
|
||||
//
|
||||
@ -107,21 +112,21 @@ template <class Interface, class Impl>
|
||||
class SingletonMockable {
|
||||
public:
|
||||
static Interface *Get() {
|
||||
if (mock_) {
|
||||
return mock_;
|
||||
if (Interface *mock = mock_.load(std::memory_order_acquire)) {
|
||||
return mock;
|
||||
}
|
||||
static Impl *impl = new Impl();
|
||||
return impl;
|
||||
static absl::NoDestructor<Impl> impl;
|
||||
return impl.get();
|
||||
}
|
||||
|
||||
static void SetMock(Interface *mock) {
|
||||
mock_.store(mock, std::memory_order_release);
|
||||
}
|
||||
static void SetMock(Interface *mock) { mock_ = mock; }
|
||||
|
||||
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
|
||||
|
||||
#endif // MOZC_BASE_SINGLETON_H_
|
||||
|
@ -134,7 +134,7 @@ int MozcServer::Run() {
|
||||
}
|
||||
|
||||
int MozcServer::Finalize() {
|
||||
mozc::SingletonFinalizer::Finalize();
|
||||
mozc::FinalizeSingletons();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ void TipDllModule::PrepareForShutdown() {
|
||||
// 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
|
||||
// here.
|
||||
// - SingletonFinalizer::Finalize() - b/10233768
|
||||
// - mozc::FinalizeSingletons() - b/10233768
|
||||
// - mozc::protobuf::ShutdownProtobufLibrary() - b/2126375
|
||||
absl::call_once(uninitialize_once_, &TipShutdownCrashReportHandler);
|
||||
}
|
||||
|
Reference in New Issue
Block a user