Skip to content

Fix tcmalloc compatibility issue with aligned_alloc#683

Open
wupq2022 wants to merge 1 commit intomicrosoft:cpp_mainfrom
wupq2022:fix/tcmalloc-aligned-alloc-compatibility
Open

Fix tcmalloc compatibility issue with aligned_alloc#683
wupq2022 wants to merge 1 commit intomicrosoft:cpp_mainfrom
wupq2022:fix/tcmalloc-aligned-alloc-compatibility

Conversation

@wupq2022
Copy link

@wupq2022 wupq2022 commented Dec 9, 2025

Replace aligned_alloc() with posix_memalign() to fix crashes when using tcmalloc. The issue occurs because aligned_alloc() uses glibc's allocator while tcmalloc replaces free(), causing "invalid pointer" errors when freeing memory.

posix_memalign() is more compatible with tcmalloc and other memory allocators, as it properly integrates with the malloc/free interface that tcmalloc intercepts.

Changes:

  • Add #include <stdlib.h> for posix_memalign()
  • Replace aligned_alloc() with posix_memalign() in alloc_aligned()
  • Add proper error handling for posix_memalign() return value

This fixes crashes during index construction when InMemDataStore objects are destructed after saving the index.

  • Does this PR have a descriptive title that could go in our release notes?
  • Does this PR add any new dependencies?
  • Does this PR modify any existing APIs?
    • Is the change to the API backwards compatible?
  • Should this result in any changes to our documentation, either updating existing docs or adding new ones?

Reference Issues/PRs

Related to #527. This issue has been reported by multiple users experiencing the same "Attempt to free invalid pointer" error when using tcmalloc. This fix addresses the root cause of the compatibility issue between aligned_alloc() and tcmalloc's free() function.

What does this implement/fix? Briefly explain your changes.

This PR fixes a crash that occurs when building DiskANN indexes with tcmalloc enabled. The crash manifests as "Attempt to free invalid pointer" errors when InMemDataStore objects are destructed after saving the index.

Root Cause:

  • aligned_alloc() (C11) uses glibc's memory allocator directly
  • tcmalloc replaces the standard free() function
  • When tcmalloc's free() tries to free memory allocated by aligned_alloc(), it detects that the pointer was not allocated by tcmalloc and reports an "invalid pointer" error

Solution:

  • Replace aligned_alloc() with posix_memalign() in the alloc_aligned() function
  • posix_memalign() is older (POSIX standard) and better supported by memory allocators like tcmalloc
  • Memory allocated with posix_memalign() properly integrates with the malloc/free interface that tcmalloc intercepts
  • Added proper error handling for posix_memalign() return value (it returns an error code instead of NULL)

Changes:

  1. Added #include <stdlib.h> for posix_memalign() function
  2. Replaced aligned_alloc() call with posix_memalign() in alloc_aligned() function
  3. Added error handling to check return value and set pointer to nullptr on failure

Any other comments?

This change maintains backward compatibility as posix_memalign() has been available since POSIX.1-2001 and is widely supported. The function signature and behavior are equivalent for the use case in alloc_aligned(), with the only difference being the return value (error code vs. NULL pointer).

The fix has been tested and resolves the crash during index construction when using tcmalloc.

Replace aligned_alloc() with posix_memalign() to fix crashes when using
tcmalloc. The issue occurs because aligned_alloc() uses glibc's allocator
while tcmalloc replaces free(), causing "invalid pointer" errors when freeing
memory.

posix_memalign() is more compatible with tcmalloc and other memory allocators,
as it properly integrates with the malloc/free interface that tcmalloc
intercepts.

Changes:
- Add #include <stdlib.h> for posix_memalign()
- Replace aligned_alloc() with posix_memalign() in alloc_aligned()
- Add proper error handling for posix_memalign() return value

This fixes crashes during index construction when InMemDataStore objects
are destructed after saving the index.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates DiskANN’s aligned allocation helper to avoid crashes when running with tcmalloc by replacing aligned_alloc() with posix_memalign() on non-Windows platforms.

Changes:

  • Include <stdlib.h> to declare posix_memalign().
  • Switch alloc_aligned() (non-Windows) from aligned_alloc() to posix_memalign().
  • Add explicit error handling for the posix_memalign() return code.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +263 to +269
// Use posix_memalign instead of aligned_alloc for tcmalloc compatibility.
// posix_memalign is more compatible with tcmalloc and avoids "invalid pointer" errors
// when freeing memory allocated with aligned_alloc and freed with tcmalloc's free().
int ret = posix_memalign(ptr, align, size);
if (ret != 0)
{
*ptr = nullptr;
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

posix_memalign requires align to be a power-of-two and a multiple of sizeof(void*) (and typically >= sizeof(void*)). There are existing call sites that pass align = 1 (e.g., PQDataStore allocates _quantized_data with align=1), which will now fail with EINVAL and terminate. Consider special-casing small/invalid alignments (e.g., treat align < sizeof(void*) as sizeof(void*), or fall back to malloc when no special alignment is needed), and/or explicitly validate align and surface a clearer error than "Memory Allocation Failed" for EINVAL.

Suggested change
// Use posix_memalign instead of aligned_alloc for tcmalloc compatibility.
// posix_memalign is more compatible with tcmalloc and avoids "invalid pointer" errors
// when freeing memory allocated with aligned_alloc and freed with tcmalloc's free().
int ret = posix_memalign(ptr, align, size);
if (ret != 0)
{
*ptr = nullptr;
// Normalize and validate alignment for posix_memalign.
// posix_memalign requires that the alignment be a power of two and
// a multiple of sizeof(void*), and typically at least sizeof(void*).
size_t effective_align = align;
if (effective_align < sizeof(void *))
effective_align = sizeof(void *);
bool align_is_valid = (effective_align != 0) &&
((effective_align & (effective_align - 1)) == 0) &&
(effective_align % sizeof(void *) == 0);
if (!align_is_valid)
{
std::stringstream stream;
stream << "Invalid alignment (" << align
<< ") requested for aligned allocation. Alignment must be a power-of-two "
<< "multiple of " << sizeof(void *) << ".";
print_error_and_terminate(stream);
}
// Use posix_memalign instead of aligned_alloc for tcmalloc compatibility.
// posix_memalign is more compatible with tcmalloc and avoids "invalid pointer" errors
// when freeing memory allocated with aligned_alloc and freed with tcmalloc's free().
int ret = posix_memalign(ptr, effective_align, size);
if (ret != 0)
{
*ptr = nullptr;
if (ret == EINVAL)
{
std::stringstream stream;
stream << "posix_memalign failed with EINVAL for alignment " << align
<< " (effective alignment " << effective_align
<< "). Alignment must be a power-of-two multiple of " << sizeof(void *) << ".";
print_error_and_terminate(stream);
}

Copilot uses AI. Check for mistakes.
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.

2 participants