From 24e25ff19131c4fb7595755ad9fb3c6bd369ccd9 Mon Sep 17 00:00:00 2001 From: ljluestc Date: Tue, 4 Nov 2025 10:15:50 -0800 Subject: [PATCH 1/2] Add standalone test verifying no hang after tcmalloc ReleaseFreeMemory --- tcmalloc_release_test.cc | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tcmalloc_release_test.cc diff --git a/tcmalloc_release_test.cc b/tcmalloc_release_test.cc new file mode 100644 index 0000000000..c5edb55825 --- /dev/null +++ b/tcmalloc_release_test.cc @@ -0,0 +1,59 @@ +// Minimal test to verify no hang after calling tcmalloc's +// MallocExtension_ReleaseFreeMemory C API. +// +// Build (Linux): +// g++ -O2 -Wall tcmalloc_release_test.cc -o tcmalloc_release_test -ltcmalloc -ldl \ +// || g++ -O2 -Wall tcmalloc_release_test.cc -o tcmalloc_release_test -ltcmalloc_minimal -ldl +// Run: +// ./tcmalloc_release_test + +#include +#include +#include +#include +#include +#include + +extern "C" void MallocExtension_ReleaseFreeMemory(void) __attribute__((weak)); + +static void on_alarm(int) { + std::fprintf(stderr, "Timed out (possible hang after ReleaseFreeMemory)\n"); + std::_Exit(124); +} + +static void churn_allocations(std::size_t bytes_total) { + const std::size_t chunk = 1 << 20; // 1 MiB + std::vector ptrs; + ptrs.reserve(bytes_total / chunk); + for (std::size_t i = 0; i < bytes_total; i += chunk) { + void* p = std::malloc(chunk); + if (!p) break; + std::memset(p, 0xA5, chunk); + ptrs.push_back(p); + } + for (void* p : ptrs) std::free(p); +} + +int main() { + // Install a timeout so a hang becomes visible fast. + std::signal(SIGALRM, on_alarm); + alarm(10); + + std::printf("Phase 1 alloc/free...\n"); + churn_allocations(256ULL << 20); // 256 MiB + + std::printf("Release free memory to system via C API...\n"); + if (MallocExtension_ReleaseFreeMemory) { + MallocExtension_ReleaseFreeMemory(); + } else { + std::fprintf(stderr, "MallocExtension_ReleaseFreeMemory symbol not found\n"); + } + + std::printf("Phase 2 alloc/free (should not hang)...\n"); + churn_allocations(256ULL << 20); // 256 MiB + + std::printf("OK\n"); + return 0; +} + + From 667647159e163496a6c22a23628524ce97bd5bb8 Mon Sep 17 00:00:00 2001 From: ljluestc Date: Tue, 4 Nov 2025 20:39:12 -0800 Subject: [PATCH 2/2] fix tests --- test/brpc_tcmalloc_release_unittest.cpp | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 test/brpc_tcmalloc_release_unittest.cpp diff --git a/test/brpc_tcmalloc_release_unittest.cpp b/test/brpc_tcmalloc_release_unittest.cpp new file mode 100644 index 0000000000..30d74687db --- /dev/null +++ b/test/brpc_tcmalloc_release_unittest.cpp @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include +#include +#include +#include +#include +#include + +// Weakly reference the C symbol so the test runs even without tcmalloc. +extern "C" void MallocExtension_ReleaseFreeMemory(void) __attribute__((weak)); + +namespace { + +class AlarmGuard { +public: + explicit AlarmGuard(unsigned seconds) { + std::signal(SIGALRM, &AlarmHandler); + alarm(seconds); + } + ~AlarmGuard() { alarm(0); } +private: + static void AlarmHandler(int) { + // Fail fast on potential hang. + std::fprintf(stderr, "Timed out in ReleaseFreeMemory test\n"); + _exit(124); + } +}; + +static void churn_allocations(std::size_t bytes_total) { + const std::size_t chunk = 1 << 20; // 1 MiB + std::vector ptrs; + ptrs.reserve(bytes_total / chunk); + for (std::size_t i = 0; i < bytes_total; i += chunk) { + void* p = std::malloc(chunk); + if (!p) break; + std::memset(p, 0xA5, chunk); + ptrs.push_back(p); + } + for (void* p : ptrs) std::free(p); +} + +TEST(TCMallocReleaseTest, NoHangAfterRelease) { + AlarmGuard guard(10); + churn_allocations(128ULL << 20); // 128 MiB + + if (MallocExtension_ReleaseFreeMemory) { + MallocExtension_ReleaseFreeMemory(); + } + + churn_allocations(128ULL << 20); // 128 MiB + SUCCEED(); +} + +} // namespace + +