socket.io-client-cpp icon indicating copy to clipboard operation
socket.io-client-cpp copied to clipboard

memory leak on Windows

Open AndrewMagpie opened this issue 3 years ago • 8 comments

In a Visual Studio Windows C++ command line project I get memory leaks for a simple client:

    sio::client s;
    s.connect("http://blah.haha");

These are defined:

#define ASIO_STANDALONE
#define _WEBSOCKETPP_CPP11_STL_
#define _WEBSOCKETPP_CPP11_FUNCTIONAL_

Using the latest client code with OpenSSL 1.1.1.m:

OPENSSL_VERSION_NUMBER=0x101010dfL
OPENSSL_API_COMPAT=0x101010dfL

I enable mem leak reporting by calling this:

void DetectMemoryLeaks()
{
    _HFILE hReportFile = _CRTDBG_FILE_STDERR;

    //Always check for memleaks in debug builds.
    // Calls _CrtDumpMemoryLeaks() on program exit.
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    //const int iReportMode = _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_WNDW | _CRTDBG_MODE_FILE;
    int iReportMode = _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_FILE;

    if (GetConsoleWindow() == nullptr)
        iReportMode |= _CRTDBG_MODE_WNDW;

    //Warning: (memleaks)
    _CrtSetReportMode(_CRT_WARN, iReportMode);
    _CrtSetReportFile(_CRT_WARN, hReportFile);

    //Errors:
    _CrtSetReportMode(_CRT_ERROR, iReportMode);
    _CrtSetReportFile(_CRT_ERROR, hReportFile);

    //Asserts:
    _CrtSetReportMode(_CRT_ASSERT, iReportMode);
    _CrtSetReportFile(_CRT_ASSERT, hReportFile);
}

The leaks I get are:

Detected memory leaks!
Dumping objects ->
{4353} normal block at 0x0000023D9CAB9F10, 48 bytes long.
 Data: <                > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
{4352} normal block at 0x0000023D9CB22D60, 64 bytes long.
 Data: <    =           > 10 9F AB 9C 3D 02 00 00 00 00 00 00 00 00 00 00 
{4349} normal block at 0x0000023D9CB24E60, 264 bytes long.
 Data: <                > 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 
{4348} normal block at 0x0000023D9CB24D80, 160 bytes long.
 Data: < `              > 80 60 A2 8F F7 7F 00 00 00 00 00 00 00 00 00 00 
{4347} normal block at 0x0000023D9CB24C30, 264 bytes long.
 Data: <i] +  BT'}   *d > 69 5D B6 2B F4 0B 42 54 27 7D 95 AD 96 2A 64 BA 
{4346} normal block at 0x0000023D9CB24AE0, 264 bytes long.
 Data: <i] +  BT'}   *d > 69 5D B6 2B F4 0B 42 54 27 7D 95 AD 96 2A 64 BA 
{4345} normal block at 0x0000023D9CB24A00, 160 bytes long.
 Data: <@d              > 40 64 A2 8F F7 7F 00 00 00 00 00 00 00 00 00 00 
{4344} normal block at 0x0000023D9CB24920, 160 bytes long.
 Data: < `              > 80 60 A2 8F F7 7F 00 00 00 00 00 00 00 00 00 00 
{4343} normal block at 0x0000023D9CB24760, 376 bytes long.
 Data: <            =   > 00 00 00 00 00 00 00 00 00 0E B2 9C 3D 02 00 00 
{4342} normal block at 0x0000023D9CAB9420, 48 bytes long.
 Data: <                > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
{4341} normal block at 0x0000023D9CB225E0, 64 bytes long.
 Data: <    =           > 20 94 AB 9C 3D 02 00 00 00 00 00 00 00 00 00 00 
{4338} normal block at 0x0000023D9CB24610, 264 bytes long.
 Data: <                > 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 
{4337} normal block at 0x0000023D9CB24530, 160 bytes long.
 Data: < `              > 80 60 A2 8F F7 7F 00 00 00 00 00 00 00 00 00 00 
{4336} normal block at 0x0000023D9CB243E0, 264 bytes long.
 Data: <  ,     X =4 J  > A6 9C 2C BF F5 E0 F6 91 58 84 3D 34 EF 4A A2 80 
{4335} normal block at 0x0000023D9CB24290, 264 bytes long.
 Data: <  ,     X =4 J  > A6 9C 2C BF F5 E0 F6 91 58 84 3D 34 EF 4A A2 80 
{4334} normal block at 0x0000023D9CB241B0, 160 bytes long.
 Data: <@d              > 40 64 A2 8F F7 7F 00 00 00 00 00 00 00 00 00 00 
{4333} normal block at 0x0000023D9CB21DA0, 160 bytes long.
 Data: < `              > 80 60 A2 8F F7 7F 00 00 00 00 00 00 00 00 00 00 
{4332} normal block at 0x0000023D9CB21650, 376 bytes long.
 Data: <            =   > 00 00 00 00 00 00 00 00 00 0E B2 9C 3D 02 00 00 
{3536} normal block at 0x0000023D9CB0F720, 12 bytes long.
 Data: <            > 00 00 00 00 01 00 00 00 01 00 00 00 
{3535} normal block at 0x0000023D9CB0FEA0, 520 bytes long.
 Data: <                > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
Object dump complete.

Leaks can trigger break points when calling: _CrtSetBreakAlloc(3535);

The {3535} and {3536} leaks is triggered from context::context from this source:

context::context(context::method m)
  : handle_(0)
{
    ::ERR_clear_error();

The {4332} leak is triggered from SSL_CTX_new in context::context from this source:

// Any supported TLS version.
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER)
  case context::tls:
    handle_ = ::SSL_CTX_new(::TLS_method());

I'm linking against the static versions of OpenSSL:

libssl_static.lib
libcrypto_static.lib

The memory leaks do not occur when linked against the non-static versions of OpenSSL:

libssl.lib
libcrypto.lib

When the above libs are used the following DLLs must exist in the same location as the executable:

libcrypto-1_1-x64.dll
libssl-1_1-x64.dll

But this is what I'm trying to avoid, (having 3rd party DLLs).

So why does it leak for the static libraries of OpenSSL but not for the non-static libraries?

AndrewMagpie avatar Mar 10 '22 05:03 AndrewMagpie

This OPENSSL_thread_stop man page mentions in the notes that for statically linked versions OPENSSL_thread_stop should be called before exiting a thread.

So now my question is where is the best place to call OPENSSL_thread_stop?

AndrewMagpie avatar Mar 10 '22 08:03 AndrewMagpie

Just to be sure, the memory leak only happens if you are using TLS? If you are only building the non-TLS version do we have memory leaks?

jmigual avatar Mar 10 '22 10:03 jmigual

How do I quickly test that?

I know for sure that its only when statically linking OpenSSL. And thus related to OPENSSL_thread_stop not being called.

AndrewMagpie avatar Mar 10 '22 10:03 AndrewMagpie

You can try linking your example against the non-TLS CMake target sioclient instead to the TLS version sioclient_tls and analyze the memory leak again.

If you are sure that it's only when statically linking OpenSSL I guess then it's already only for the TLS version as OpenSSL is not required for the non-TLS version.

Now, for the OPENSSL_thread_stop I'm not sure which is the best place to call it since we actually don't make any calls to OPENSSL. It's actually the asio library the one that uses OPENSSL and we use the TLS calls from the library. Maybe we could try updating the library version

jmigual avatar Mar 10 '22 12:03 jmigual

I tried updated the asio library, but the leak still occurs.

AndrewMagpie avatar Mar 11 '22 03:03 AndrewMagpie

I see, then I suspect that either:

  1. We are not using the asio library properly.
  2. The asio library has the memory leak and we should report it there.

My intuition tells me that 1 is probably the cause since the asio library is quite popular and other people would've found the memory leak as well. So, we would need to check whether our usage of the asio library is correct. I'm not an expert of the asio library so help would be welcome here.

jmigual avatar Mar 11 '22 08:03 jmigual

I suspect this is the reason for the leak:

This OPENSSL_thread_stop man page mentions in the notes that for statically linked versions OPENSSL_thread_stop should be called before exiting a thread.

AndrewMagpie avatar Mar 13 '22 09:03 AndrewMagpie

Yes, I agree with your conclusion, but since we are not the ones actually using OpenSSL I don't see which is the best way to solve it. I'm also not that knowledgable on the code

jmigual avatar Mar 14 '22 09:03 jmigual