Boost C++ Libraries: Ticket #11638: stored weak_ptr of track object will prevent the track object memory being freed https://svn.boost.org/trac10/ticket/11638 <p> boost signals2 will store weak_ptr of a tracked object, if the tracked object is allocated by std::make_shared() or std::allocate_shared(), after it is destructed, the memory occupied by it will not be freed because the weak_ptr is still hold by the signal connection. Only when the signal is re-triggered will the memory being freed. </p> <p> there's another bug here, after the tracked object is destructed, signals2::connection.connected() will return false, and connection.disconnect() seem does nothing when connected is false, and leave the tracked object memory leaked. </p> <p> below is some test cases </p> <pre class="wiki">#include &lt;iostream&gt; // std::cout #include &lt;memory&gt; // std::shared_ptr #include &lt;functional&gt; // std::function #include &lt;boost/signals2.hpp&gt; class TrackObject { public: TrackObject() { std::cout &lt;&lt; "construct TrackObject" &lt;&lt; std::endl; } ~TrackObject() { std::cout &lt;&lt; "destruct TrackObject" &lt;&lt; std::endl; } static void* operator new(std::size_t sz) { void* p = ::operator new(sz); std::cout &lt;&lt; "custom new for size " &lt;&lt; sz &lt;&lt; ", got p " &lt;&lt; p &lt;&lt; std::endl; return p; } static void operator delete(void* p) { std::cout &lt;&lt; "custom delete for p " &lt;&lt; p &lt;&lt; std::endl; return ::operator delete(p); } static void* operator new[](std::size_t sz) { std::cout &lt;&lt; "custom new for size " &lt;&lt; sz &lt;&lt; std::endl; return ::operator new(sz); } private: char data[50]; }; class SignalEmitter { public: using SignalType = boost::signals2::signal&lt;void()&gt;; SignalEmitter() = default; virtual ~SignalEmitter() = default; boost::signals2::connection registerHandler(std::function&lt;void()&gt; func, std::shared_ptr&lt;TrackObject&gt; trackobj) { SignalType::slot_type handler(func); return signal_.connect(handler.track_foreign(trackobj)); } void trigger() { signal_(); } private: SignalType signal_; }; template &lt;class T&gt; struct custom_allocator { typedef T value_type; custom_allocator() noexcept {} template &lt;class U&gt; custom_allocator(const custom_allocator&lt;U&gt;&amp;) noexcept {} T* allocate(std::size_t n) { std::cout &lt;&lt; "allocate " &lt;&lt; n &lt;&lt; " objects, object size is " &lt;&lt; sizeof(T) &lt;&lt; std::endl; return static_cast&lt;T*&gt;(::operator new(n * sizeof(T))); } void deallocate(T* p, std::size_t n) { std::cout &lt;&lt; "deallocate " &lt;&lt; n &lt;&lt; " objects" &lt;&lt; std::endl; ::operator delete(p); } }; int main() { std::cout &lt;&lt; "---TestCase1: check wp will hold the memory---" &lt;&lt; std::endl; custom_allocator&lt;TrackObject&gt; alloc; { std::weak_ptr&lt;TrackObject&gt; wp4; { auto p4 = std::allocate_shared&lt;TrackObject&gt;(alloc); wp4 = p4; } std::cout &lt;&lt; "---TestCase1: expect to free the memory before this line, " &lt;&lt; "but wp prevents it from being freed ---" &lt;&lt; std::endl; } std::cout &lt;&lt; "---TestCase1: the memory is only freed when weak_ptr is out of scope" &lt;&lt; ", before this line ---" &lt;&lt; std::endl; std::cout &lt;&lt; "---TestCase2: check signal2 will prevent memory from being freed---" &lt;&lt; std::endl; { SignalEmitter signal_emiiter; boost::signals2::connection conn; { auto trackobj = std::allocate_shared&lt;TrackObject&gt;(alloc); conn = signal_emiiter.registerHandler( []() { std::cout &lt;&lt; "signal triggered" &lt;&lt; std::endl; }, trackobj); std::cout &lt;&lt; "---TestCase2: after registerHandler: conn.connected: " &lt;&lt; conn.connected() &lt;&lt; std::endl; signal_emiiter.trigger(); } std::cout &lt;&lt; "---TestCase2: should free trackobj here, but not---\n"; std::cout &lt;&lt; "---TestCase2: connected: " &lt;&lt; conn.connected() &lt;&lt; std::endl; std::cout &lt;&lt; "---TestCase2: connected() shows that it's already disconnected" &lt;&lt; std::endl; conn.disconnect(); std::cout &lt;&lt; "--TestCase2: so, conn.disconnect() does nothing, " "the memory is still not freed" &lt;&lt; std::endl; signal_emiiter.trigger(); std::cout &lt;&lt; "---TestCase2: only re-trigger will free memory before this line ---" &lt;&lt; std::endl; } std::cout &lt;&lt; "---TestCase3: check non-make_shared workaround---" &lt;&lt; std::endl; { SignalEmitter signal_emiiter; { std::shared_ptr&lt;TrackObject&gt; trackobj(new TrackObject()); signal_emiiter.registerHandler( [](){ std::cout &lt;&lt; "signal triggered" &lt;&lt; std::endl; }, trackobj); signal_emiiter.trigger(); } std::cout &lt;&lt; "---TestCase3: without use make_shared, memory will be " "freed before this line---" &lt;&lt; std::endl; signal_emiiter.trigger(); std::cout &lt;&lt; "---TestCase3: re-trigger do nothing ---" &lt;&lt; std::endl; } } </pre> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/11638 Trac 1.4.3 Lutts Cao <lutts.cao@…> Thu, 10 Sep 2015 08:30:45 GMT <link>https://svn.boost.org/trac10/ticket/11638#comment:1 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/11638#comment:1</guid> <description> <p> nolock_cleanup_connections_from() is only called by connect() and operator(), I think there's need to add an explicit API to boost::signals2::signal to let the user explicitly cleanup expired connections </p> </description> <category>Ticket</category> </item> <item> <dc:creator>Frank Mori Hess</dc:creator> <pubDate>Thu, 10 Sep 2015 17:24:51 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/11638#comment:2 https://svn.boost.org/trac10/ticket/11638#comment:2 <ul> <li><strong>status</strong> <span class="trac-field-old">new</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">invalid</span> </li> </ul> <p> You may be happier with signals2 from boost 1.59. It deletes slot objects inside the signal (and their tracked weak_ptrs) ASAP after slot disconnection, rather than putting it off for the signal to garbage collect at some later point. </p> <p> While weak_ptr causing a delay in the deallocation of memory for shared_ptr created with allocate_shared is interesting, it is an issue with allocate_shared, not signals2. My guess is allocate_shared allocates a single block of memory to contain both the pointed-at object and the shared reference count, in order to minimize the number of memory allocations. Thus the memory cannot be freed until both the pointed-at object and the shared reference count are destroyed. Since the weak_ptr has a handle to the shared reference count, it delays the deallocation. </p> <p> I'm closing this ticket, if there is still a need for an added cleanup API when using signals2 from 1.59, please open a separate ticket for that feature request. </p> Ticket