boost::asio::io_service crash in win_mutex lock
Asked Answered
I

3

6

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:

  • The issue does not occur in Ubuntu 14.04, Ubuntu 14.10 or Red Hat 6.5 with boost 1.54.
  • The issue appears related to the order of inclusion of Winsock2. For instance, exchanging the order of inclusion with global_io_service.h eliminates the crash.
  • The issue appears related to the extern linkage of global_timer_io_service. Moving the definition of global_timer_io_service into main.cpp eliminates the crash.
  • I've found reports of similar crashes occuring on io_service internal critical sections. Those issues were mostly related to lifetime of io_service objects being passed into timer/socket constructors. In my case, I think the io_service I'm using was already constructed before main is entered.
  • My gut says there is a race condition (perhaps some global state setup in WinSock2?) that prevents proper construction of the io_service object.

Hopefully, I'm having a bad day and invoking undefined behavior. Otherwise, I'd like to understand why this is happening? Thanks in advance.

Ie answered 20/7, 2015 at 15:28 Comment(3)
My gut says that it's Static Initialization Fiasco sooner than a (thread) data race.Underprop
@Underprop Agreed. The fiasco makes sense as a possible cause. Changing the linkage of foo_base_io_service and swapping header inclusion order could both trigger changes in static initialization ordering.Ie
@Ie , any solution so far???Pew
S
10

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).

Shikari answered 9/12, 2016 at 21:0 Comment(1)
Awesome answer, I'd upvote if I could, but don't have enough rep. Appreciate the detail; another reason to hate macros!Ie
M
0

The problem is the lifetime of yoor ioservice. You grab it out of the object.

The ioservice must live longer than all services.

In this example http://www.boost.org/doc/libs/1_58_0/doc/html/boost_asio/tutorial/tuttimer2.html The ioservice lives longer than the deadline timer.

Edit: Here is a german online book from boris schäling http://dieboostcppbibliotheken.de/boost.asio-ioservices-und-objekte

Mutinous answered 21/7, 2015 at 7:37 Comment(1)
Thanks for the reply. However, unless I'm missing something, I don't think the issue is related to the lifetime of the io_service in this particular case. I added the wrapper 'foo_base_io_service' as mechanism to track the lifetime of the io_service. I've edited the code above to add a print statement in the destructor. The destructor never gets called, indicating the object is still alive at time of crash. I've also added an extern io_service variable 'test_io_service' to bypass the wrapper class. The test_io_service object produces the same crash.Ie
E
0

In my case, I defined deadline_timer before io_service in the class header caused this problem.

Eccrinology answered 28/11, 2019 at 6:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.