From 82d3178119cc492e5f2c898cb17e1cb69c7d6cac Mon Sep 17 00:00:00 2001 From: DanS Date: Sat, 18 Jul 2026 18:03:10 -0500 Subject: [PATCH] fix: use reentrant localtime_r/localtime_s off the worker thread std::localtime shares a process-wide static tm; both sites are reachable from background threads (the RPC price-parse on the worker thread, and Logger::write from worker/monitor threads), so a concurrent localtime call could clobber the struct between the call and the read. Copy into a local std::tm via localtime_r / localtime_s, matching the codebase's existing convention (console_tab rpcTraceTimestamp, app_network, etc.). From the console-tab audit; benign (garbled/stale timestamp only), no crash or state impact. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/services/network_refresh_service.cpp | 11 +++++++++-- src/util/logger.cpp | 10 +++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/services/network_refresh_service.cpp b/src/services/network_refresh_service.cpp index b776b3c..fefc08e 100644 --- a/src/services/network_refresh_service.cpp +++ b/src/services/network_refresh_service.cpp @@ -434,8 +434,15 @@ std::optional NetworkRefreshService:: result.market.market_cap = data.value("usd_market_cap", 0.0); char buf[64]; - std::tm* tm = std::localtime(&fetchedAt); - if (tm && std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm) > 0) { + // Runs on the RPC worker thread — std::localtime shares a process-wide static tm, so use the + // reentrant variant into a local tm (matches the rest of the codebase). + std::tm tmv{}; +#ifdef _WIN32 + localtime_s(&tmv, &fetchedAt); +#else + localtime_r(&fetchedAt, &tmv); +#endif + if (std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tmv) > 0) { result.market.last_updated = buf; } return result; diff --git a/src/util/logger.cpp b/src/util/logger.cpp index 36de00a..a912767 100644 --- a/src/util/logger.cpp +++ b/src/util/logger.cpp @@ -57,7 +57,15 @@ void Logger::write(const std::string& message) now.time_since_epoch()) % 1000; std::stringstream ss; - ss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S"); + // Reachable from worker/monitor threads — std::localtime shares a process-wide static tm, so use the + // reentrant variant into a local tm (the logger's own mutex can't protect other localtime callers). + std::tm tmv{}; +#ifdef _WIN32 + localtime_s(&tmv, &time); +#else + localtime_r(&time, &tmv); +#endif + ss << std::put_time(&tmv, "%Y-%m-%d %H:%M:%S"); ss << '.' << std::setfill('0') << std::setw(3) << ms.count(); ss << " | " << message;