Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does boost::wave::context constructor deadlocks when called from a DLL?

Tags:

c++

dll

boost

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 (this project is being modernized so it was broken for a long time) it used to work fine in the same setup:

Library A is a DLL that hosts the PreProcess source function, executable B uses the DLL A and can call PreProcess source, and DLL A can also sometimes call the function itself.

But right now this is no longer the case: whenever DLL A calls the function itself, or DLL A calls another function, which in turn calls back into DLL A PreProcess source, it deadlocks.

Here's an example that works just fine from my unit testing:

BOOST_AUTO_TEST_CASE(Preprocessor)
{
    std::stringstream sstream;
    sstream << "void main(inout float4 vtxInput : POSITION) { }" << std::endl << std::endl;
    std::string source = sstream.str();
    std::string defines = "";
    try
    {
        std::string shaderCode = Nitro::PreProcessSource(source, defines);
    } catch (boost::wave::preprocess_exception& pe)
    {
        std::cerr << pe.what() << std::endl;
    }
}

And here is the weird bit, if DO_DEADLOCK is defined to 1 in the following piece of code, the deadlock will happen:

class Tutorial00 : public Game
{
public:
    // ...
    Tutorial00()
    : 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");
    }

    // ...
};

#if 1
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
#else
int main(int argc, char* argv[])
#endif
{
    Tutorial00 app;
    app.Run();
    return 0;
}

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

// In the 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 options except for BOOST_ALL_DYN_LINK, BOOST_THREAD_BUILD_DLL and the string pooling option.

The Boost unit test library is build separately as a static library.

Here is the stack trace of the working version:

Nitro.dll!boost::call_once<void (__cdecl*)(void)>(boost::once_flag & flag, void (void)* f)  Line 200    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++
NitroCoreUnitTests.exe!Preprocessor::test_method()  Line 16 C++
NitroCoreUnitTests.exe!Preprocessor_invoker()  Line 7 + 0x1f bytes  C++
...
NitroCoreUnitTests.exe!boost::unit_test::unit_test_main(boost::unit_test::test_suite * (int, char * *)* init_func, int argc, char * * argv)  Line 187   C++
NitroCoreUnitTests.exe!main(int argc, char * * argv)  Line 238  C++
NitroCoreUnitTests.exe!__tmainCRTStartup()  Line 555 + 0x19 bytes   C
NitroCoreUnitTests.exe!mainCRTStartup()  Line 371   C
kernel32.dll!00000000766a652d()     
[Frames below may be incorrect and/or missing, no symbols loaded for kernel32.dll]  
ntdll.dll!0000000076d9c521()    

And here is the 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 South.

Thanks in advance for any help :)

like image 601
Julien Lebot Avatar asked Jun 19 '26 19:06

Julien Lebot


1 Answers

I found the following ticket opened on boost's trac: boost::call_once not re-entrant (at least in win32)

It says that call_once is not re-entrant, and shouldn't be called recursively. Thus the code that I am employing is undefined behavior; the ticket was marked as "will not fix".

The solution I've taken was to create a separate DLL that only contains the PreProcessSource function.

like image 174
Julien Lebot Avatar answered Jun 21 '26 09:06

Julien Lebot



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!