Commit 9b2c712d authored by Dan Melnic's avatar Dan Melnic Committed by Facebook Github Bot

Fix mem leak due to observer not being freed

Summary: Fix mem leak due to observer not being freed

Reviewed By: yfeldblum

Differential Revision: D16234386

fbshipit-source-id: 54ea9e904cf6cde0e24edf1511c2e3b46a58376f
parent c4f04f9a
...@@ -130,6 +130,22 @@ IOBuf::SharedInfo::SharedInfo(FreeFunction fn, void* arg, bool hfs) ...@@ -130,6 +130,22 @@ IOBuf::SharedInfo::SharedInfo(FreeFunction fn, void* arg, bool hfs)
refcount.store(1, std::memory_order_relaxed); refcount.store(1, std::memory_order_relaxed);
} }
void IOBuf::SharedInfo::invokeAndDeleteEachObserver(
SharedInfoObserverEntryBase* observerListHead,
ObserverCb cb) noexcept {
if (observerListHead && cb) {
// break the chain
observerListHead->prev->next = nullptr;
auto* entry = observerListHead;
while (entry) {
auto* tmp = entry->next;
cb(*entry);
delete entry;
entry = tmp;
}
}
}
void IOBuf::SharedInfo::releaseStorage(SharedInfo* info) noexcept { void IOBuf::SharedInfo::releaseStorage(SharedInfo* info) noexcept {
if (info->useHeapFullStorage) { if (info->useHeapFullStorage) {
auto* storageAddr = auto* storageAddr =
...@@ -962,17 +978,8 @@ void IOBuf::freeExtBuffer() noexcept { ...@@ -962,17 +978,8 @@ void IOBuf::freeExtBuffer() noexcept {
free(buf_); free(buf_);
} }
if (observerListHead) { SharedInfo::invokeAndDeleteEachObserver(
// break the chain observerListHead, [](auto& entry) { entry.afterFreeExtBuffer(); });
observerListHead->prev->next = nullptr;
auto* entry = observerListHead;
while (entry) {
auto* tmp = entry->next;
entry->afterFreeExtBuffer();
delete entry;
entry = tmp;
}
}
} }
void IOBuf::allocExtBuffer( void IOBuf::allocExtBuffer(
...@@ -1017,9 +1024,10 @@ void IOBuf::initExtBuffer( ...@@ -1017,9 +1024,10 @@ void IOBuf::initExtBuffer(
} }
fbstring IOBuf::moveToFbString() { fbstring IOBuf::moveToFbString() {
// we need to save useHeapFullStorage since // we need to save useHeapFullStorage and the observerListHead since
// sharedInfo() may not be valid after fbstring str // sharedInfo() may not be valid after fbstring str
bool useHeapFullStorage = false; bool useHeapFullStorage = false;
SharedInfoObserverEntryBase* observerListHead = nullptr;
// malloc-allocated buffers are just fine, everything else needs // malloc-allocated buffers are just fine, everything else needs
// to be turned into one. // to be turned into one.
if (!sharedInfo() || // user owned, not ours to give up if (!sharedInfo() || // user owned, not ours to give up
...@@ -1032,9 +1040,19 @@ fbstring IOBuf::moveToFbString() { ...@@ -1032,9 +1040,19 @@ fbstring IOBuf::moveToFbString() {
// to reallocate; we need 1 byte for NUL terminator. // to reallocate; we need 1 byte for NUL terminator.
coalesceAndReallocate(0, computeChainDataLength(), this, 1); coalesceAndReallocate(0, computeChainDataLength(), this, 1);
} else { } else {
// if we do not call coalesceAndReallocate auto* info = sharedInfo();
// we might need to call SharedInfo::releaseStorage() if (info) {
useHeapFullStorage = sharedInfo() && sharedInfo()->useHeapFullStorage; // if we do not call coalesceAndReallocate
// we might need to call SharedInfo::releaseStorage()
// and/or SharedInfo::invokeAndDeleteEachObserver()
useHeapFullStorage = info->useHeapFullStorage;
// save the observerListHead
// the coalesceAndReallocate path will call
// decrementRefcount and freeExtBuffer if needed
// so the observer lis notification is needed here
observerListHead = info->observerListHead;
info->observerListHead = nullptr;
}
} }
// Ensure NUL terminated // Ensure NUL terminated
...@@ -1045,6 +1063,9 @@ fbstring IOBuf::moveToFbString() { ...@@ -1045,6 +1063,9 @@ fbstring IOBuf::moveToFbString() {
capacity(), capacity(),
AcquireMallocatedString()); AcquireMallocatedString());
SharedInfo::invokeAndDeleteEachObserver(
observerListHead, [](auto& entry) { entry.afterReleaseExtBuffer(); });
if (flags() & kFlagFreeSharedInfo) { if (flags() & kFlagFreeSharedInfo) {
delete sharedInfo(); delete sharedInfo();
} else { } else {
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include <folly/FBString.h> #include <folly/FBString.h>
#include <folly/FBVector.h> #include <folly/FBVector.h>
#include <folly/Function.h>
#include <folly/Portability.h> #include <folly/Portability.h>
#include <folly/Range.h> #include <folly/Range.h>
#include <folly/detail/Iterators.h> #include <folly/detail/Iterators.h>
...@@ -1419,6 +1420,7 @@ class IOBuf { ...@@ -1419,6 +1420,7 @@ class IOBuf {
virtual ~SharedInfoObserverEntryBase() = default; virtual ~SharedInfoObserverEntryBase() = default;
virtual void afterFreeExtBuffer() const noexcept = 0; virtual void afterFreeExtBuffer() const noexcept = 0;
virtual void afterReleaseExtBuffer() const noexcept = 0;
}; };
template <typename Observer> template <typename Observer>
...@@ -1432,6 +1434,10 @@ class IOBuf { ...@@ -1432,6 +1434,10 @@ class IOBuf {
void afterFreeExtBuffer() const noexcept final { void afterFreeExtBuffer() const noexcept final {
observer.afterFreeExtBuffer(); observer.afterFreeExtBuffer();
} }
void afterReleaseExtBuffer() const noexcept final {
observer.afterReleaseExtBuffer();
}
}; };
struct SharedInfo { struct SharedInfo {
...@@ -1440,6 +1446,11 @@ class IOBuf { ...@@ -1440,6 +1446,11 @@ class IOBuf {
static void releaseStorage(SharedInfo* info) noexcept; static void releaseStorage(SharedInfo* info) noexcept;
using ObserverCb = folly::FunctionRef<void(SharedInfoObserverEntryBase&)>;
static void invokeAndDeleteEachObserver(
SharedInfoObserverEntryBase* observerListHead,
ObserverCb cb) noexcept;
// A pointer to a function to call to free the buffer when the refcount // A pointer to a function to call to free the buffer when the refcount
// hits 0. If this is null, free() will be used instead. // hits 0. If this is null, free() will be used instead.
FreeFunction freeFn; FreeFunction freeFn;
......
...@@ -1609,49 +1609,71 @@ TEST(IOBuf, FreeFn) { ...@@ -1609,49 +1609,71 @@ TEST(IOBuf, FreeFn) {
class IOBufFreeObserver { class IOBufFreeObserver {
public: public:
using Func = std::function<void()>; using Func = std::function<void()>;
explicit IOBufFreeObserver(Func&& func) : func_(std::move(func)) {} explicit IOBufFreeObserver(Func&& freeFunc, Func&& releaseFunc)
: freeFunc_(std::move(freeFunc)),
releaseFunc_(std::move(releaseFunc)) {}
void afterFreeExtBuffer() const noexcept { void afterFreeExtBuffer() const noexcept {
func_(); freeFunc_();
}
void afterReleaseExtBuffer() const noexcept {
releaseFunc_();
} }
private: private:
Func func_; Func freeFunc_;
Func releaseFunc_;
}; };
int freeVal = 0;
int releaseVal = 0;
IOBufFreeObserver observer(
[&freeVal]() { freeVal += 1; }, [&releaseVal]() { releaseVal += 1; });
// no observers // no observers
{ unique_ptr<IOBuf> iobuf(IOBuf::create(64)); } { unique_ptr<IOBuf> iobuf(IOBuf::create(64)); }
int val = 0;
// one observer // one observer
{ {
unique_ptr<IOBuf> iobuf(IOBuf::create(64)); unique_ptr<IOBuf> iobuf(IOBuf::create(64));
EXPECT_TRUE(iobuf->appendSharedInfoObserver(IOBufFreeObserver([&val]() { EXPECT_TRUE(iobuf->appendSharedInfoObserver(observer));
EXPECT_EQ(val, 0);
val += 1;
})));
} }
EXPECT_EQ(val, 1); EXPECT_EQ(freeVal, 1);
EXPECT_EQ(releaseVal, 0);
val = 0; freeVal = 0;
// multiple observers releaseVal = 0;
// reserve
{ {
unique_ptr<IOBuf> iobuf(IOBuf::create(64)); unique_ptr<IOBuf> iobuf(IOBuf::create(64));
EXPECT_TRUE(iobuf->appendSharedInfoObserver(observer));
iobuf->reserve(0, iobuf->capacity() + 1024);
EXPECT_EQ(freeVal, 1);
EXPECT_EQ(releaseVal, 0);
}
EXPECT_TRUE(iobuf->appendSharedInfoObserver(IOBufFreeObserver([&val]() { freeVal = 0;
EXPECT_EQ(val, 0); releaseVal = 0;
val += 1; // one observer - call moveToFbString
}))); {
unique_ptr<IOBuf> iobuf(IOBuf::create(64 * 1024));
EXPECT_TRUE(iobuf->appendSharedInfoObserver(IOBufFreeObserver([&val]() { EXPECT_TRUE(iobuf->appendSharedInfoObserver(observer));
EXPECT_EQ(val, 1); auto str = iobuf->moveToFbString().toStdString();
val += 20; }
}))); EXPECT_EQ(freeVal, 0);
EXPECT_EQ(releaseVal, 1);
EXPECT_TRUE(iobuf->appendSharedInfoObserver(IOBufFreeObserver([&val]() { freeVal = 0;
EXPECT_EQ(val, 21); releaseVal = 0;
val += 300; // multiple observers
}))); {
unique_ptr<IOBuf> iobuf(IOBuf::create(64));
for (size_t i = 0; i < 3; i++) {
EXPECT_TRUE(iobuf->appendSharedInfoObserver(observer));
}
} }
EXPECT_EQ(val, 321); EXPECT_EQ(freeVal, 3);
EXPECT_EQ(releaseVal, 0);
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment