Skip to content

Commit 98619a0

Browse files
committed
Add constinit global string holder classes
These are utility classes for global strings whose value is known only at runtime. They are motivated by the on-going effort to remove Singleton<T>, which has surfaced several places in system_util.cc (directory caches, user-SID lookup, program-invocation-name holder) where the singleton wraps a string lookup that must run *after* the surrounding modules are ready. A function-local "static std::string" would be the obvious replacement, but reintroduces two issues that Singleton<T> callers have learnt to avoid: * On Windows, function-local static initialization is not safe to invoke from DllMain because the compiler-generated guard can take locks that interact badly with the Loader Lock. * The resulting destructor runs at process exit in unspecified order relative to other globals. Both classes address these by being trivially destructible (values that don't fit inline live in an intentionally-leaked heap buffer) and by keeping all foreign code outside the publish lock. ConstInitImmutableString is the publish-once, read-many variant. It takes an IdempotentInitializer function pointer that is invoked lock-free on the first GetOrInit(), so the class is safe to use from DllMain even when the initializer itself acquires the Loader Lock. Subsequent reads return a string_view over stable, NUL-terminated storage on a lock-free fast path: #include "base/strings/const_init_immutable_string.h" constinit mozc::ConstInitImmutableString<256> kProgramFilesX86( []() -> std::string { return ComputeProgramFilesX86Path(); }); absl::string_view path = kProgramFilesX86.GetOrInit(); ConstInitMutableString is the set-many, snapshot-read variant. Set atomically replaces the stored value; Get returns a std::string snapshot under a spinlock-protected copy. Empty doubles as the "never set" sentinel; callers that want a lazy default observe empty and Set it themselves: #include "base/strings/const_init_mutable_string.h" constinit mozc::ConstInitMutableString<256> g_invocation_name; void Init(absl::string_view name) { g_invocation_name.Set(name); } std::string GetName() { return g_invocation_name.Get(); } Implementation notes: * Only the publish step is serialized, by a small atomic_flag spinlock with PAUSE / YIELD hints. Heap allocation for the fallback buffer is staged before the spinlock is taken, so the critical section is bounded by fixed_array_size and a throwing bad_alloc cannot leave the spinlock latched. * Constructors are consteval, so instances must be constant-initialized. * Common helpers (spin hint, RAII spinlock guard, heap-fallback staging and commit) live in base/strings/internal/const_init_string_helpers.{h,cc}. Templates that operate on CharT data are explicitly instantiated for char and wchar_t so they live in one TU.
1 parent 46058f5 commit 98619a0

8 files changed

Lines changed: 685 additions & 0 deletions

src/base/strings/BUILD.bazel

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,38 @@ mozc_cc_test(
7575
],
7676
)
7777

78+
mozc_cc_library(
79+
name = "const_init_immutable_string",
80+
hdrs = ["const_init_immutable_string.h"],
81+
deps = ["//base/strings/internal:const_init_string_helpers"],
82+
)
83+
84+
mozc_cc_test(
85+
name = "const_init_immutable_string_test",
86+
size = "small",
87+
srcs = ["const_init_immutable_string_test.cc"],
88+
deps = [
89+
":const_init_immutable_string",
90+
"//testing:gunit_main",
91+
],
92+
)
93+
94+
mozc_cc_library(
95+
name = "const_init_mutable_string",
96+
hdrs = ["const_init_mutable_string.h"],
97+
deps = [":const_init_immutable_string"],
98+
)
99+
100+
mozc_cc_test(
101+
name = "const_init_mutable_string_test",
102+
size = "small",
103+
srcs = ["const_init_mutable_string_test.cc"],
104+
deps = [
105+
":const_init_mutable_string",
106+
"//testing:gunit_main",
107+
],
108+
)
109+
78110
mozc_cc_library(
79111
name = "japanese",
80112
srcs = [
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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 <mutex>
38+
#include <string>
39+
#include <string_view>
40+
#include <type_traits>
41+
42+
#include "base/strings/internal/const_init_string_helpers.h"
43+
44+
namespace mozc {
45+
46+
// A utility class to deal with constant global strings whose value is known
47+
// only at runtime. It has the following capabilities:
48+
// 1. It allows the library users to lazily initialize the string by calling
49+
// `GetOrInit()` only after it becomes ready, e.g. only after dependent
50+
// modules are fully loaded.
51+
// 2. It is thread-safe, meaning that multiple threads can call `GetOrInit()`
52+
// concurrently without causing data race, with an assumption that the
53+
// `idempotent_initializer` is idempotent (i.e., it always returns the same
54+
// value when called multiple times) and thread-safe.
55+
// 3. It guarantees that the string is null-terminated.
56+
// 4. It is trivially destructible, which means it can be used in static
57+
// storage duration objects without causing destructor order issues, with a
58+
// caveat that it may leak memory if the string is larger than the fixed
59+
// array size provided by the template parameter.
60+
//
61+
// Once the string is published it cannot be replaced. See
62+
// `ConstInitMutableString` for a variant that additionally supports
63+
// thread-safe `Set()`.
64+
template <size_t fixed_array_size,
65+
const_init_string_internal::SupportedChar CharT = char>
66+
class ConstInitImmutableString {
67+
public:
68+
using StringT = std::basic_string<CharT>;
69+
using StringViewT = std::basic_string_view<CharT>;
70+
71+
// A data initializer that is guaranteed to return the same value no matter
72+
// how many times it is called. It must also be reentrant and safe to call
73+
// from multiple threads concurrently.
74+
using IdempotentInitializer = std::add_pointer_t<StringT()>;
75+
76+
ConstInitImmutableString() = delete;
77+
~ConstInitImmutableString() = default;
78+
79+
consteval explicit ConstInitImmutableString(
80+
IdempotentInitializer idempotent_initializer)
81+
: idempotent_initializer_(idempotent_initializer) {}
82+
83+
[[nodiscard]] StringViewT GetOrInit() {
84+
// Fast path: result already published.
85+
if (const CharT* ptr = result_ptr_.load(std::memory_order::acquire))
86+
[[likely]] {
87+
return StringViewT(ptr, result_size_);
88+
}
89+
90+
// The initializer is called outside any lock so that no foreign code
91+
// runs under a lock held by this class. This avoids classic deadlock
92+
// scenarios such as Win32 Loader Lock recursion when `GetOrInit()` is
93+
// called from `DllMain` and the initializer internally invokes
94+
// `LoadLibrary`. Racing threads may each call it; per the
95+
// `IdempotentInitializer` contract that is acceptable.
96+
// https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices#deadlocks-caused-by-lock-order-inversion
97+
const StringT value = idempotent_initializer_();
98+
99+
// Stage any heap fallback outside the spinlock so that the critical
100+
// section is bounded by `fixed_array_size` and a throwing
101+
// allocation cannot leave the spinlock held.
102+
std::unique_ptr<CharT[]> heap_fallback =
103+
const_init_string_internal::StageHeapFallback<CharT>(value,
104+
std::size(value_));
105+
106+
std::lock_guard<const_init_string_internal::SpinLock> l(committing_);
107+
if (const CharT* ptr = result_ptr_.load(std::memory_order::relaxed)) {
108+
// Another thread already published; staged `heap_fallback` is
109+
// freed by its destructor when this scope unwinds.
110+
return StringViewT(ptr, result_size_);
111+
}
112+
113+
// Winner: any staged heap buffer is intentionally leaked for the
114+
// remainder of the process so that `ConstInitImmutableString` itself
115+
// remains trivially destructible.
116+
CharT* dest = const_init_string_internal::CommitStagedValue<CharT>(
117+
value, heap_fallback, value_.data());
118+
result_size_ = value.size();
119+
result_ptr_.store(dest, std::memory_order::release);
120+
return StringViewT(dest, value.size());
121+
}
122+
123+
private:
124+
std::atomic<const CharT*> result_ptr_ = nullptr;
125+
size_t result_size_ = 0;
126+
const_init_string_internal::SpinLock committing_;
127+
std::array<CharT, fixed_array_size> value_ = {};
128+
const IdempotentInitializer idempotent_initializer_;
129+
};
130+
131+
// Verify the trivial destructibility contract for both supported `CharT`
132+
// instantiations so misuse (e.g. accidentally adding a non-trivial member)
133+
// is caught at compile time even if no instance is constructed.
134+
static_assert(
135+
std::is_trivially_destructible_v<ConstInitImmutableString<1, char>>);
136+
static_assert(
137+
std::is_trivially_destructible_v<ConstInitImmutableString<1, wchar_t>>);
138+
139+
} // namespace mozc
140+
141+
#endif // MOZC_BASE_STRINGS_CONST_INIT_IMMUTABLE_STRING_H_
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
#include "base/strings/const_init_immutable_string.h"
31+
32+
#include <string>
33+
#include <string_view>
34+
35+
#include "testing/gunit.h"
36+
37+
namespace mozc::strings {
38+
namespace {
39+
40+
constinit ConstInitImmutableString<256> g_value_simple_init(
41+
[]() -> std::string { return "Mozc"; });
42+
43+
TEST(ConstInitImmutableStringTest, SimpleInit) {
44+
EXPECT_EQ(g_value_simple_init.GetOrInit(), "Mozc");
45+
EXPECT_EQ(g_value_simple_init.GetOrInit(), "Mozc");
46+
}
47+
48+
TEST(ConstInitImmutableStringTest, NullTermination) {
49+
const auto value = g_value_simple_init.GetOrInit();
50+
EXPECT_EQ(value.data()[value.size()], '\0');
51+
}
52+
53+
constexpr std::string_view kLongValue =
54+
"this string is longer than the inline buffer";
55+
56+
constinit ConstInitImmutableString<8> g_value_overflow([]() -> std::string {
57+
return std::string("this string is longer than the inline buffer");
58+
});
59+
60+
TEST(ConstInitImmutableStringTest, HeapFallback) {
61+
const auto value = g_value_overflow.GetOrInit();
62+
EXPECT_EQ(value, kLongValue);
63+
EXPECT_EQ(value.data()[value.size()], '\0');
64+
// Repeated calls return the same pointer (stable storage).
65+
EXPECT_EQ(g_value_overflow.GetOrInit().data(), value.data());
66+
}
67+
68+
constinit ConstInitImmutableString<256, wchar_t> g_value_wide(
69+
[]() -> std::wstring { return L"WideMozc"; });
70+
71+
TEST(ConstInitImmutableStringTest, WideChar) {
72+
EXPECT_EQ(g_value_wide.GetOrInit(), std::wstring_view(L"WideMozc"));
73+
const auto value = g_value_wide.GetOrInit();
74+
EXPECT_EQ(value.data()[value.size()], L'\0');
75+
}
76+
77+
} // namespace
78+
} // namespace mozc::strings
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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_MUTABLE_STRING_H_
31+
#define MOZC_BASE_STRINGS_CONST_INIT_MUTABLE_STRING_H_
32+
33+
#include <array>
34+
#include <atomic>
35+
#include <cstddef>
36+
#include <memory>
37+
#include <mutex>
38+
#include <string>
39+
#include <string_view>
40+
#include <type_traits>
41+
42+
#include "base/strings/internal/const_init_string_helpers.h"
43+
44+
namespace mozc {
45+
46+
// A variant of `ConstInitImmutableString` that additionally supports
47+
// thread-safe `Set()`. Like `ConstInitImmutableString`,
48+
// `ConstInitMutableString` is trivially destructible and may be used in
49+
// `constinit` globals.
50+
//
51+
// Semantics:
52+
// * `Get()` returns a snapshot of the most recent `Set` value, or an
53+
// empty string if `Set` has never been called. Empty thus doubles as
54+
// the "never set" sentinel; callers that need lazy default behaviour
55+
// should compute the default themselves on observing empty and call
56+
// `Set` to publish it.
57+
// * `Set(value)` atomically replaces the stored value.
58+
//
59+
// Storage:
60+
// * Values that fit in `fixed_array_size` characters (including the
61+
// terminating NUL) live in an inline array.
62+
// * Larger values live in a heap buffer. When `Set` replaces a heap-backed
63+
// value, the prior heap allocation is freed before `Set` returns.
64+
// * The currently-installed heap buffer (if any) is intentionally leaked at
65+
// process exit so that the class itself remains trivially destructible.
66+
template <size_t fixed_array_size,
67+
const_init_string_internal::SupportedChar CharT = char>
68+
class ConstInitMutableString {
69+
public:
70+
using StringT = std::basic_string<CharT>;
71+
using StringViewT = std::basic_string_view<CharT>;
72+
73+
consteval ConstInitMutableString() noexcept = default;
74+
~ConstInitMutableString() = default;
75+
76+
void Set(StringViewT value);
77+
StringT Get();
78+
79+
private:
80+
std::atomic<bool> initialized_ = {};
81+
CharT* current_ptr_ = nullptr;
82+
size_t current_size_ = 0;
83+
const_init_string_internal::SpinLock committing_;
84+
std::array<CharT, fixed_array_size> value_ = {};
85+
};
86+
87+
template <size_t fixed_array_size,
88+
const_init_string_internal::SupportedChar CharT>
89+
void ConstInitMutableString<fixed_array_size, CharT>::Set(StringViewT value) {
90+
// Stage any heap fallback outside the spinlock so the critical section
91+
// is bounded by `fixed_array_size` and a throwing `bad_alloc` cannot
92+
// leave the spinlock held.
93+
std::unique_ptr<CharT[]> heap_fallback =
94+
const_init_string_internal::StageHeapFallback<CharT>(value,
95+
std::size(value_));
96+
97+
std::unique_ptr<CharT[]> old_heap;
98+
{
99+
std::lock_guard<const_init_string_internal::SpinLock> l(committing_);
100+
// If the previously-installed buffer was on the heap, hand it to a
101+
// local `unique_ptr` so it is freed after we drop the lock. Readers
102+
// complete their copy under the same lock, so no one is left
103+
// holding the old pointer.
104+
if (current_ptr_ != nullptr && current_ptr_ != value_.data()) {
105+
old_heap.reset(current_ptr_);
106+
}
107+
current_ptr_ = const_init_string_internal::CommitStagedValue<CharT>(
108+
value, heap_fallback, value_.data());
109+
current_size_ = value.size();
110+
initialized_.store(true, std::memory_order::release);
111+
}
112+
}
113+
114+
template <size_t fixed_array_size,
115+
const_init_string_internal::SupportedChar CharT>
116+
auto ConstInitMutableString<fixed_array_size, CharT>::Get() -> StringT {
117+
// Lock-free fast exit when nothing has ever been set.
118+
if (!initialized_.load(std::memory_order::acquire)) {
119+
return StringT();
120+
}
121+
// Snapshot under the lock since `Set` may concurrently swap pointers.
122+
// The `lock_guard` releases the lock if the `StringT` allocation
123+
// here throws.
124+
std::lock_guard<const_init_string_internal::SpinLock> l(committing_);
125+
return StringT(current_ptr_, current_size_);
126+
}
127+
128+
// Verify the trivial destructibility contract for both supported `CharT`
129+
// instantiations so misuse (e.g. accidentally adding a non-trivial member)
130+
// is caught at compile time even if no instance is constructed.
131+
static_assert(
132+
std::is_trivially_destructible_v<ConstInitMutableString<1, char>>);
133+
static_assert(
134+
std::is_trivially_destructible_v<ConstInitMutableString<1, wchar_t>>);
135+
136+
} // namespace mozc
137+
138+
#endif // MOZC_BASE_STRINGS_CONST_INIT_MUTABLE_STRING_H_

0 commit comments

Comments
 (0)