Skip to content

fix: apply Intentional Leak pattern to GlobalLoggerRegistry for SDOF prevention #200

Description

@kcenon

Summary

GlobalLoggerRegistry::instance() currently uses Meyer's Singleton pattern, which can cause Static Destruction Order Fiasco (SDOF) when accessed during static destruction phase.

Problem

Current Implementation (global_logger_registry.h:335-337)

static GlobalLoggerRegistry& instance() {
    static GlobalLoggerRegistry instance;
    return instance;
}

Root Cause

When thread_system components (e.g., thread_pool, thread_context) are destroyed during static destruction:

  1. thread_context holds std::shared_ptr<ILogger> obtained from GlobalLoggerRegistry
  2. If GlobalLoggerRegistry is destroyed before thread_pool, the ILogger implementation stored inside the registry becomes invalid
  3. Even with is_shutting_down() guards in thread_context::log(), the logger_ member itself may reference destroyed memory

Destruction Chain

Process termination
    │
    ├─► atexit handlers (before static destruction)
    │       └─► thread_logger::prepare_shutdown() → is_shutting_down_ = true
    │
    └─► Static object destruction (LIFO order)
            │
            ├─► GlobalLoggerRegistry::instance() destroyed ⚠️
            │       └─► Internal loggers_, default_logger_ destroyed
            │
            └─► User's static thread_pool destroyed
                    └─► thread_context::logger_ may point to destroyed ILogger

Proposed Solution

Apply Intentional Leak pattern to GlobalLoggerRegistry::instance():

static GlobalLoggerRegistry& instance() {
    // Intentionally leak to avoid static destruction order issues
    // Registry may be accessed during other singletons' destruction
    static GlobalLoggerRegistry* instance = new GlobalLoggerRegistry();
    return *instance;
}

Also apply to null_logger() (global_logger_registry.h:340-343):

static std::shared_ptr<ILogger> null_logger() {
    // Intentionally leak to avoid static destruction order issues
    static auto* null_logger_instance = new std::shared_ptr<NullLogger>(std::make_shared<NullLogger>());
    return *null_logger_instance;
}

Benefits

  1. Complete SDOF prevention: Registry remains valid throughout process lifetime
  2. No behavioral changes: API remains identical
  3. Minimal memory impact: Single instance (~few hundred bytes) leaked at process termination, reclaimed by OS
  4. Consistent with thread_logger: thread_system's thread_logger already uses this pattern (refactor: Extract generic enum serialization template to eliminate duplication #293)

Related Issues

  • thread_system#293 (thread_logger Intentional Leak pattern)
  • thread_system#295 (thread_pool SDOF prevention)
  • thread_system#296 (PR implementing SDOF fixes)

Acceptance Criteria

  • Change GlobalLoggerRegistry::instance() to Intentional Leak pattern
  • Change null_logger() to Intentional Leak pattern
  • Add documentation comments explaining the pattern choice
  • Verify existing tests pass
  • Verify no memory sanitizer false positives (leak is intentional)

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions