Skip to content

Introduce ConstInitImmutableString and ConstInitMutableString#1493

Open
yukawa wants to merge 1 commit into
google:masterfrom
ciceroaware:constinit_string
Open

Introduce ConstInitImmutableString and ConstInitMutableString#1493
yukawa wants to merge 1 commit into
google:masterfrom
ciceroaware:constinit_string

Conversation

@yukawa
Copy link
Copy Markdown
Collaborator

@yukawa yukawa commented Apr 28, 2026

Description

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.

Issue IDs

N/A

Steps to test new behaviors (if any)

  • OS: All
  • Steps:
    1. bazelisk test //base/strings/... --build_tests_only

Additional context

Add any other context about the pull request here.

Copy link
Copy Markdown
Collaborator

@hiroyuki-komatsu hiroyuki-komatsu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR!

Comment thread src/base/strings/internal/const_init_string_helpers.cc Outdated
Comment thread src/base/strings/internal/const_init_string_helpers.cc Outdated
Comment thread src/base/strings/internal/const_init_string_helpers.cc Outdated
Comment thread src/base/strings/const_init_mutable_string.h Outdated
Comment thread src/base/strings/const_init_mutable_string.h Outdated
Comment thread src/base/strings/const_init_immutable_string_test.cc Outdated
Comment thread src/base/strings/const_init_immutable_string_test.cc Outdated
Comment thread src/base/strings/const_init_immutable_string.h Outdated
Comment thread src/base/strings/const_init_immutable_string.h Outdated
Comment thread src/base/strings/const_init_immutable_string.h Outdated
@yukawa yukawa force-pushed the constinit_string branch from ab58626 to 98619a0 Compare May 1, 2026 06:38
@yukawa
Copy link
Copy Markdown
Collaborator Author

yukawa commented May 1, 2026

Done. Let me know if you prefer std::make_unique over std::make_unique_for_overwrite.

Comment thread src/base/strings/internal/const_init_string_helpers.h Outdated
Comment thread src/base/strings/internal/const_init_string_helpers.h Outdated
@yukawa yukawa force-pushed the constinit_string branch from 98619a0 to 358d019 Compare May 8, 2026 08:16
Comment thread src/base/strings/internal/const_init_string_helpers.h Outdated
@yukawa yukawa force-pushed the constinit_string branch from 358d019 to c62288f Compare May 11, 2026 08:36
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.
@yukawa yukawa force-pushed the constinit_string branch from c62288f to 1797c00 Compare May 11, 2026 09:07
@yukawa
Copy link
Copy Markdown
Collaborator Author

yukawa commented May 12, 2026

I've uploaded another variant of the implementation that relies on std::atomic_flag::wait() instead of custom implementation of the spinlock with a fallback absl::Mutex-based implementation for platforms where std::atomic_flag::wait() is disallowed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants