Skip to content

Add lz4 and zstd compression support to the CLFUS RAM cache#13257

Open
phongn wants to merge 9 commits into
apache:masterfrom
phongn:lz4
Open

Add lz4 and zstd compression support to the CLFUS RAM cache#13257
phongn wants to merge 9 commits into
apache:masterfrom
phongn:lz4

Conversation

@phongn

@phongn phongn commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Add lz4 and zstd compression support to the CLFUS RAM cache

Summary

This adds two modern compression backends to the CLFUS RAM cache, as optional build-time dependencies:

  • lz4 (proxy.config.cache.ram_cache.compress: 4) — a replacement for fastlz: strictly better compression ratio at substantially higher throughput.
  • zstd (proxy.config.cache.ram_cache.compress: 5, level 3) — a replacement for libz/deflate-6: comparable ratio at roughly 10× the compression speed and 3× the decompression speed.

The existing fastlz/libz/liblzma backends are unchanged and remain valid for their config values; the docs now recommend lz4 over fastlz and zstd over libz.

Benchmarks (lzbench, silesia XML corpus, Xeon Gold 6338, one thread):

Method Compress Decompress Final size
fastlz 452 MB/s 913 MB/s ~26%
lz4 727 MB/s 3458 MB/s ~23%
libz-6 54 MB/s 536 MB/s ~13%
zstd-3 508 MB/s 1690 MB/s ~12%

Implementation notes

  • lz4 uses the one-shot LZ4_compress_default / LZ4_decompress_safe API.
  • zstd uses per-thread reusable contexts (thread_local ZSTD_CCtx/ZSTD_DCtx) with ZSTD_compress2 and a sticky compression level, avoiding a context allocation per call — relevant since decompression sits on the cache-hit path. This requires zstd ≥ 1.4.0 (the first release with the advanced one-shot API stable); the version floor is enforced in find_package.
  • Misconfiguration is caught at startup: configuring a backend that wasn't compiled in is Fatal, matching the existing liblzma behavior.
  • New cmake/FindLZ4.cmake and cmake/FindZSTD.cmake modules. zstd detection previously used find_package(zstd CONFIG) only, which fails on distributions that don't ship zstd-config.cmake; it now resolves via the module (the zstd::zstd alias shim is kept for builds using CMAKE_FIND_PACKAGE_PREFER_CONFIG).
  • traffic_layout info now reports TS_HAS_LZ4 and the lz4 build/runtime versions, mirroring zstd.

Testing

The RamCacheCLFUS class definition moved from the .cc into a new private header so unit tests can drive compress_entries() synchronously. A new Catch2 test (test_RamCacheCLFUS) does store → compress → read-back roundtrips across all five backends, asserting byte-for-byte equality and the expected RAM_HIT_COMPRESS_* state, plus incompressible-fallback and small-payload cases. No prior test verified compression data integrity for any backend.

Drive-by fixes

  • The liblzma compress path allocated its output buffer at e->len instead of lzma_stream_buffer_bound(e->len), causing encode failures (and spurious "incompressible" marking) for data that didn't shrink; it now matches the other backends, with the existing REQUIRED_COMPRESSION/REQUIRED_SHRINK thresholds deciding what to keep.
  • Removed a stale unconditional "libz not available for RAM cache compression" warning that fired every second when compress: 2 was configured — zlib is a required dependency and always available.

CI / packaging

liblz4-dev / lz4-devel added to the deb and yum CI images. The Fedora CI image will need lz4-devel added for build coverage of the new backend (zstd-devel is already present).

Future work

Compression is currently implemented inside CLFUS only. A follow-up refactor will extract it into a shared layer so all RAM cache algorithms can use it; the new unit test is structured to migrate there.

@bryancall bryancall left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a pass over this — really nice change. The liblzma lzma_stream_buffer_bound() fix is a real correctness improvement, the per-thread zstd context design is clean, and adding compression-integrity tests where there were none is great. A handful of improvement suggestions inline; none are blockers.

The one I'd most encourage looking at is the sticky-null behavior of the thread-local zstd context: a one-time allocation failure silently degrades that thread to uncompressed forever (and evicts valid entries on read) with no log or metric. Details inline.

Comment thread src/iocore/cache/RamCacheCLFUS.cc
Comment thread src/iocore/cache/RamCacheCLFUS.cc
Comment thread src/iocore/cache/unit_tests/test_RamCacheCLFUS.cc Outdated
Comment thread include/iocore/cache/Cache.h
Comment thread doc/developer-guide/cache-architecture/ram-cache.en.rst Outdated
@cmcfarlen cmcfarlen added this to the 11.0.0 milestone Jun 15, 2026
@cmcfarlen cmcfarlen self-requested a review June 15, 2026 22:35
@ezelkow1

ezelkow1 commented Jun 15, 2026

Copy link
Copy Markdown
Member

Since I know you probably cant get to the CI output @phongn , here's the failure. It's in the clfus catch test:

28/162 Test  #30: test_cache_RamCacheCLFUS ...............***Failed    0.87 sec
Randomness seeded to: 1464257562
[Jun 11 21:42:58.809] RamCacheCLFUS DIAG: <Stripe.cc:154 (_init_directory)> (cache_init) Stripe  0:10: allocating 24576 directory bytes for a 81920 byte volume (30.000000%)
[Jun 11 21:42:58.923] RamCacheCLFUS DIAG: <Stripe.cc:178 (~Stripe)> (cache_free) Stripe  0:10: freeing raw_dir=0x62c000009000 size=24576 huge=false
[Jun 11 21:42:58.926] RamCacheCLFUS DIAG: <Stripe.cc:154 (_init_directory)> (cache_init) Stripe  0:10: allocating 24576 directory bytes for a 81920 byte volume (30.000000%)
[Jun 11 21:42:58.932] RamCacheCLFUS DIAG: <Stripe.cc:178 (~Stripe)> (cache_free) Stripe  0:10: freeing raw_dir=0x62c000011000 size=24576 huge=false
[Jun 11 21:42:58.934] RamCacheCLFUS DIAG: <Stripe.cc:154 (_init_directory)> (cache_init) Stripe  0:10: allocating 24576 directory bytes for a 81920 byte volume (30.000000%)
[Jun 11 21:42:58.942] RamCacheCLFUS DIAG: <Stripe.cc:178 (~Stripe)> (cache_free) Stripe  0:10: freeing raw_dir=0x62c000019000 size=24576 huge=false
[Jun 11 21:42:58.944] RamCacheCLFUS DIAG: <Stripe.cc:154 (_init_directory)> (cache_init) Stripe  0:10: allocating 24576 directory bytes for a 81920 byte volume (30.000000%)
[Jun 11 21:42:58.965] RamCacheCLFUS DIAG: <Stripe.cc:178 (~Stripe)> (cache_free) Stripe  0:10: freeing raw_dir=0x62c000021000 size=24576 huge=false
[Jun 11 21:42:58.967] RamCacheCLFUS DIAG: <Stripe.cc:154 (_init_directory)> (cache_init) Stripe  0:10: allocating 24576 directory bytes for a 81920 byte volume (30.000000%)
[Jun 11 21:42:58.974] RamCacheCLFUS DIAG: <Stripe.cc:178 (~Stripe)> (cache_free) Stripe  0:10: freeing raw_dir=0x62c000031000 size=24576 huge=false
[Jun 11 21:42:58.977] RamCacheCLFUS DIAG: <Stripe.cc:154 (_init_directory)> (cache_init) Stripe  0:10: allocating 24576 directory bytes for a 81920 byte volume (30.000000%)
[Jun 11 21:42:58.987] RamCacheCLFUS DIAG: <Stripe.cc:178 (~Stripe)> (cache_free) Stripe  0:10: freeing raw_dir=0x62c000039000 size=24576 huge=false
[Jun 11 21:42:58.989] RamCacheCLFUS DIAG: <Stripe.cc:154 (_init_directory)> (cache_init) Stripe  0:10: allocating 24576 directory bytes for a 81920 byte volume (30.000000%)
[Jun 11 21:42:59.002] RamCacheCLFUS DIAG: <Stripe.cc:178 (~Stripe)> (cache_free) Stripe  0:10: freeing raw_dir=0x62c000041000 size=24576 huge=false
[Jun 11 21:42:59.004] RamCacheCLFUS DIAG: <Stripe.cc:154 (_init_directory)> (cache_init) Stripe  0:10: allocating 24576 directory bytes for a 81920 byte volume (30.000000%)
[Jun 11 21:42:59.121] RamCacheCLFUS DIAG: <Stripe.cc:178 (~Stripe)> (cache_free) Stripe  0:10: freeing raw_dir=0x62c000049000 size=24576 huge=false
[Jun 11 21:42:59.123] RamCacheCLFUS DIAG: <Stripe.cc:154 (_init_directory)> (cache_init) Stripe  0:10: allocating 24576 directory bytes for a 81920 byte volume (30.000000%)
[Jun 11 21:42:59.126] RamCacheCLFUS DIAG: <Stripe.cc:178 (~Stripe)> (cache_free) Stripe  0:10: freeing raw_dir=0x62c000051000 size=24576 huge=false
[Jun 11 21:42:59.129] RamCacheCLFUS DIAG: <Stripe.cc:154 (_init_directory)> (cache_init) Stripe  0:10: allocating 24576 directory bytes for a 81920 byte volume (30.000000%)
[Jun 11 21:42:59.129] RamCacheCLFUS DIAG: <Stripe.cc:178 (~Stripe)> (cache_free) Stripe  0:10: freeing raw_dir=0x62c000059000 size=24576 huge=false

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
RamCacheCLFUS is a Catch2 v3.9.1 host application.
Run with -? for options

-------------------------------------------------------------------------------
CLFUS compression backends compiled in
-------------------------------------------------------------------------------
../src/iocore/cache/unit_tests/test_RamCacheCLFUS.cc:253
...............................................................................

../src/iocore/cache/unit_tests/test_RamCacheCLFUS.cc:259: warning:
  lz4 is not compiled in; the lz4 RAM cache compression backend is NOT tested

===============================================================================
All tests passed (45 assertions in 4 test cases)

=================================================================
==7803==ERROR: AddressSanitizer: heap-use-after-free on address 0x62f0000004f8 at pc 0x00000049078a bp 0x7f263c3ddbd0 sp 0x7f263c3ddbc0
WRITE of size 8 at 0x62f0000004f8 thread T1 ([ET_NET 0])
    #0 0x490789 in std::__atomic_base<long>::fetch_add(long, std::memory_order) /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/atomic_base.h:636
    #1 0x490789 in ts::Metrics::Counter::increment(ts::Metrics::Counter::AtomicType*, unsigned long) ../include/tsutil/Metrics.h:527
    #2 0x5c8e79 in NetHandler::waitForActivity(long) ../src/iocore/net/NetHandler.cc:350
    #3 0xd042e9 in EThread::execute_regular() ../src/iocore/eventsystem/UnixEThread.cc:326
    #4 0xd0488f in EThread::execute() ../src/iocore/eventsystem/UnixEThread.cc:383
    #5 0xd012fe in spawn_thread_internal ../src/iocore/eventsystem/Thread.cc:75
    #6 0x7f2640d421c9 in start_thread (/lib64/libpthread.so.0+0x81c9)
    #7 0x7f264099d952 in clone (/lib64/libc.so.6+0x39952)

0x62f0000004f8 is located 248 bytes inside of 49152-byte region [0x62f000000400,0x62f00000c400)
freed by thread T0 here:
    #0 0x7f264397236f in operator delete(void*, unsigned long) (/lib64/libasan.so.6+0xb736f)
    #1 0xdf3e0c in std::default_delete<std::tuple<std::array<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, 1024ul>, std::array<ts::Metrics::AtomicType, 1024ul> > >::operator()(std::tuple<std::array<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, 1024ul>, std::array<ts::Metrics::AtomicType, 1024ul> >*) const /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/unique_ptr.h:85
    #2 0xdf2a8a in std::unique_ptr<std::tuple<std::array<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, 1024ul>, std::array<ts::Metrics::AtomicType, 1024ul> >, std::default_delete<std::tuple<std::array<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, 1024ul>, std::array<ts::Metrics::AtomicType, 1024ul> > > >::~unique_ptr() /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/unique_ptr.h:361

=================================================================
    #3 0xdf1643 in std::array<std::unique_ptr<std::tuple<std::array<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, 1024ul>, std::array<ts::Metrics::AtomicType, 1024ul> >, std::default_delete<std::tuple<std::array<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, 1024ul>, std::array<ts::Metrics::AtomicType, 1024ul> > > >, 8192ul>::~array() /opt/rh/gcc-toolset-11/root/usr/include/c++/11/array:95
==7803==ERROR: LeakSanitizer: detected memory leaks
    #4 0xdf1d31 in ts::Metrics::Storage::~Storage() ../include/tsutil/Metrics.h:325

    #5 0xdfc516 in void std::destroy_at<ts::Metrics::Storage>(ts::Metrics::Storage*) /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/stl_construct.h:88
Direct leak of 5080 byte(s) in 5 object(s) allocated from:
    #6 0xdfc4ed in void std::allocator_traits<std::allocator<ts::Metrics::Storage> >::destroy<ts::Metrics::Storage>(std::allocator<ts::Metrics::Storage>&, ts::Metrics::Storage*) /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/alloc_traits.h:537
    #0 0x7f264396f9a7 in __interceptor_malloc (/lib64/libasan.so.6+0xb49a7)
    #7 0xdfc306 in std::_Sp_counted_ptr_inplace<ts::Metrics::Storage, std::allocator<ts::Metrics::Storage>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr_base.h:528
    #1 0xdd17f6 in ats_malloc(unsigned long) ../src/tscore/ink_memory.cc:65
    #8 0x58e416 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr_base.h:168
    #9 0x58d9af in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr_base.h:705
    #2 0x5229b9 in RamCacheCLFUS::_resize_hashtable() ../src/iocore/cache/RamCacheCLFUS.cc:192
    #10 0xdf1313 in std::__shared_ptr<ts::Metrics::Storage, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr_base.h:1154
    #3 0x522e32 in RamCacheCLFUS::init(long, StripeSM*) ../src/iocore/cache/RamCacheCLFUS.cc:223
    #11 0xdf132f in std::shared_ptr<ts::Metrics::Storage>::~shared_ptr() /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr.h:122
    #12 0x7f26409b4d4b in __run_exit_handlers (/lib64/libc.so.6+0x50d4b)
    #4 0x46e045 in store_compress_get ../src/iocore/cache/unit_tests/test_RamCacheCLFUS.cc:157

    #5 0x46f559 in CATCH2_INTERNAL_TEST_0 ../src/iocore/cache/unit_tests/test_RamCacheCLFUS.cc:201
previously allocated by thread T0 here:
    #6 0x7f2641c19080 in invoke ../lib/Catch2/src/catch2/internal/catch_test_registry.cpp:60
    #7 0x7f2641be5741 in Catch::TestCaseHandle::invoke() const ../lib/Catch2/src/catch2/catch_test_case_info.hpp:124
    #0 0x7f2643971307 in operator new(unsigned long) (/lib64/libasan.so.6+0xb6307)
    #8 0x7f2641be227b in Catch::RunContext::invokeActiveTestCase() ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:673
    #9 0x7f2641be19f0 in Catch::RunContext::runCurrentTest() ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:631
    #1 0xdf2c19 in std::_MakeUniq<std::tuple<std::array<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, 1024ul>, std::array<ts::Metrics::AtomicType, 1024ul> > >::__single_object std::make_unique<std::tuple<std::array<std::tuple<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, 1024ul>, std::array<ts::Metrics::AtomicType, 1024ul> >>() /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/unique_ptr.h:962
    #10 0x7f2641bdc948 in Catch::RunContext::runTest(Catch::TestCaseHandle const&) ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:273
    #2 0xdf1a15 in ts::Metrics::Storage::Storage() ../include/tsutil/Metrics.h:319
    #11 0x7f2641b21a28 in execute ../lib/Catch2/src/catch2/catch_session.cpp:108
    #3 0xdfb5f8 in decltype (::new ((void*)(0)) ts::Metrics::Storage()) std::construct_at<ts::Metrics::Storage>(ts::Metrics::Storage*) /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/stl_construct.h:97
    #12 0x7f2641b2472f in Catch::Session::runInternal() ../lib/Catch2/src/catch2/catch_session.cpp:328
    #4 0xdfb642 in void std::allocator_traits<std::allocator<ts::Metrics::Storage> >::construct<ts::Metrics::Storage>(std::allocator<ts::Metrics::Storage>&, ts::Metrics::Storage*) /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/alloc_traits.h:518
    #13 0x7f2641b23ca4 in Catch::Session::run() ../lib/Catch2/src/catch2/catch_session.cpp:260
    #5 0xdfaadc in std::_Sp_counted_ptr_inplace<ts::Metrics::Storage, std::allocator<ts::Metrics::Storage>, (__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr_inplace<>(std::allocator<ts::Metrics::Storage>) /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr_base.h:519
    #6 0xdf953e in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<ts::Metrics::Storage, std::allocator<ts::Metrics::Storage>>(ts::Metrics::Storage*&, std::_Sp_alloc_shared_tag<std::allocator<ts::Metrics::Storage> >) /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr_base.h:650
    #7 0xdf7bd9 in std::__shared_ptr<ts::Metrics::Storage, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<ts::Metrics::Storage>>(std::_Sp_alloc_shared_tag<std::allocator<ts::Metrics::Storage> >) /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr_base.h:1342
    #8 0xdf5fe5 in std::shared_ptr<ts::Metrics::Storage>::shared_ptr<std::allocator<ts::Metrics::Storage>>(std::_Sp_alloc_shared_tag<std::allocator<ts::Metrics::Storage> >) /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr.h:409
    #9 0xdf4265 in std::shared_ptr<ts::Metrics::Storage> std::allocate_shared<ts::Metrics::Storage, std::allocator<ts::Metrics::Storage>>(std::allocator<ts::Metrics::Storage> const&) /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr.h:863
    #10 0xdf2d6d in std::shared_ptr<ts::Metrics::Storage> std::make_shared<ts::Metrics::Storage>() /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/shared_ptr.h:879
    #11 0xdedee3 in ts::Metrics::instance() ../src/tsutil/Metrics.cc:39
    #12 0xd2be8a in RecLookupRecord(char const*, void (*)(RecRecord const*, void*), void*, bool) ../src/records/RecCore.cc:520
    #14 0x7f26444c74e7 in int Catch::Session::run<char>(int, char const* const*) ../lib/Catch2/src/catch2/catch_session.hpp:49
    #13 0xd2ba97 in RecGetRecordStringAlloc[abi:cxx11](char const*, bool) ../src/records/RecCore.cc:477
    #15 0x7f26444c72ce in main ../lib/Catch2/src/catch2/internal/catch_main.cpp:36
    #16 0x7f264099e864 in __libc_start_main (/lib64/libc.so.6+0x3a864)

Direct leak of 4064 byte(s) in 4 object(s) allocated from:
    #0 0x7f264396f9a7 in __interceptor_malloc (/lib64/libasan.so.6+0xb49a7)
    #1 0xdd17f6 in ats_malloc(unsigned long) ../src/tscore/ink_memory.cc:65
    #2 0x5229b9 in RamCacheCLFUS::_resize_hashtable() ../src/iocore/cache/RamCacheCLFUS.cc:192
    #3 0x522e32 in RamCacheCLFUS::init(long, StripeSM*) ../src/iocore/cache/RamCacheCLFUS.cc:223
    #4 0x46e045 in store_compress_get ../src/iocore/cache/unit_tests/test_RamCacheCLFUS.cc:157
    #14 0x56ed81 in configure_net ../src/iocore/net/Net.cc:59
    #5 0x470c93 in CATCH2_INTERNAL_TEST_4 ../src/iocore/cache/unit_tests/test_RamCacheCLFUS.cc:229
    #15 0x570a57 in ink_net_init(ts::ModuleVersion) ../src/iocore/net/Net.cc:139
    #6 0x7f2641c19080 in invoke ../lib/Catch2/src/catch2/internal/catch_test_registry.cpp:60
    #7 0x7f2641be5741 in Catch::TestCaseHandle::invoke() const ../lib/Catch2/src/catch2/catch_test_case_info.hpp:124
    #16 0x451f33 in EventProcessorListener::testRunStarting(Catch::TestRunInfo const&) (/home/jenkins/workspace/Github_Builds/rocky/src/build/src/iocore/cache/RamCacheCLFUS+0x451f33)
    #8 0x7f2641be227b in Catch::RunContext::invokeActiveTestCase() ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:673
    #17 0x7f2641ae7227 in Catch::MultiReporter::testRunStarting(Catch::TestRunInfo const&) ../lib/Catch2/src/catch2/reporters/catch_reporter_multi.cpp:89
    #9 0x7f2641be19f0 in Catch::RunContext::runCurrentTest() ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:631
    #18 0x7f2641bdbccf in Catch::RunContext::RunContext(Catch::IConfig const*, Catch::Detail::unique_ptr<Catch::IEventListener>&&) ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:207
    #10 0x7f2641bdc948 in Catch::RunContext::runTest(Catch::TestCaseHandle const&) ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:273
    #11 0x7f2641b21a28 in execute ../lib/Catch2/src/catch2/catch_session.cpp:108
    #19 0x7f2641b20dce in TestGroup ../lib/Catch2/src/catch2/catch_session.cpp:79
    #12 0x7f2641b2472f in Catch::Session::runInternal() ../lib/Catch2/src/catch2/catch_session.cpp:328
    #20 0x7f2641b246af in Catch::Session::runInternal() ../lib/Catch2/src/catch2/catch_session.cpp:327
    #13 0x7f2641b23ca4 in Catch::Session::run() ../lib/Catch2/src/catch2/catch_session.cpp:260
    #21 0x7f2641b23ca4 in Catch::Session::run() ../lib/Catch2/src/catch2/catch_session.cpp:260
    #14 0x7f26444c74e7 in int Catch::Session::run<char>(int, char const* const*) ../lib/Catch2/src/catch2/catch_session.hpp:49
    #22 0x7f26444c74e7 in int Catch::Session::run<char>(int, char const* const*) ../lib/Catch2/src/catch2/catch_session.hpp:49
    #15 0x7f26444c72ce in main ../lib/Catch2/src/catch2/internal/catch_main.cpp:36
    #23 0x7f26444c72ce in main ../lib/Catch2/src/catch2/internal/catch_main.cpp:36
    #16 0x7f264099e864 in __libc_start_main (/lib64/libc.so.6+0x3a864)

    #24 0x7f264099e864 in __libc_start_main (/lib64/libc.so.6+0x3a864)

Direct leak of 1016 byte(s) in 1 object(s) allocated from:
    #0 0x7f264396f9a7 in __interceptor_malloc (/lib64/libasan.so.6+0xb49a7)
Thread T1 ([ET_NET 0]) created by T0 here:
    #1 0xdd17f6 in ats_malloc(unsigned long) ../src/tscore/ink_memory.cc:65
    #0 0x7f26439137c5 in pthread_create (/lib64/libasan.so.6+0x587c5)
    #2 0x5229b9 in RamCacheCLFUS::_resize_hashtable() ../src/iocore/cache/RamCacheCLFUS.cc:192
    #1 0xd00da3 in ink_thread_create ../include/tscore/ink_thread.h:129
    #3 0x522e32 in RamCacheCLFUS::init(long, StripeSM*) ../src/iocore/cache/RamCacheCLFUS.cc:223
    #2 0xd0142b in Thread::start(char const*, void*, unsigned long, std::function<void ()> const&) ../src/iocore/eventsystem/Thread.cc:92
    #4 0x46e045 in store_compress_get ../src/iocore/cache/unit_tests/test_RamCacheCLFUS.cc:157
    #3 0xd0d1c7 in EventProcessor::spawn_event_threads(int, int, unsigned long) ../src/iocore/eventsystem/UnixEventProcessor.cc:472
    #5 0x4719d4 in CATCH2_INTERNAL_TEST_8 ../src/iocore/cache/unit_tests/test_RamCacheCLFUS.cc:244
    #4 0xd0dd52 in EventProcessor::start(int, unsigned long) ../src/iocore/eventsystem/UnixEventProcessor.cc:553
    #6 0x7f2641c19080 in invoke ../lib/Catch2/src/catch2/internal/catch_test_registry.cpp:60
    #5 0x45202f in EventProcessorListener::testRunStarting(Catch::TestRunInfo const&) (/home/jenkins/workspace/Github_Builds/rocky/src/build/src/iocore/cache/RamCacheCLFUS+0x45202f)
    #7 0x7f2641be5741 in Catch::TestCaseHandle::invoke() const ../lib/Catch2/src/catch2/catch_test_case_info.hpp:124
    #6 0x7f2641ae7227 in Catch::MultiReporter::testRunStarting(Catch::TestRunInfo const&) ../lib/Catch2/src/catch2/reporters/catch_reporter_multi.cpp:89
    #8 0x7f2641be227b in Catch::RunContext::invokeActiveTestCase() ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:673
    #7 0x7f2641bdbccf in Catch::RunContext::RunContext(Catch::IConfig const*, Catch::Detail::unique_ptr<Catch::IEventListener>&&) ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:207
    #9 0x7f2641be19f0 in Catch::RunContext::runCurrentTest() ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:631
    #8 0x7f2641b20dce in TestGroup ../lib/Catch2/src/catch2/catch_session.cpp:79
    #10 0x7f2641bdc948 in Catch::RunContext::runTest(Catch::TestCaseHandle const&) ../lib/Catch2/src/catch2/internal/catch_run_context.cpp:273
    #9 0x7f2641b246af in Catch::Session::runInternal() ../lib/Catch2/src/catch2/catch_session.cpp:327
    #11 0x7f2641b21a28 in execute ../lib/Catch2/src/catch2/catch_session.cpp:108
    #10 0x7f2641b23ca4 in Catch::Session::run() ../lib/Catch2/src/catch2/catch_session.cpp:260
    #12 0x7f2641b2472f in Catch::Session::runInternal() ../lib/Catch2/src/catch2/catch_session.cpp:328
    #11 0x7f26444c74e7 in int Catch::Session::run<char>(int, char const* const*) ../lib/Catch2/src/catch2/catch_session.hpp:49
    #13 0x7f2641b23ca4 in Catch::Session::run() ../lib/Catch2/src/catch2/catch_session.cpp:260
    #12 0x7f26444c72ce in main ../lib/Catch2/src/catch2/internal/catch_main.cpp:36
    #14 0x7f26444c74e7 in int Catch::Session::run<char>(int, char const* const*) ../lib/Catch2/src/catch2/catch_session.hpp:49
    #13 0x7f264099e864 in __libc_start_main (/lib64/libc.so.6+0x3a864)
    #15 0x7f26444c72ce in main ../lib/Catch2/src/catch2/internal/catch_main.cpp:36

    #16 0x7f264099e864 in __libc_start_main (/lib64/libc.so.6+0x3a864)

SUMMARY: AddressSanitizer: 10160 byte(s) leaked in 10 allocation(s).
SUMMARY: AddressSanitizer: heap-use-after-free /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/atomic_base.h:636 in std::__atomic_base<long>::fetch_add(long, std::memory_order)

RamCacheCLFUS allocated its hash table, seen filter, and entries but had
no destructor, so destroying an instance leaked them. LeakSanitizer
flagged this once the new unit test started creating and destroying
instances. Add a destructor that releases each entry's data, returns the
entries to the allocator, and frees the table and seen filter.

The synchronous RAM cache test never calls TEST_DONE(), so the event
threads kept running as the process tore down static state at exit and
an ET_NET thread incremented the freed Metrics singleton
(heap-use-after-free). Shut the event system down from the shared cache
test harness at end of run so the threads stop first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@phongn

phongn commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

Related PR to add zstd and lz4 to our CI hosts: apache/trafficserver-ci#441

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants