|
| 1 | +// Copyright 2010-2021, Google Inc. |
| 2 | +// All rights reserved. |
| 3 | +// |
| 4 | +// Redistribution and use in source and binary forms, with or without |
| 5 | +// modification, are permitted provided that the following conditions are |
| 6 | +// met: |
| 7 | +// |
| 8 | +// * Redistributions of source code must retain the above copyright |
| 9 | +// notice, this list of conditions and the following disclaimer. |
| 10 | +// * Redistributions in binary form must reproduce the above |
| 11 | +// copyright notice, this list of conditions and the following disclaimer |
| 12 | +// in the documentation and/or other materials provided with the |
| 13 | +// distribution. |
| 14 | +// * Neither the name of Google Inc. nor the names of its |
| 15 | +// contributors may be used to endorse or promote products derived from |
| 16 | +// this software without specific prior written permission. |
| 17 | +// |
| 18 | +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 19 | +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 20 | +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 21 | +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 22 | +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 23 | +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 24 | +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 25 | +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 26 | +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 | +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 28 | +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 | + |
| 30 | +#ifndef MOZC_BASE_STRINGS_CONST_INIT_IMMUTABLE_STRING_H_ |
| 31 | +#define MOZC_BASE_STRINGS_CONST_INIT_IMMUTABLE_STRING_H_ |
| 32 | + |
| 33 | +#include <array> |
| 34 | +#include <atomic> |
| 35 | +#include <cstddef> |
| 36 | +#include <memory> |
| 37 | +#include <string> |
| 38 | +#include <string_view> |
| 39 | +#include <type_traits> |
| 40 | + |
| 41 | +#if defined(MOZC_NO_ATOMIC_FLAG_WAIT) |
| 42 | +#include "absl/base/const_init.h" |
| 43 | +#include "absl/synchronization/mutex.h" |
| 44 | +#endif // defined(MOZC_NO_ATOMIC_FLAG_WAIT) |
| 45 | + |
| 46 | +#include "base/strings/internal/const_init_string_helpers.h" |
| 47 | + |
| 48 | +namespace mozc { |
| 49 | + |
| 50 | +// A utility class to deal with constant global strings whose value is known |
| 51 | +// only at runtime. It has the following capabilities: |
| 52 | +// |
| 53 | +// 1. It allows the library users to lazily initialize the string by calling |
| 54 | +// GetOrInit() only after it becomes ready, e.g. only after dependent |
| 55 | +// modules are fully loaded. |
| 56 | +// 2. It is thread-safe, meaning that multiple threads can call GetOrInit() |
| 57 | +// concurrently without causing data race, with an assumption that the |
| 58 | +// idempotent_initializer is thread-safe and *idempotent* (i.e., it always |
| 59 | +// returns the same value when called multiple times). |
| 60 | +// 3. It guarantees that the string is null-terminated. |
| 61 | +// 4. It accepts the constinit keyword. |
| 62 | +// |
| 63 | +// Synchronization model: |
| 64 | +// --------------------- |
| 65 | +// The initializer is invoked without any class-held state. This avoids lock |
| 66 | +// order inversion when the initializer triggers foreign code that acquires |
| 67 | +// its own locks -- e.g. a LoadLibrary() call from within DllMain on Windows, |
| 68 | +// which Microsoft documents as a classic deadlock pattern: |
| 69 | +// |
| 70 | +// https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices#deadlocks-caused-by-lock-order-inversion |
| 71 | +// |
| 72 | +// Racing threads may each invoke the initializer; per the IdempotentInitializer |
| 73 | +// contract that is acceptable. |
| 74 | +// |
| 75 | +// Once the string is published it cannot be replaced. See |
| 76 | +// ConstInitMutableString for a variant that additionally supports thread-safe |
| 77 | +// Set(). |
| 78 | +// |
| 79 | +// Cavets on destruction: |
| 80 | +// --------------------- |
| 81 | +// * Without MOZC_NO_ATOMIC_FLAG_WAIT, this class is guaranteed to be |
| 82 | +// trivially destructible and can be safely used in the global scope without |
| 83 | +// worrying about destruction order at exit. |
| 84 | +// * With MOZC_NO_ATOMIC_FLAG_WAIT (e.g. server-side configurations where |
| 85 | +// std::atomic_flag::wait is not allowed), it falls back to absl::Mutex, |
| 86 | +// which offers better integration with TSAN. In this case it is no longer |
| 87 | +// trivially destructible, which means atexit-style destructions will happen |
| 88 | +// for globally instantiated objects. As explained in |
| 89 | +// absl/base/const_init.h, strictly speaking this can fall into undefined |
| 90 | +// behavior and we are just relying on known toolchain-specific behaviors |
| 91 | +// that are not guaranteed by the standard. |
| 92 | +// |
| 93 | +// Storage model: |
| 94 | +// ------------- |
| 95 | +// * Values that fit in fixed_array_size characters (including the terminating |
| 96 | +// NUL) live in an inline array. |
| 97 | +// * Larger values live in a heap buffer. When Set() replaces a heap-backed |
| 98 | +// value, the prior heap allocation is freed before Set() returns. |
| 99 | +// * The currently-installed heap buffer (if any) is intentionally leaked at |
| 100 | +// process exit. |
| 101 | +#if !defined(MOZC_NO_ATOMIC_FLAG_WAIT) |
| 102 | + |
| 103 | +// Trivially-destructible variant using `std::atomic_flag::wait`. |
| 104 | +template <size_t fixed_array_size, |
| 105 | + const_init_string_internal::SupportedChar CharT = char> |
| 106 | +class ConstInitImmutableString { |
| 107 | + public: |
| 108 | + using StringT = std::basic_string<CharT>; |
| 109 | + using StringViewT = std::basic_string_view<CharT>; |
| 110 | + |
| 111 | + // A data initializer that is guaranteed to return the same value no matter |
| 112 | + // how many times it is called. It must also be reentrant and safe to call |
| 113 | + // from multiple threads concurrently. |
| 114 | + using IdempotentInitializer = std::add_pointer_t<StringT()>; |
| 115 | + |
| 116 | + ConstInitImmutableString() = delete; |
| 117 | + ~ConstInitImmutableString() = default; |
| 118 | + |
| 119 | + consteval explicit ConstInitImmutableString(IdempotentInitializer init) |
| 120 | + : idempotent_initializer_(init) {} |
| 121 | + |
| 122 | + [[nodiscard]] StringViewT GetOrInit() { |
| 123 | + // Fast path: latch observed. Synchronization is on init_done_; the acquire |
| 124 | + // on test() already publishes all writes prior to the publisher's |
| 125 | + // release on init_done_.test_and_set(), so result_ptr_ can can be loaded |
| 126 | + // with relaxed ordering. |
| 127 | + if (init_done_.test(std::memory_order::acquire)) [[likely]] { |
| 128 | + return StringViewT(result_ptr_.load(std::memory_order::relaxed), |
| 129 | + result_size_); |
| 130 | + } |
| 131 | + // Invoke the initializer and stage any heap fallback *outside* of |
| 132 | + // init_started_. Holding init_started_ across foreign code would introduce |
| 133 | + // the Loader-Lock-style lock-order-inversion deadlock described above; |
| 134 | + // staging outside also lets a throwing initializer on one thread leave the |
| 135 | + // instance recoverable for other threads. |
| 136 | + const StringT value = idempotent_initializer_(); |
| 137 | + std::unique_ptr<CharT[]> heap_fallback = |
| 138 | + const_init_string_internal::StageHeapFallback<CharT>(value, |
| 139 | + std::size(value_)); |
| 140 | + if (!init_started_.test_and_set(std::memory_order::acquire)) { |
| 141 | + // Won the publish race. Commit the staged value. |
| 142 | + CharT* dest = const_init_string_internal::CommitStagedValue<CharT>( |
| 143 | + value, heap_fallback, value_.data()); |
| 144 | + result_size_ = value.size(); |
| 145 | + result_ptr_.store(dest, std::memory_order::relaxed); |
| 146 | + init_done_.test_and_set(std::memory_order::release); |
| 147 | + init_done_.notify_all(); |
| 148 | + return StringViewT(dest, value.size()); |
| 149 | + } |
| 150 | + // Lost the publish race. Block at the OS level until the winner publishes. |
| 151 | + init_done_.wait(false, std::memory_order::acquire); |
| 152 | + return StringViewT(result_ptr_.load(std::memory_order::relaxed), |
| 153 | + result_size_); |
| 154 | + } |
| 155 | + |
| 156 | + private: |
| 157 | + // Hot fields: all three are touched on the fast path. Keep them together at |
| 158 | + // the front so they share a cache line regardless of `fixed_array_size`. |
| 159 | + std::atomic_flag init_done_ = {}; |
| 160 | + std::atomic<const CharT*> result_ptr_ = nullptr; |
| 161 | + size_t result_size_ = 0; |
| 162 | + // Cold fields: only the slow init path touches these. |
| 163 | + const IdempotentInitializer idempotent_initializer_; |
| 164 | + std::atomic_flag init_started_ = {}; |
| 165 | + std::array<CharT, fixed_array_size> value_ = {}; |
| 166 | +}; |
| 167 | + |
| 168 | +static_assert( |
| 169 | + std::is_trivially_destructible_v<ConstInitImmutableString<1, char>>); |
| 170 | +static_assert( |
| 171 | + std::is_trivially_destructible_v<ConstInitImmutableString<1, wchar_t>>); |
| 172 | + |
| 173 | +#else // !defined(MOZC_NO_ATOMIC_FLAG_WAIT) |
| 174 | + |
| 175 | +// absl::Mutex-based variant, which offers better integration with TSAN by |
| 176 | +// giving up trivial destructibility and relying on toolchain-specific behaviors |
| 177 | +// that are not guaranteed by the standard. |
| 178 | +template <size_t fixed_array_size, |
| 179 | + const_init_string_internal::SupportedChar CharT = char> |
| 180 | +class ConstInitImmutableString { |
| 181 | + public: |
| 182 | + using StringT = std::basic_string<CharT>; |
| 183 | + using StringViewT = std::basic_string_view<CharT>; |
| 184 | + |
| 185 | + // A data initializer that is guaranteed to return the same value no matter |
| 186 | + // how many times it is called. It must also be reentrant and safe to call |
| 187 | + // from multiple threads concurrently. |
| 188 | + using IdempotentInitializer = std::add_pointer_t<StringT()>; |
| 189 | + |
| 190 | + ConstInitImmutableString() = delete; |
| 191 | + ~ConstInitImmutableString() = default; |
| 192 | + |
| 193 | + consteval explicit ConstInitImmutableString(IdempotentInitializer init) |
| 194 | + : idempotent_initializer_(init) {} |
| 195 | + |
| 196 | + [[nodiscard]] StringViewT GetOrInit() { |
| 197 | + // Fast path: publication observed (no mutex_ involvement). |
| 198 | + if (const CharT* p = result_ptr_.load(std::memory_order::acquire)) |
| 199 | + [[likely]] { |
| 200 | + return StringViewT(p, result_size_); |
| 201 | + } |
| 202 | + // Invoke the initializer and stage any heap fallback *outside* of mutex_. |
| 203 | + // See the class comment for the Loader-Lock-style lock-order-inversion |
| 204 | + // hazard this avoids. |
| 205 | + const StringT value = idempotent_initializer_(); |
| 206 | + std::unique_ptr<CharT[]> heap_fallback = |
| 207 | + const_init_string_internal::StageHeapFallback<CharT>(value, |
| 208 | + std::size(value_)); |
| 209 | + absl::MutexLock l(mutex_); |
| 210 | + if (const CharT* p = result_ptr_.load(std::memory_order::relaxed)) { |
| 211 | + return StringViewT(p, result_size_); |
| 212 | + } |
| 213 | + CharT* dest = const_init_string_internal::CommitStagedValue<CharT>( |
| 214 | + value, heap_fallback, value_.data()); |
| 215 | + result_size_ = value.size(); |
| 216 | + result_ptr_.store(dest, std::memory_order::release); |
| 217 | + return StringViewT(dest, value.size()); |
| 218 | + } |
| 219 | + |
| 220 | + private: |
| 221 | + // Hot fields: all two are touched on the fast path. Keep them together at |
| 222 | + // the front so they share a cache line regardless of `value_`. |
| 223 | + std::atomic<const CharT*> result_ptr_ = nullptr; |
| 224 | + size_t result_size_ = 0; |
| 225 | + // Cold fields: only the slow init path touches these. |
| 226 | + const IdempotentInitializer idempotent_initializer_; |
| 227 | + absl::Mutex mutex_{absl::kConstInit}; |
| 228 | + std::array<CharT, fixed_array_size> value_ = {}; |
| 229 | +}; |
| 230 | + |
| 231 | +#endif // !defined(MOZC_NO_ATOMIC_FLAG_WAIT) |
| 232 | + |
| 233 | +} // namespace mozc |
| 234 | + |
| 235 | +#endif // MOZC_BASE_STRINGS_CONST_INIT_IMMUTABLE_STRING_H_ |
0 commit comments