Boost C++ Libraries: Ticket #10056: boost::promise runs arbitrary code while holding mutex lock https://svn.boost.org/trac10/ticket/10056 <p> This deadlocks or aborts, depending on how pthreads handles re-locking a mutex in the same thread: </p> <pre class="wiki">#include &lt;boost/thread/future.hpp&gt; struct X { X() { } X(const X&amp;); }; boost::unique_future&lt;X&gt; fut; X::X(const X&amp;) { fut.wait_for(boost::chrono::milliseconds(1)); } int main() { boost::promise&lt;X&gt; p; fut = p.get_future(); p.set_value( X() ); } </pre><p> promise::set_value() owns a lock while invoking the copy constructor, which might have side-effects. </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/10056 Trac 1.4.3 viboes Sun, 18 May 2014 18:35:52 GMT owner, status changed https://svn.boost.org/trac10/ticket/10056#comment:1 https://svn.boost.org/trac10/ticket/10056#comment:1 <ul> <li><strong>owner</strong> changed from <span class="trac-author">Anthony Williams</span> to <span class="trac-author">viboes</span> </li> <li><strong>status</strong> <span class="trac-field-old">new</span> → <span class="trac-field-new">assigned</span> </li> </ul> <p> This example seems a little bit contrived to me. </p> <p> Should the library take care of this case? What the standard says? Have you tried it with some standard implementations? </p> Ticket Jonathan Wakely <jwakely.boost@…> Mon, 19 May 2014 13:31:16 GMT <link>https://svn.boost.org/trac10/ticket/10056#comment:2 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10056#comment:2</guid> <description> <p> It's a bit contrived, yes, but this is reduced to the minimum needed to show it. </p> <p> IMHO it's conceivable that the result's copy constructor would try to do something (e.g. logging or performance measurements) which might end up checking for readiness of something, which depends on the result we're initializing. </p> <p> The standard doesn't say anything about this, which I interpret to mean it's supposed to work. Otherwise I would expect the standard to place preconditions on promise::set_value(). Since I interpret it that way, GCC does not deadlock for code like this (no lock is held which the result object is copied). libc++ either deadlocks or aborts depending on the Pthreads implementation (attempting to lock a mutex in the thread that already owns it is undefined behaviour). </p> </description> <category>Ticket</category> </item> <item> <dc:creator>viboes</dc:creator> <pubDate>Fri, 30 May 2014 20:57:24 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10056#comment:3 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10056#comment:3</guid> <description> <p> How do you ensure that only one thread is doing the set_value without maintaining the lock? </p> </description> <category>Ticket</category> </item> <item> <dc:creator>anonymous</dc:creator> <pubDate>Fri, 30 May 2014 22:30:11 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10056#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10056#comment:4</guid> <description> <p> std::call_once </p> </description> <category>Ticket</category> </item> <item> <dc:creator>viboes</dc:creator> <pubDate>Fri, 30 May 2014 23:55:57 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10056#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10056#comment:5</guid> <description> <p> This would mean a severe refactoring. Doesn't this would mean a bottleneck on call_once? </p> <p> I would need sometime to digest the change. </p> </description> <category>Ticket</category> </item> <item> <author>jwakely.boost@…</author> <pubDate>Sat, 31 May 2014 00:38:06 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/10056#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/10056#comment:6</guid> <description> <p> call_once adds a bit of overhead, but is only a bottleneck if multiple threads are trying to make the shared state ready at the same time, and since all but the first are going to fail with an exception containing promise_already_satisfied it doesn't really matter if they're blocked on call_once. </p> <p> From memory, my design is roughly: </p> <pre class="wiki">void state::set(function&lt;unique_ptr&lt;Result&gt;()&gt; setter) { unique_lock&lt;mutex&gt; l(m_mutex, defer_lock); call_once(m_once, &amp;state::do_set, this, ref(setter), ref(l)); if (!l.owns_lock()) throw_promise_already_satisfied_err(); } void state::do_set(function&lt;unique_ptr&lt;Result&gt;()&gt;&amp; setter, unique_lock&lt;mutex&gt;&amp; l) { auto result = setter(); // run without lock held l.lock(); m_result.swap(result); // non-blocking } </pre><p> <code>Result</code> is a wrapper for the result, containing <code>exception_ptr</code> and <code>R</code> for a <code>future&lt;R&gt;</code>, or <code>exception_ptr</code> and <code>R*</code> for <code>future&lt;R&amp;&gt;</code>, or just <code>exception_ptr</code> for <code>future&lt;void&gt;</code>. The <code>setter</code> function creates a new <code>Result</code> and sets its members (possibly by running a deferred function or a packaged_task's callable) and any copy construction of <code>R</code> happens inside that setter, before the mutex is locked. </p> <p> The mutex is locked to transfer the <code>Result</code> into <code>m_result</code>, and also locked by waiting functions checking whether <code>m_result != nullptr</code>. </p> <p> I'm not sure that Boost's implementation should support my strange scenario - I'm still not certain my testcase is valid. No other implementations seem to support it, and maybe noone really cares if it works, so a severe refactoring probably isn't worth it. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>viboes</dc:creator> <pubDate>Thu, 05 Jun 2014 21:46:05 GMT</pubDate> <title>status changed; resolution set; milestone deleted https://svn.boost.org/trac10/ticket/10056#comment:7 https://svn.boost.org/trac10/ticket/10056#comment:7 <ul> <li><strong>status</strong> <span class="trac-field-old">assigned</span> → <span class="trac-field-new">closed</span> </li> <li><strong>resolution</strong> → <span class="trac-field-new">wontfix</span> </li> <li><strong>milestone</strong> <span class="trac-field-deleted">To Be Determined</span> </li> </ul> <p> Thanks for sharing this. I would close it for the time been as wontfix. We could reopen it if the case is found on user code. </p> Ticket