When running the http async_client example of the official boos asio example code in helgrind, there is a warning. This code from the file boost/asio/detail/posix_event.hpp seems to be the cause of the warning:
// Signal the event and unlock the mutex.
template <typename Lock>
void signal_and_unlock(Lock& lock)
{
BOOST_ASSERT(lock.locked());
signalled_ = true;
lock.unlock();
::pthread_cond_signal(&cond_); // Ignore EINVAL.
}
Here is the full output of valgrind/helgrind:
jcm@Ubuntu:~/samples/async-client/build$ valgrind --tool=helgrind ./async-client stackoverflow.com error.html
==2894== Helgrind, a thread error detector
==2894== Copyright (C) 2007-2011, and GNU GPL'd, by OpenWorks LLP et al.
==2894== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==2894== Command: ./async-client stackoverflow.com error.html
==2894==
==2894== ---Thread-Announcement------------------------------------------
==2894==
==2894== Thread #1 is the program's root thread
==2894==
==2894== ----------------------------------------------------------------
==2894==
==2894== Thread #1: pthread_cond_{signal,broadcast}: dubious: associated lock is not held by any thread
==2894== at 0x4C2C978: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so)
==2894== by 0x4C2E5EA: pthread_cond_signal@* (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so)
==2894== by 0x43FCB2: void boost::asio::detail::posix_event::signal_and_unlock<boost::asio::detail::scoped_lock<boost::asio::detail::posix_mutex> >(boost::asio::detail::scoped_lock<boost::asio::detail::posix_mutex>&) (in /home/jcm/samples/async-client/build/async-client)
==2894== by 0x43B659: boost::asio::detail::task_io_service::wake_one_idle_thread_and_unlock(boost::asio::detail::scoped_lock<boost::asio::detail::posix_mutex>&) (in /home/jcm/samples/async-client/build/async-client)
==2894== by 0x43B68A: boost::asio::detail::task_io_service::wake_one_thread_and_unlock(boost::asio::detail::scoped_lock<boost::asio::detail::posix_mutex>&) (in /home/jcm/samples/async-client/build/async-client)
==2894== by 0x43B0B8: boost::asio::detail::task_io_service::post_immediate_completion(boost::asio::detail::task_io_service_operation*) (in /home/jcm/samples/async-client/build/async-client)
==2894== by 0x43DBFD: boost::asio::detail::resolver_service_base::start_resolve_op(boost::asio::detail::task_io_service_operation*) (in /home/jcm/samples/async-client/build/async-client)
==2894== by 0x442F86: void boost::asio::detail::resolver_service<boost::asio::ip::tcp>::async_resolve<boost::_bi::bind_t<void, boost::_mfi::mf2<void, client, boost::system::error_code const&, boost::asio::ip::basic_resolver_iterator<boost::asio::ip::tcp> >, boost::_bi::list3<boost::_bi::value<client*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >(std::shared_ptr<void>&, boost::asio::ip::basic_resolver_query<boost::asio::ip::tcp> const&, boost::_bi::bind_t<void, boost::_mfi::mf2<void, client, boost::system::error_code const&, boost::asio::ip::basic_resolver_iterator<boost::asio::ip::tcp> >, boost::_bi::list3<boost::_bi::value<client*>, boost::arg<1> (*)(), boost::arg<2> (*)()> >) (in /home/jcm/samples/async-client/build/async-client)
==2894== by 0x44187E: void boost::asio::ip::resolver_service<boost::asio::ip::tcp>::async_resolve<boost::_bi::bind_t<void, boost::_mfi::mf2<void, client, boost::system::error_code const&, boost::asio::ip::basic_resolver_iterator<boost::asio::ip::tcp> >, boost::_bi::list3<boost::_bi::value<client*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >(std::shared_ptr<void>&, boost::asio::ip::basic_resolver_query<boost::asio::ip::tcp> const&, boost::_bi::bind_t<void, boost::_mfi::mf2<void, client, boost::system::error_code const&, boost::asio::ip::basic_resolver_iterator<boost::asio::ip::tcp> >, boost::_bi::list3<boost::_bi::value<client*>, boost::arg<1> (*)(), boost::arg<2> (*)()> >&&) (in /home/jcm/samples/async-client/build/async-client)
==2894== by 0x44097E: void boost::asio::ip::basic_resolver<boost::asio::ip::tcp, boost::asio::ip::resolver_service<boost::asio::ip::tcp> >::async_resolve<boost::_bi::bind_t<void, boost::_mfi::mf2<void, client, boost::system::error_code const&, boost::asio::ip::basic_resolver_iterator<boost::asio::ip::tcp> >, boost::_bi::list3<boost::_bi::value<client*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >(boost::asio::ip::basic_resolver_query<boost::asio::ip::tcp> const&, boost::_bi::bind_t<void, boost::_mfi::mf2<void, client, boost::system::error_code const&, boost::asio::ip::basic_resolver_iterator<boost::asio::ip::tcp> >, boost::_bi::list3<boost::_bi::value<client*>, boost::arg<1> (*)(), boost::arg<2> (*)()> >&&) (in /home/jcm/samples/async-client/build/async-client)
==2894== by 0x43E05F: client::client(boost::asio::io_service&, std::string const&, std::string const&) (in /home/jcm/samples/async-client/build/async-client)
==2894== by 0x437018: main (in /home/jcm/samples/async-client/build/async-client)
==2894==
Response returned with status code 400
==2894==
==2894== For counts of detected and suppressed errors, rerun with: -v
==2894== Use --history-level=approx or =none to gain increased speed, at
==2894== the cost of reduced accuracy of conflicting-access information
==2894== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 89 from 29)
I seems wrong to me that mutex is unlocked before the condition variable is signalled. In case it’s a false positive, please let me know why the above code is correct.
Presuming that
lockis a wrapper object for the very samepthread_mutex_tobject that was used in the matching call topthread_cond_(timed)wait, most implementations of pthreads will allow you to issue the signal and the unlock in either order, becausepthread_cond_(timed)waitis defined to unblock on the condition variable and acquire the mutex as an atomic operation — that is, it will not return successfully until both the CV has been signaled and the calling thread has acquired the mutex.Internet rumor suggests that nobody fails this operation no matter what order it’s in, but some thread schedulers have been written for maximum efficiency when the unlock happens after the signal — and that others have been written for maximum efficiency when the unlock happens before the signal, which means you can’t win all the time regardless of how you write it. Personally, if I were maintaining the code in question, I would switch the order just on the grounds that it makes helgrind happy. You don’t want to have to wade through junk complaints to find the real race conditions.
And I’m adding “
pthread_cond_signalshould take both a CV and a mutex, just likepthread_cond_wait” to my list of POSIX API botches to fix when I get a time machine.