I've been having a problem with boost::asio where timer and/or sockets created using a global io_service instance crash during construction. The system where the crash occurs is as follows:
Windows 7
Visual Studio 2013 Express for Windows Desktop; v 12.0.31101.00 Update 4
Boost 1.57, dynamically linked, compiled with multithreading as, e.g. boost_thread-vc120-mt-gd-1_57.dll
I've been able to replicate the issue in the following simplified code:
// file global_io_service.h
#ifndef INCLUDED_GLOBAL_IO_SERVICE_H
#define INCLUDED_GLOBAL_IO_SERVICE_H
#include <boost/asio/io_service.hpp>
#include <iostream>
#include <string>
namespace foo{
extern boost::asio::io_service test_io_service;
class foo_base_io_service{
public:
foo_base_io_service(const std::string& name)
: d_who_am_i(name)
{
std::cout << "constructing copy " << ++foo_base_io_service::num_instances << "my name is " << d_who_am_i << std::endl;
}
boost::asio::io_service& get_ref()
{
std::cout << "class requested copy of " << d_who_am_i << std::endl;
return d_ios;
}
~foo_base_io_service()
{
std::cout << "Someone 86'd the base_io_service..." << std::endl;
}
private:
// this class is not copyable
foo_base_io_service(const foo_base_io_service&);
foo_base_io_service& operator=(const foo_base_io_service&);
std::string d_who_am_i;
static int num_instances;
boost::asio::io_service d_ios;
};
extern foo_base_io_service global_timer_io_service;
} // namespace foo
#endif
// File global_io_service.cpp
#include "global_io_service.h"
namespace foo{
boost::asio::io_service test_io_service;
foo_base_io_service global_timer_io_service("FOO_TIMER_SERVICE");
// static initialization
int foo_base_io_service::num_instances = 0;
}
// FILE main.cpp
#include <WinSock2.h>
#include "global_io_service.h"
#include <boost/asio/deadline_timer.hpp>
int main(int argc, char *argv[])
{
// also causes crash
boost::asio::deadline_timer crash_timer2(foo::test_io_service);
// causes crash
boost::asio::deadline_timer crash_timer(foo::global_timer_io_service.get_ref());
return 0 ;
}
Here is the backtrace of the crash:
test_io_service.exe!boost::asio::detail::win_mutex::lock() Line 51
test_io_service.exe!boost::asio::detail::scoped_lock::scoped_lock(boost::asio::detail::win_mutex & m) Line 47
test_io_service.exe!boost::asio::detail::win_iocp_io_service::do_add_timer_queue(boost::asio::detail::timer_queue_base & queue) Line 477
test_io_service.exe!boost::asio::detail::win_iocp_io_service::add_timer_queue >(boost::asio::detail::timer_queue > & queue) Line 79
test_io_service.exe!boost::asio::detail::deadline_timer_service >::deadline_timer_service >(boost::asio::io_service & io_service) Line 69
test_io_service.exe!boost::asio::deadline_timer_service >::deadline_timer_service >(boost::asio::io_service & io_service) Line 78
test_io_service.exe!boost::asio::detail::service_registry::create > >(boost::asio::io_service & owner) Line 81
test_io_service.exe!boost::asio::detail::service_registry::do_use_service(const boost::asio::io_service::service::key & key, boost::asio::io_service::service * (boost::asio::io_service &) * factory) Line 123
test_io_service.exe!boost::asio::detail::service_registry::use_service > >() Line 49
test_io_service.exe!boost::asio::use_service > >(boost::asio::io_service & ios) Line 34
test_io_service.exe!boost::asio::basic_io_object >,0>::basic_io_object >,0>(boost::asio::io_service & io_service) Line 91
test_io_service.exe!boost::asio::basic_deadline_timer,boost::asio::deadline_timer_service > >::basic_deadline_timer,boost::asio::deadline_timer_service > >(boost::asio::io_service & io_service) Line 151
test_io_service.exe!main(int argc, char * * argv) Line 16 C++
Here's what I've learned:
Hopefully, I'm having a bad day and invoking undefined behavior. Otherwise, I'd like to understand why this is happening? Thanks in advance.
The problem is that ASIO selects its io_service implementation on Windows by whether or not BOOST_ASIO_HAS_IOCP
gets defined by boost/asio/detail/config.hpp
. If defined, it will use the win_iocp_io_service
. If not, it will use the task_io_service
- see boost/asio/io_service.hpp
. If this selection is different across translation units, you will end up initializing the io_service as one and using it as another. They differ in subtle ways, e.g. what mutexes are initialized, so this problem can manifest as a crash due to using an uninitialized mutex.
As for what selects BOOST_ASIO_HAS_IOCP
, let's look at config.hpp
:
#if !defined(BOOST_ASIO_HAS_IOCP)
# if defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
# if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0400)
# if !defined(UNDER_CE)
# if !defined(BOOST_ASIO_DISABLE_IOCP)
# define BOOST_ASIO_HAS_IOCP 1
# endif // !defined(BOOST_ASIO_DISABLE_IOCP)
# endif // !defined(UNDER_CE)
# endif // defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0400)
# endif // defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
#endif // !defined(BOOST_ASIO_HAS_IOCP)
In this case, the controversial macro is _WIN32_WINNT
, which appears to be getting defined by WinSock2.h
in your project. Because it's defined in main.cpp
, but not defined in global_io_service.cpp
, you're initializing io_service to use task_io_service
and calling it as if it used win_iocp_io_service
To resolve the problem, either appropriately define _WIN32_WINNT
in your compiler definitions or global header file, or just turn the IOCP reactor off altogether by defining BOOST_ASIO_DISABLE_IOCP
(again, globally).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With