Skip to content

Are FutureTimeout and FutureCancellation Causing Memory Leaks? #2548

@lingbin

Description

@lingbin

Hi Folly team,

I am experiencing a significant memory leak in my application, which heavily uses folly::Future. After enabling AddressSanitizer (ASAN) with LeakSanitizer (LSan), I've identified that hundreds of thousands of folly::FutureTimeout and folly::FutureCancellation exception objects are being leaked.

I suspect this might be related to how Future::within() handles timeouts, but I am also aware this could be a usage issue in my own code, such as a circular reference. I would greatly appreciate your insights or guidance on this matter.

Problem Description

My application extensively uses Folly's Future timeout features to ensure responsiveness. These leaks are not random; they are always composed of the exception objects that Folly allocates internally to represent timeout or cancellation states.

LSan Reports

I have two distinct stack traces from LSan, one for folly::FutureTimeout and another for folly::FutureCancellation. The call stacks are nearly identical, differing only in the type of exception being created.

Here is the cleaned-up stack trace for the FutureTimeout leak:

Direct leak of 61085952 byte(s) in 424208 object(s) allocated from:
___interceptor_aligned_alloc
/data1/workspace/llvm-project-src/compiler-rt/lib/asan/asan_malloc_linux.cpp:113:3

??
??:0:0

std::exception_ptr folly::make_exception_ptr_with_fn::operator()<auto folly::make_exception_ptr_with_fn::make<folly::FutureTimeout, folly::FutureTimeou...
/data1/workspace/lingbin/xxx/thirdparty/installed/include/folly/lang/Exception.h:568:14

std::exception_ptr folly::make_exception_ptr_with_fn::operator()<folly::FutureTimeout, folly::FutureTimeout>(std::__1::in_place_type_t<folly::FutureTime...
/data1/workspace/lingbin/xxx/thirdparty/installed/include/folly/lang/Exception.h:579:12

folly::exception_wrapper::exception_wrapper<folly::FutureTimeout, folly::FutureTimeout>(folly::exception_wrapper::PrivateCtor, std::__1::in_place_type_...
/data1/workspace/lingbin/xxx/thirdparty/installed/include/folly/ExceptionWrapper-inl.h:69:12

... (stack continues) ...

void folly::futures::detail::FutureBase<folly::Unit>::raise<folly::FutureTimeout>(folly::FutureTimeout&&)
/data1/workspace/lingbin/xxx/thirdparty/installed/include/folly/futures/Future.h:368:11

folly::SemiFuture<folly::Unit> folly::SemiFuture<folly::Unit>::within<folly::FutureTimeout>(std::__1::chrono::duration<long long, std::__1::ratio<1l, 10...
/data1/workspace/lingbin/xxx/thirdparty/installed/include/folly/futures/Future-inl.h:2160:31

... (stack continues to thread pool execution) ...

And here is the stack trace for the FutureCancellation leak:

Direct leak of 100511280 byte(s) in 697995 object(s) allocated from:
___interceptor_aligned_alloc
/data1/workspace/llvm-project-src/compiler-rt/lib/asan/asan_malloc_linux.cpp:113:3

??
??:0:0

std::exception_ptr folly::make_exception_ptr_with_fn::operator()<auto folly::make_exception_ptr_with_fn::make<folly::FutureCancellation, folly::FutureC...
/data1/workspace/lingbin/xxx/thirdparty/installed/include/folly/lang/Exception.h:568:14

... (stack continues) ...

void folly::futures::detail::FutureBase<folly::Unit>::raise<folly::FutureCancellation>(folly::FutureCancellation&&)
/data1/workspace/lingbin/xxx/thirdparty/installed/include/folly/futures/Future.h:368:11

folly::futures::detail::FutureBase<folly::Unit>::cancel()
/data1/workspace/lingbin/xxx/thirdparty/installed/include/folly/futures/Future.h:374:19

folly::SemiFuture<folly::Unit> folly::SemiFuture<folly::Unit>::within<folly::FutureTimeout>(std::__1::chrono::duration<long long, std::__1::ratio<1l, ...
/data1/workspace/lingbin/xxx/thirdparty/installed/include/folly/futures/Future-inl.h:2140:24

... (stack continues to thread pool execution) ...

Question

My understanding is that Folly, as a library, is responsible for the lifecycle of the objects it creates. When a timeout occurs, Folly allocates an exception object to represent this state. If the corresponding Future is properly destructed without its exception being handled, I would expect a std::terminate call, not a memory leak.

The presence of a leak strongly suggests that the Future objects themselves are not being destructed.

While I am actively investigating my codebase for such patterns, my question to the community is:

Is there any known scenario or subtle behavior within Folly's Future::within or its continuation handling that could lead to such a leak?

Any advice on how to best debug such Future-related circular references would also be highly appreciated.

Thanks for your time and for this great library.

Environment:

  • Folly version: 2024.06.10.00
  • Compiler: LLVM 17.0.6
  • Platform: Linux
  • Using __lsan_do_recoverable_leak_check to report leaks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions