Boost logo

Boost Users :

Subject: [Boost-users] boost::wave::context deadlocks when called from a DLL
From: Julien Lebot (julien.ar.lebot_at_[hidden])
Date: 2012-06-20 15:39:57


Hi all,

I'm having some issue with boost::wave::context: I have a function
called PreProcessSource, which allocates a boost::wave::context and
does some preprocessing; nothing fancy at all.

std::string PreProcessSource(const std::string& instring, const
std::string& defines)
{
    typedef boost::wave::cpplexer::lex_token<> token_type;
    typedef boost::wave::cpplexer::lex_iterator<token_type> lex_iterator_type;
    typedef boost::wave::context<std::string::iterator,
lex_iterator_type> context_type;

    std::string source = instring;
    context_type ctx(source.begin(), source.end()); // DEADLOCK here
    ctx.set_language(boost::wave::enable_emit_line_directives(ctx.get_language(),
true));

    if(!defines.empty())
    {
        std::vector<std::string> tokens;
        Split<std::string>(defines, tokens, ",");

        std::vector<std::string>::const_iterator cit = tokens.begin();

        for (;cit != tokens.end(); ++cit)
            ctx.add_macro_definition(*cit);
    }

    context_type::iterator_type first = ctx.begin();
    context_type::iterator_type last = ctx.end();

    std::string outstring;

    while (first != last)
    {
        const token_type::string_type& value = (*first).get_value();
        std::copy(value.begin(), value.end(), std::back_inserter(outstring));
        ++first;
    }

    return outstring;
}

In the past it used to work fine, albeit with boost 1.47.0:

Library Nitro is a DLL that hosts the PreProcess source function, the
executable uses the Nitro DLL and can call PreProcess source, and
Nitro DLL can also sometimes call the function itself.
But right now this is no longer the case: whenever Nitro DLL calls the
function itself, or Nitro DLL calls another function, which in turn
calls back into Nitro DLL PreProcess function, it deadlocks.

If DO_DEADLOCK is defined to 1 in the following piece of code, the
deadlock will happen:

// In executable
class Tutorial01 : public Game
{
public:
    // ...
    Tutorial01()
    : Game()
    {
#if !DO_DEADLOCK
        std::stringstream sstream;
        sstream << "void main(inout float4 vtxInput : POSITION) { }"
<< std::endl << std::endl;
        std::string source = sstream.str();
        std::string defines = "";
        std::string shaderCode = Nitro::PreProcessSource(source, defines);
#endif
    }

    void LoadContent()
    {
#if DO_DEADLOCK
        std::stringstream sstream;
        sstream << "void main(inout float4 vtxInput : POSITION) { }"
<< std::endl << std::endl;
        std::string source = sstream.str();
        std::string defines = "";
        std::string shaderCode = Nitro::PreProcessSource(source, defines);
#endif
        // Original code that deadlocks too.
        // Calls in the DLL, which will call PreProcessSource.
        // effect->Initialize(device, source, "DX11");
    }

    // ...
};

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow)
{
    Tutorial00 app;
    app.Run();
    return 0;
}

Note that the constructor is called directly from the executable,
whereas LoadContent is called from the DLL:

// In Nitro DLL
void Game::Run()
{
    try
    {
        // ...

        LoadContent();

        // ...
    } catch(/* ... */) { }
}

The code is compiled for x64 and in debug mode. I compile boost myself
into a DLL (using bcp to get the files for the libraries I use) with
the following options:

Preprocessor:
WIN32
BOOST_ALL_NO_LIB
BOOST_ALL_DYN_LINK
BOOST_THREAD_BUILD_DLL
_DLL
_DEBUG
_WINDOWS
_USRDLL

Code Generation:
C++ Exceptions (/EHsc)
Multi-threaded Debug DLL (/MDd)
Function-level linking (/Gy)
Streaming SIMD Extensions 2 (/arch:SSE2) (/arch:SSE2)
Fast floating point model (/fp:fast)

DLL A uses the same code gen options, and has the following
preprocessor macros defined:
_WINDLL
_UNICODE
UNICODE
WIN32
_WINDOWS
BOOST_ALL_NO_LIB
_DEBUG

The executable and unit test executable uses the same code gen
options, and have the following preprocessor macros defined:
BOOST_ALL_NO_LIB
UNICODE
_UNICODE
_MBCS

I have tried various combinations of preprocessor definitions (with
_DEBUG and without, with BOOST_THREAD_USE_DLL, and without; to no
avail).

Here is the (cleaned up) stack trace of the deadlock:

ntdll.dll!0000000076dc135a()
[Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll]
KernelBase.dll!000007fefd4f10dc()
Nitro.dll!boost::call_once<void (__cdecl*)(void)>(boost::once_flag &
flag, void (void)* f) Line 197 + 0x18 bytes C++
Nitro.dll!boost::call_once(void (void)* func, boost::once_flag & flag)
 Line 28 C++
Nitro.dll!boost::spirit::classic::static_<boost::thread_specific_ptr<boost::weak_ptr<boost::spirit::classic::impl::grammar_helper
... Line 72 + 0x13 bytes C++
Nitro.dll!boost::spirit::classic::impl::get_definition<boost::wave::util::time_conversion::time_conversion_grammar,
... Line 241 + 0x17 bytes C++
Nitro.dll!boost::spirit::classic::impl::grammar_parser_parse<0,boost::wave::util::time_conversion::time_conversion_grammar,
... Line 296 + 0xa bytes C++
Nitro.dll!boost::spirit::classic::grammar<boost::wave::util::time_conversion::time_conversion_grammar,
... Line 55 + 0x3c bytes C++
Nitro.dll!boost::spirit::classic::grammar<boost::wave::util::time_conversion::time_conversion_grammar,
... Line 65 + 0x75 bytes C++
Nitro.dll!boost::spirit::classic::impl::phrase_parser<boost::spirit::classic::space_parser>::parse<char
const * __ptr64,boost::wave::util::time_conversion::time_conversion_grammar>
... Line 136 C++
Nitro.dll!boost::spirit::classic::parse<char const *
__ptr64,boost::wave::util::time_conversion::time_conversion_grammar,
... Line 155 + 0x3a bytes C++
Nitro.dll!boost::spirit::classic::parse<char,boost::wave::util::time_conversion::time_conversion_grammar,
... Line 173 + 0x23 bytes C++
Nitro.dll!boost::wave::util::time_conversion::time_conversion_helper::time_conversion_helper(const
char * act_time) Line 123 C++
Nitro.dll!boost::wave::util::predefined_macros::predefined_macros()
Line 196 + 0x38 bytes C++
...
Nitro.dll!Nitro::PreProcessSource(const
std::basic_string<char,std::char_traits<char>,std::allocator<char> > &
instring, const
std::basic_string<char,std::char_traits<char>,std::allocator<char> > &
defines) Line 51 + 0xa0 bytes C++
Tutorial01.exe!Tutorial01::LoadContent() Line 70 + 0x33 bytes C++
Nitro.dll!Nitro::Game::Run() Line 40 C++
Tutorial01.exe!wWinMain(HINSTANCE__ * hInstance, HINSTANCE__ *
hPrevInstance, wchar_t * lpCmdLine, int nCmdShow) Line 149 C++
Tutorial01.exe!__tmainCRTStartup() Line 547 + 0x42 bytes C
Tutorial01.exe!wWinMainCRTStartup() Line 371 C
kernel32.dll!00000000766a652d()
ntdll.dll!0000000076d9c521()

It seems boost::wave is trying to parse some time stamp, and by doing
so instantiates a grammar and that's when things seem to go bad.

The deadlock happens inside the constructor of the context type, and
the Visual Studio debugger points me to line 197 in
boost\thread\win32\once.hpp

            BOOST_VERIFY(!::boost::detail::win32::WaitForSingleObject(
                             event_handle,::boost::detail::win32::infinite));

When called directly from the executable, I have noticed that the
once_flag.status value is 0, and it exits the loop on the first
iteration, whereas when it's called from inside the DLL, the
once_flag.status value is 53, and it goes around the loop twice before
falling into the WaitForSingleObject. I'm not sure if that's even
relevant but I've exhausted all the possibilities and I'm out of
ideas.

I'd be grateful if anyone could shed some light on this.

Best Regards,
Julien Lebot


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net