Skip to content

Conversation

@chaowyc
Copy link

@chaowyc chaowyc commented Nov 12, 2025

Summary

This PR fixes a memory leak in TracyVector where element destructors are not called for non-trivially-destructible types. The bug affects production code using nested containers like Vector<Vector<short_ptr<ZoneEvent>>>.

Root Cause

TracyVector is designed as a lightweight container optimized for POD types. However, it's also used with non-trivially-destructible types throughout Tracy's codebase. The current implementation never calls element destructors, causing memory leaks when elements manage resources (e.g., heap-allocated memory in nested vectors).

Solution

Add compile-time type checking using C++17 if constexpr with std::is_trivially_destructible_v<T>:

~Vector() {
    if (m_ptr) {
        // Smart destruction - call destructors for non-trivial types
        // For trivial types, if constexpr is optimized away by compiler, zero overhead
        if constexpr (!std::is_trivially_destructible_v<T>) {
            for (uint32_t i = 0; i < m_size; i++) {
                m_ptr[i].~T();
            }
        }
        free(m_ptr);
    }
}

This approach:

  • Zero overhead for trivial types - if constexpr is evaluated at compile time
  • Proper cleanup for non-trivial types - destructors are called when needed
  • Backward compatible - doesn't change behavior for existing POD usage
  • Applied to 4 locations: destructor, move assignment, clear(), Realloc()

Impact on Production Code

This bug affects critical data structures in TracyWorker.hpp:

// Line ~500
Vector<Vector<short_ptr<ZoneEvent>>> zoneChildren;

// Line ~520
Vector<Vector<short_ptr<GpuEvent>>> gpuChildren;

// Line ~540
Vector<Vector<GhostZone>> ghostChildren;

// Line ~560
Vector<Vector<short_ptr<ZoneEvent>>> zoneVectorCache;

TracyVector Usage Statistics:

  • 25+ uses in TracyEvent.hpp (mostly POD types)
  • 4 critical nested container instances in TracyWorker.hpp (affected by this bug)

Verification

Tested with AddressSanitizer/LeakSanitizer using Tracy's actual production types:

Before Fix

=================================================================
==12345==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 24 byte(s) in 3 allocation(s) from:
    #0 0x7f8b4c in malloc
    #1 0x401234 in Vector<Vector<short_ptr<ZoneEvent>>>::push_back

Direct leak of 12 byte(s) in 3 allocation(s) from:
    #0 0x7f8b4c in malloc
    #1 0x401567 in Vector<short_ptr<ZoneEvent>>::push_back

SUMMARY: AddressSanitizer: 36 byte(s) leaked in 6 allocation(s).

After Fix

=================================================================
==12346==LeakSanitizer has found no leaks.
SUMMARY: AddressSanitizer: 0 byte(s) leaked in 0 allocation(s).

Test Coverage

The fix includes a comprehensive test suite (test_before_after_fix.cpp) with 6 test cases:

  1. Nested vectors (Vector<Vector<short_ptr<ZoneEvent>>>)
  2. Move assignment for nested vectors
  3. clear() with non-trivial types
  4. POD types (zero overhead verification)
  5. Complex nested structures (Vector<Vector<GhostZone>>)
  6. Memory reallocation scenarios

All tests pass with AddressSanitizer validation.

Backward Compatibility

  • ✅ No breaking changes
  • ✅ Existing POD usage unchanged (compile-time optimization)
  • ✅ No performance regression for trivial types
  • ✅ Fixes undefined behavior for non-trivial types

Files Changed

  • server/TracyVector.hpp: Added <type_traits> include and if constexpr checks in 4 locations

Request for Feedback

I'd appreciate feedback on:

  1. Whether this approach aligns with Tracy's design philosophy
  2. Any additional test cases you'd like to see
  3. Performance implications (though compile-time checks should have zero overhead)

Thank you for maintaining this excellent profiling tool!

- Add <type_traits> include for compile-time type checking
- Use if constexpr to conditionally call element destructors
- Apply fix to 4 locations: destructor, move assignment, clear(), Realloc()
- Fixes nested containers like Vector<Vector<short_ptr<ZoneEvent>>>
- Zero overhead for trivial types (compile-time check)

This bug affects Tracy's production code in TracyWorker.hpp:
- Vector<Vector<short_ptr<ZoneEvent>>> zoneChildren (line ~500)
- Vector<Vector<short_ptr<GpuEvent>>> gpuChildren (line ~520)
- Vector<Vector<GhostZone>> ghostChildren (line ~540)
- Vector<Vector<short_ptr<ZoneEvent>>> zoneVectorCache (line ~560)

Verified with AddressSanitizer:
- Before: 36 bytes leaked in 6 allocations
- After: 0 bytes leaked

Test suite uses Tracy's actual production types.
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.

1 participant