Boost C++ Libraries: Ticket #7861: lockfree freelist doesn't destruct objects until its destructor is called https://svn.boost.org/trac10/ticket/7861 <p> This code demonstrates the problem: </p> <pre class="wiki"> $ cat leak.cc #include &lt;boost/lockfree/queue.hpp&gt; #include &lt;memory&gt; #include &lt;vector&gt; #include &lt;thread&gt; #include &lt;atomic&gt; static std::atomic&lt;uint64_t&gt; count{0}; struct LeakFinder { LeakFinder() { ++count; } ~LeakFinder() { --count; } }; static boost::lockfree::queue&lt;std::shared_ptr&lt;LeakFinder&gt;&gt; queue(128); static bool done = false; void producer() { for (int i=0; i&lt;1; ++i) { queue.push(std::make_shared&lt;LeakFinder&gt;()); } } void consumer() { while (!done) { std::shared_ptr&lt;LeakFinder&gt; v; while (queue.pop(v)); } } int main() { std::vector&lt;std::thread&gt; producer_threads; std::vector&lt;std::thread&gt; consumer_threads; for (int i=0; i&lt;1; ++i) { producer_threads.emplace_back(producer); } for (int i=0; i&lt;1; ++i) { consumer_threads.emplace_back(producer); } for (auto &amp;t : producer_threads) { t.join(); } done = true; for (auto &amp;t : consumer_threads) { t.join(); } std::cout &lt;&lt; "leak: " &lt;&lt; count &lt;&lt; "\n"; } </pre><pre class="wiki"> $ g++ -ggdb -std=c++11 -pthread -I/home/jason/boost-1.53/include leak.cc $ ./a.out leak: 2 </pre><p> I think destructors should be called when memory is added to freelist. </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/7861 Trac 1.4.3 timblechmann Mon, 07 Jan 2013 09:05:32 GMT status changed; resolution set https://svn.boost.org/trac10/ticket/7861#comment:1 https://svn.boost.org/trac10/ticket/7861#comment:1 <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> destructors <strong>are</strong> called when nodes are pushed to the freelist. however your reproducer has 3 problems: </p> <ul><li>there is no guarantee that the consumers will pop() all elements </li><li>the queue type does not have a trivial assignment </li><li>the queue type does not have a trivial destructor </li></ul><p> feel free to reopen, if you think it is not a problem in your code. </p> Ticket anonymous Mon, 07 Jan 2013 10:12:52 GMT status changed; resolution deleted https://svn.boost.org/trac10/ticket/7861#comment:2 https://svn.boost.org/trac10/ticket/7861#comment:2 <ul> <li><strong>status</strong> <span class="trac-field-old">closed</span> → <span class="trac-field-new">reopened</span> </li> <li><strong>resolution</strong> <span class="trac-field-deleted">invalid</span> </li> </ul> <p> Reopening because I've got a few questions about your response. True my code had a race condition, but the original code I was trying to distill did not. Here is a version without a race condition that still has the leak: </p> <pre class="wiki">#include &lt;boost/lockfree/queue.hpp&gt; #include &lt;memory&gt; #include &lt;vector&gt; #include &lt;thread&gt; #include &lt;atomic&gt; static std::atomic&lt;uint64_t&gt; count{0}; struct LeakFinder { LeakFinder() { ++count; } ~LeakFinder() { --count; } }; static boost::lockfree::queue&lt;std::shared_ptr&lt;LeakFinder&gt;&gt; queue(128); static bool done = false; void producer() { for (int i=0; i&lt;1; ++i) { queue.push(std::make_shared&lt;LeakFinder&gt;()); } } int main() { std::vector&lt;std::thread&gt; producer_threads; for (int i=0; i&lt;1; ++i) { producer_threads.emplace_back(producer); } for (auto &amp;t : producer_threads) { t.join(); } { std::shared_ptr&lt;LeakFinder&gt; v; while (queue.pop(v)); } std::cout &lt;&lt; "leak: " &lt;&lt; count &lt;&lt; "\n"; } </pre><p> I didn't see anything in the documentation that says queue can only store POD types with trivial assignment and destructor. If that is the case it should be noted and use static_assert to make it a compile time error. </p> Ticket timblechmann Mon, 07 Jan 2013 10:19:45 GMT <link>https://svn.boost.org/trac10/ticket/7861#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/7861#comment:3</guid> <description> <p> check the requirements: <a class="ext-link" href="http://boost-sandbox.sourceforge.net/doc/html/boost/lockfree/queue.html"><span class="icon">​</span>http://boost-sandbox.sourceforge.net/doc/html/boost/lockfree/queue.html</a> </p> <p> you can use non-pod types, but as stated above, trivial destructors and assignments are required (by the algorithm). </p> <p> maybe it can be statically asserted these days, when i wrote this code, the type traits were not widely implemented. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>anonymous</dc:creator> <pubDate>Mon, 07 Jan 2013 10:35:03 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/7861#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/7861#comment:4</guid> <description> <p> I see, I missed that part in the docs. Thanks for being patient. boost has wrappers to make type traits and static assert work on supported compilers: </p> <p> <a href="http://www.boost.org/doc/libs/1_52_0/libs/type_traits/doc/html/boost_typetraits/reference/is_pod.html">http://www.boost.org/doc/libs/1_52_0/libs/type_traits/doc/html/boost_typetraits/reference/is_pod.html</a> <a href="http://www.boost.org/doc/libs/1_52_0/doc/html/boost_staticassert.html">http://www.boost.org/doc/libs/1_52_0/doc/html/boost_staticassert.html</a> </p> <p> What about the algorithm requires POD? It seems like it is doing everything that would be needed to support non-POD types. And indeed the destructor is called for all but 1 of the items, which sticks around until the queue goes out of scope at which point it too is destructed. Which is to say the program is valgrind clean: </p> <pre class="wiki">==38928== HEAP SUMMARY: ==38928== in use at exit: 0 bytes in 0 blocks ==38928== total heap usage: 147 allocs, 147 frees, 9,088 bytes allocated ==38928== ==38928== All heap blocks were freed -- no leaks are possible </pre><p> Also, if only trivial POD types are supported, why even call the destructor? trivial destructors don't do anything. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>timblechmann</dc:creator> <pubDate>Mon, 07 Jan 2013 10:44:22 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/7861#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/7861#comment:5</guid> <description> <p> a) check the michael/scott paper. in short: the content is copied before it is known to be valid. likewise the invalid objects can be destructed. </p> <p> b) the queue uses a dummy node, probably the item you see when the queue is destructed </p> <p> c) the freelist is also used by the stack, which does not have the requirements for assignment and destructor </p> </description> <category>Ticket</category> </item> <item> <dc:creator>anonymous</dc:creator> <pubDate>Mon, 07 Jan 2013 11:18:12 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/7861#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/7861#comment:6</guid> <description> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/7861#comment:5" title="Comment 5">timblechmann</a>: </p> <blockquote class="citation"> <p> a) check the michael/scott paper. in short: the content is copied before it is known to be valid. likewise the invalid objects can be destructed. </p> </blockquote> <p> I see, that makes sense. The value is copied before the CAS, but only used if the CAS succeeds, so if it had a non-trivial destructor or assignment it could be in some invalid state. I'm probably just getting lucky with std::shared_ptr since it is meant to be thread-safe. </p> <p> You probably can't sneak this into boost, but I think most types actually would work if you ignored the rules that C++ objects aren't relocatable because most are, see: </p> <p> github.com/facebook/folly/blob/master/folly/docs/FBVector.md#object-relocation </p> <p> Also what I said before was wrong, is_pod is too strict, and you want these: </p> <p> <a href="http://www.boost.org/doc/libs/1_52_0/libs/type_traits/doc/html/boost_typetraits/reference/has_trivial_copy.html">http://www.boost.org/doc/libs/1_52_0/libs/type_traits/doc/html/boost_typetraits/reference/has_trivial_copy.html</a> <a href="http://www.boost.org/doc/libs/1_52_0/libs/type_traits/doc/html/boost_typetraits/reference/has_trivial_destructor.html">http://www.boost.org/doc/libs/1_52_0/libs/type_traits/doc/html/boost_typetraits/reference/has_trivial_destructor.html</a> </p> <blockquote class="citation"> <p> b) the queue uses a dummy node, probably the item you see when the queue is destructed </p> </blockquote> <p> I considered that, but the default constructor for shared_ptr wouldn't create my <a class="missing wiki">LeakFinder</a> object. If I modify the program to only put 1 item in the queue and then break on the constructor of <a class="missing wiki">LeakFinder</a> in gdb, the only one created is the one I push to the queue. </p> <blockquote class="citation"> <p> c) the freelist is also used by the stack, which does not have the requirements for assignment and destructor </p> </blockquote> <p> In any case I'm satisfied. Thanks for humoring me. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>timblechmann</dc:creator> <pubDate>Sun, 03 Mar 2013 11:49:29 GMT</pubDate> <title>status changed; resolution set https://svn.boost.org/trac10/ticket/7861#comment:7 https://svn.boost.org/trac10/ticket/7861#comment:7 <ul> <li><strong>status</strong> <span class="trac-field-old">reopened</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">fixed</span> </li> </ul> <p> closing, as neither valgrind nor totalview actually detect a memory leak. we also have static assertions now. </p> Ticket