Boost C++ Libraries: Ticket #5516: Upgrade lock is not acquired when previous upgrade lock releases if another read lock is present https://svn.boost.org/trac10/ticket/5516 <p> Thread 1 acquires a read lock on a shared mutex<br /> Thread 2 acquires an upgrade lock on the shared mutex<br /> Thread 3 tries to acquire an upgrade lock on the shared mutex, waits for it<br /> Thread 2 releases the lock </p> <p> At this point, I would expect thread 3 to acquire the lock, but it does not do it until thread 1 releases its read lock. </p> <p> This seems to be caused by the "shared_mutex ::unlock_upgrade()" method in thread/pthread/shared_mutex.hpp, that only calls "release_waiters()" if it is the last reader, ignoring that another upgrade lock might be waiting to take the lock. </p> <p> I suppose it could be fixed by maintaining a count of the number of threads waiting for an upgrade lock, and to signal the shared condition if this is non-zero. </p> <p> Here is an example program: </p> <pre class="wiki">#include &lt;iostream&gt; #include &lt;boost/thread.hpp&gt; class Test { public: void m1() { std::cout &lt;&lt; "m1 - Trying to take an shared lock" &lt;&lt; std::endl; boost::shared_lock&lt;boost::shared_mutex&gt; lock(_mutex); std::cout &lt;&lt; "m1 - Took a shared lock" &lt;&lt; std::endl; sleep(5); std::cout &lt;&lt; "m1 - Released the lock" &lt;&lt; std::endl; } void m2() { sleep(1); std::cout &lt;&lt; "m2 - Trying to take an upgradable lock" &lt;&lt; std::endl; boost::upgrade_lock&lt;boost::shared_mutex&gt; lock(_mutex); std::cout &lt;&lt; "m2 - Took an upgradable lock" &lt;&lt; std::endl; sleep(1); std::cout &lt;&lt; "m2 - Released an upgradable lock" &lt;&lt; std::endl; } void m3() { sleep(2); std::cout &lt;&lt; "m3 - Trying to take an upgradable lock" &lt;&lt; std::endl; boost::upgrade_lock&lt;boost::shared_mutex&gt; lock(_mutex); std::cout &lt;&lt; "m3 - Took an upgradable lock" &lt;&lt; std::endl; std::cout &lt;&lt; "m3 - Releasing locks" &lt;&lt; std::endl; } void run() { boost::thread t1(&amp;Test::m1, this); boost::thread t2(&amp;Test::m2, this); m3(); t1.join(); t2.join(); } private: boost::shared_mutex _mutex; }; int main() { Test test; test.run(); return 0; } </pre><p> Results: </p> <pre class="wiki">m1 - Trying to take an shared lock m1 - Took a shared lock m2 - Trying to take an upgradable lock m2 - Took an upgradable lock m3 - Trying to take an upgradable lock m2 - Released an upgradable lock m1 - Released the lock m3 - Took an upgradable lock m3 - Releasing locks </pre><p> Expected: </p> <pre class="wiki">m1 - Trying to take an shared lock m1 - Took a shared lock m2 - Trying to take an upgradable lock m2 - Took an upgradable lock m3 - Trying to take an upgradable lock m2 - Released an upgradable lock m3 - Took an upgradable lock m3 - Releasing locks m1 - Released the lock </pre><p> Tested in Boost 1.42, visually checked that Trunk would have the same behaviour. </p> en-us Boost C++ Libraries /htdocs/site/boost.png https://svn.boost.org/trac10/ticket/5516 Trac 1.4.3 viboes Wed, 11 May 2011 06:17:53 GMT component changed https://svn.boost.org/trac10/ticket/5516#comment:1 https://svn.boost.org/trac10/ticket/5516#comment:1 <ul> <li><strong>component</strong> <span class="trac-field-old">threads</span> → <span class="trac-field-new">thread</span> </li> </ul> Ticket viboes Wed, 07 Dec 2011 17:59:04 GMT owner, status changed; cc set https://svn.boost.org/trac10/ticket/5516#comment:2 https://svn.boost.org/trac10/ticket/5516#comment:2 <ul> <li><strong>cc</strong> <span class="trac-author">viboes</span> added </li> <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> It seems to me that this is by definition of the provided feature. </p> <p> From the documentation: "This is an extension to the multiple-reader / single-write model provided by the <a class="missing wiki">SharedLockable</a> concept: a single thread may have upgradable ownership at the same time as others have shared ownership. The thread with upgradable ownership may at any time attempt to upgrade that ownership to exclusive ownership. If no other threads have shared ownership, the upgrade is completed immediately, and the thread now has exclusive ownership, which must be relinquished by a call to unlock(), just as if it had been acquired by a call to lock(). </p> <p> If a thread with upgradable ownership tries to upgrade whilst other threads have shared ownership, the attempt will fail and the thread will block until exclusive ownership can be acquired. " Of course the implementation could give priority to writers, but this should be implemented in another class. </p> <p> What is then wrong? </p> <p> Moved to Support request until resolution clarified. </p> Ticket viboes Wed, 07 Dec 2011 22:33:07 GMT type changed https://svn.boost.org/trac10/ticket/5516#comment:3 https://svn.boost.org/trac10/ticket/5516#comment:3 <ul> <li><strong>type</strong> <span class="trac-field-old">Bugs</span> → <span class="trac-field-new">Support Requests</span> </li> </ul> Ticket fred@… Wed, 07 Dec 2011 23:34:31 GMT <link>https://svn.boost.org/trac10/ticket/5516#comment:4 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/5516#comment:4</guid> <description> <p> Quoted: "the thread will block until exclusive ownership can be acquired". </p> <p> In the example I gave, m3 could have acquired ownership just after m2 released its upgradable lock, because only a read lock was taken at that point, by m1. And yet, it did wait until after m1 released the lock to acquire it. </p> <p> I understand this sentence of the documentation as meaning the acquisition is eager, but it seems that it is not. Is that correct? </p> </description> <category>Ticket</category> </item> <item> <author>fred@…</author> <pubDate>Thu, 08 Dec 2011 11:00:58 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/5516#comment:5 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/5516#comment:5</guid> <description> <p> The quote in my previous comment is not relevant, actually, sorry about this. My issue is specifically when upgradable ownership may be acquired, because only readers are left, but it is not. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>viboes</dc:creator> <pubDate>Fri, 09 Dec 2011 18:04:16 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/5516#comment:6 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/5516#comment:6</guid> <description> <p> Coming back to you example </p> <p> m1 - Trying to take an shared lock m1 - Took a shared lock m2 - Trying to take an upgradable lock m2 - Took an upgradable lock m3 - Trying to take an upgradable lock m2 - Released an upgradable lock m3 - Took an upgradable lock m3 - Releasing locks m1 - Released the lock </p> <p> Whether these sequence appear before or after in the trace are irrelevant. </p> <p> m3 - Took an upgradable lock m3 - Releasing locks </p> <p> m1 - Released the lock </p> <p> It is up to the scheduler to execute m1 or m3, as both are active threads, no one is blocking for the other. Note that m1 had already a shared lock, so it can continue to be executed independently of whether m3 takes the upgrade lock. </p> <p> The following sequence could also be possible </p> <p> m3 - Took an upgradable lock m1 - Released the lock m3 - Releasing locks </p> </description> <category>Ticket</category> </item> <item> <author>fred@…</author> <pubDate>Fri, 09 Dec 2011 22:50:04 GMT</pubDate> <title/> <link>https://svn.boost.org/trac10/ticket/5516#comment:7 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/5516#comment:7</guid> <description> <p> Viboes, agreed, last steps don't matter. My concern here is on the 7th step: </p> <p> m1 - Trying to take an shared lock<br /> m1 - Took a shared lock<br /> m2 - Trying to take an upgradable lock<br /> m2 - Took an upgradable lock<br /> m3 - Trying to take an upgradable lock<br /> m2 - Released an upgradable lock<br /> <strong> here I would expect m3 to be able to take the upgradable lock, even though m1 still has a read lock </strong><br /> m1 - Released the lock </p> <p> Because the "release_waiters()" is only called when there are no more readers, we ignore the case when a thread wanting an upgradable lock should be waken up when there are still read locks going on. </p> <p> This makes the locking non-eager. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>viboes</dc:creator> <pubDate>Thu, 29 Dec 2011 11:37:27 GMT</pubDate> <title>type changed https://svn.boost.org/trac10/ticket/5516#comment:8 https://svn.boost.org/trac10/ticket/5516#comment:8 <ul> <li><strong>type</strong> <span class="trac-field-old">Support Requests</span> → <span class="trac-field-new">Bugs</span> </li> </ul> <p> Replying to <a class="ticket" href="https://svn.boost.org/trac10/ticket/5516#comment:7" title="Comment 7">fred@…</a>: </p> <blockquote class="citation"> <p> Viboes, agreed, last steps don't matter. My concern here is on the 7th step: </p> <p> m1 - Trying to take an shared lock<br /> m1 - Took a shared lock<br /> m2 - Trying to take an upgradable lock<br /> m2 - Took an upgradable lock<br /> m3 - Trying to take an upgradable lock<br /> m2 - Released an upgradable lock<br /> <strong> here I would expect m3 to be able to take the upgradable lock, even though m1 still has a read lock </strong><br /> </p> <p> m1 - Released the lock </p> <p> Because the "release_waiters()" is only called when there are no more readers, we ignore the case when a thread wanting an upgradable lock should be waken up when there are still read locks going on. </p> <p> This makes the locking non-eager. </p> </blockquote> <p> You are right. The fact that in </p> <pre class="wiki"> void unlock_upgrade() { boost::mutex::scoped_lock lk(state_change); state.upgrade=false; bool const last_reader=!--state.shared_count; if(last_reader) { state.exclusive_waiting_blocked=false; release_waiters(); } } </pre><p> state.upgrade has been set at least </p> <pre class="wiki">shared_cond.notify_all(); </pre><p> must be called. </p> <p> Please could you try the following </p> <pre class="wiki"> void unlock_upgrade() { boost::mutex::scoped_lock lk(state_change); state.upgrade=false; bool const last_reader=!--state.shared_count; if(last_reader) { state.exclusive_waiting_blocked=false; release_waiters(); } else { shared_cond.notify_all(); } } </pre> Ticket viboes Thu, 29 Dec 2011 14:15:52 GMT type changed https://svn.boost.org/trac10/ticket/5516#comment:9 https://svn.boost.org/trac10/ticket/5516#comment:9 <ul> <li><strong>type</strong> <span class="trac-field-old">Bugs</span> → <span class="trac-field-new">Patches</span> </li> </ul> Ticket viboes Sat, 07 Jan 2012 23:04:32 GMT milestone changed https://svn.boost.org/trac10/ticket/5516#comment:10 https://svn.boost.org/trac10/ticket/5516#comment:10 <ul> <li><strong>milestone</strong> <span class="trac-field-old">To Be Determined</span> → <span class="trac-field-new">Boost 1.49.0</span> </li> </ul> Ticket viboes Tue, 17 Jan 2012 06:38:28 GMT <link>https://svn.boost.org/trac10/ticket/5516#comment:11 </link> <guid isPermaLink="false">https://svn.boost.org/trac10/ticket/5516#comment:11</guid> <description> <p> Committed in trunk at revision <a class="changeset" href="https://svn.boost.org/trac10/changeset/76543" title=" * [@http://svn.boost.org/trac/boost/ticket/2741 #2741] Proposal to ...">[76543]</a>. </p> </description> <category>Ticket</category> </item> <item> <dc:creator>viboes</dc:creator> <pubDate>Mon, 28 May 2012 15:42:48 GMT</pubDate> <title>status, milestone changed; resolution set https://svn.boost.org/trac10/ticket/5516#comment:12 https://svn.boost.org/trac10/ticket/5516#comment:12 <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">fixed</span> </li> <li><strong>milestone</strong> <span class="trac-field-old">Boost 1.49.0</span> → <span class="trac-field-new">Boost 1.50.0</span> </li> </ul> <p> Committed in release branch at <a class="changeset" href="https://svn.boost.org/trac10/changeset/78543" title="Merged boost.thread from trunk">[78543]</a> </p> Ticket