fix(i18n): thread-safe translation for the daemon monitor thread (don't race loadLanguage)
Concurrency audit: I18n::translate() returns a const char* pointing INTO strings_, and loadLanguage() clears/rebuilds strings_ on the main thread at a language switch. The EmbeddedDaemon state/monitor callback builds its status strings via TR() off the main thread, so a concurrent language switch could free the map node out from under that pointer — a use-after-free data race. - Add a shared_mutex guarding strings_: loadLanguage() takes it exclusively for the whole clear+rebuild (its helpers loadBuiltinEnglish/overlayTranslations are called only from there and don't lock, so no recursion); a new translateCopy()/trCopy() takes a shared lock and returns an owned std::string. - The lock-free translate()/TR() stays for main-thread UI (same thread as the writer, so never races). - The four daemon-state setDaemonStatus(TR(...)) calls in the monitor callback now use util::trCopy(...). Synchronization only; no fund-path call touched. Threading fix — clean build + ctest; the race is runtime-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -114,6 +114,11 @@ void I18n::overlayTranslations(const nlohmann::json& translations)
|
||||
|
||||
bool I18n::loadLanguage(const std::string& locale)
|
||||
{
|
||||
// Exclusive for the whole load: strings_ is cleared/rebuilt below, and translateCopy() readers on
|
||||
// other threads (daemon monitor) must never observe it mid-rewrite. loadBuiltinEnglish() and
|
||||
// overlayTranslations() are called only from here and do not lock (no recursion).
|
||||
std::unique_lock<std::shared_mutex> lock(strings_mutex_);
|
||||
|
||||
// Try to load from file: CWD-relative first, then exe-relative
|
||||
std::string rel_path = "res/lang/" + locale + ".json";
|
||||
const std::string exe_dir = Platform::getExecutableDirectory();
|
||||
@@ -3116,6 +3121,15 @@ const char* I18n::translate(const char* key) const
|
||||
return key;
|
||||
}
|
||||
|
||||
std::string I18n::translateCopy(const char* key) const
|
||||
{
|
||||
if (!key) return std::string();
|
||||
std::shared_lock<std::shared_mutex> lock(strings_mutex_);
|
||||
auto it = strings_.find(key);
|
||||
if (it != strings_.end()) return it->second;
|
||||
return std::string(key);
|
||||
}
|
||||
|
||||
void I18n::registerLanguage(const std::string& code, const std::string& name)
|
||||
{
|
||||
// Check if already registered
|
||||
|
||||
Reference in New Issue
Block a user