Logger and timer macros improvements - #556
Conversation
a175844 to
fc1dfa9
Compare
There was a problem hiding this comment.
Pull request overview
Hardens ModuleLogger concurrency behavior and reduces hot-path overhead for logging/timing macros, adding a dedicated multithreaded regression test and minor build-system tweaks to quiet a bundled warning.
Changes:
- Makes
ModuleLoggermap access consistently mutex-protected and preventsGetLogger()from returning null by eager init + on-demand creation with hard-fail fallback. - Optimizes
ScopedTimerand logging macros to avoid unnecessary work when DEBUG is disabled (call-site caching, early-outs,string_viewhandling). - Adds a new TSAN-friendly concurrency test and wires it into CTest; adds branch-hint macros and suppresses a deprecated literal-operator warning for bundled
fmt/spdlog.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
tests/cpp/utils/test_logging.cpp |
Adds multithreaded stress tests targeting logger resolution/creation races and concurrent reconfiguration. |
tests/cpp/CMakeLists.txt |
Builds and registers the new test_logging gtest binary independently of BUILD_IO. |
include/mori/utils/mori_log.hpp |
Refactors ModuleLogger locking/initialization and updates logging/timer macros for hot-path performance. |
include/mori/core/utils/utils.hpp |
Adds MORI_LIKELY/MORI_UNLIKELY and scopes warpSize macro to device compilation. |
CMakeLists.txt |
Conditionally applies -Wno-deprecated-literal-operator to spdlog and mori_logging consumers. |
Suppressed comments (2)
include/mori/utils/mori_log.hpp:424
- ScopedTimer::ElapsedSeconds() computes Clock::now() - start_ even when the timer is disabled (logger_ == nullptr), but start_ is left default-initialized in that case. That yields a meaningless large duration (time since epoch) if ElapsedSeconds() is called on a disabled timer.
double ElapsedSeconds() const {
return std::chrono::duration<double>(Clock::now() - start_).count();
}
include/mori/utils/mori_log.hpp:445
- MORI_FUNCTION_TIMER also expands to multiple statements, with the same single-statement-context pitfalls as MORI_TIMER. Converting it to a single declaration expression keeps the scope-based RAII behavior while remaining safe in
if/elsewithout braces.
#define MORI_FUNCTION_TIMER(module) \
static const std::shared_ptr<spdlog::logger> _mori_timer_logger = \
::mori::ModuleLogger::GetInstance().GetLogger(module); \
::mori::ScopedTimer timer_instance(__PRETTY_FUNCTION__, _mori_timer_logger.get())
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| ts.emplace_back([&, t] { | ||
| gate.wait(); | ||
| for (int i = 0; i < kIters; ++i) { | ||
| const char* known = kKnown[(t + i) % 7]; |
|
Hi @maning00, you mentioned that includng utils.hpp from mori_log.hpp breaks some device module compilations? Can you point me to the relevant module? I do not see any compile errors locally. The reason I did it is to have MORI_LIKELY/UNLIKELY macros to be defined in one central place.. |
Summary
This PR hardens the logging subsystem (
include/mori/utils/mori_log.hpp) on two fronts:ModuleLogger's internal maps that, under themultithreaded RDMA workload, could hand back an empty
shared_ptrthatcallers then cached forever.
ScopedTimeroverhead on the hot path so timers compile down toa true no-op unless DEBUG logging is actually enabled for the module.
It also adds a dedicated, TSAN-friendly concurrency test and silences a
deprecated-literal warning coming from the bundled
fmt/spdlog.Motivation
ModuleLoggerguarded itsloggers_/envOverrides_maps inconsistently andrelied on a
recursive_mutex, while several methods called each other andre-locked. Concurrent first-touch of a module (or resolution of an unknown
module) raced the on-demand insert, occasionally returning a null logger that a
call-site cached permanently. Separately,
ScopedTimeralways copied strings,read the clock twice, and did a per-call
GetLogger()hashtable lookup even whenDEBUG was off — measurable on hot paths such as a per-batch
MORI_IO_TRACE.Changes
ModuleLoggerthread-safetystill single-threaded, so
loggers_is fully populated before anyGetLogger()can touch the map.*Lockedhelpers (GetLoggerLocked,InitModuleLocked,HasEnvOverrideLocked) so methods no longer re-lock viaeach other.
recursive_mutexwith a plainstd::mutex.GetLogger()never returns null: unknown modules are created on demand, and ahard failure logs a fatal message and
abort()s instead of silently droppinglogs.
SetModuleLevel()now emits the env-override warning via the pre-resolvedlogger while holding the lock (no re-entrant
GetLogger).ScopedTimerhot-path optimization(module -> logger)lookup once per call-site in afunction-local static (
MORI_TIMER/MORI_FUNCTION_TIMER).read, no allocation. The enabled path is marked
MORI_UNLIKELY.std::string_view, copying into an owned stringonly on the enabled path (safe for temporaries).
enabled_/module_members;logger_ != nullptrnowserves as the "enabled" sentinel.
Supporting changes
include/mori/core/utils/utils.hpp: addMORI_LIKELY/MORI_UNLIKELYbranch-hint macros and scope the
warpSizemacro to device compilation.CMakeLists.txt: silence-Wdeprecated-literal-operatorfrom bundledfmtfor both the
spdlogbuild andmori_loggingconsumers (guarded by acompiler-flag check).
Tests
tests/cpp/utils/test_logging.cppwith multi-threaded stress tests:ResolveNeverReturnsNull— concurrent resolution of known + unknown modules.ConcurrentFirstTouchSameModule— 64 threads first-touch the same newmodule; all must observe one stable, non-null instance.
LogWhileReconfiguring— logging while levels are reconfigured concurrently.tests/cpp/CMakeLists.txtastest_logging(builds independentlyof
BUILD_IOsince core logging is header-only). Intended to be run underThreadSanitizer to prove map access is synchronized.