Application hangs on exit when trying to use ODBC driver 17 for MS SQL Server

Repkin Dmitry 11 Reputation points
2021-07-19T13:16:45.023+00:00

Have an issue with ODBC Driver 17 for SQL Server.

Application hangs on exit when trying to use ODBC driver in static object that create another thread.

The code works fine with ODBC Driver 13 and below but hangs with ODBC Driver 17.
Issue appeared in release build 32/64 bit and not in debug build.

Have a sample code that reproduce the issue but MS Q&A does not allow to attach it to this post.

main thread stack (just wait second thread to join):

ntdll.dll!_NtWaitForSingleObject@12 () Unknown
KernelBase.dll!WaitForSingleObjectEx() Unknown
msvcp140.dll!_Thrd_join(_Thrd_t thr, int * code) Line 57 C++
[Inline Frame] HangOdbc2.exe!std::thread::join() Line 130 C++
[Inline Frame] HangOdbc2.exe!SQLThread::{dtor}() Line 62 C++
HangOdbc2.exe!`dynamic atexit destructor for 'theThread''() C++
ucrtbase.dll!<lambda>(void)() Unknown
ucrtbase.dll!__crt_seh_guarded_call<...>::operator()<...>() Unknown
ucrtbase.dll!<lambda>(void)() Unknown
ucrtbase.dll!__crt_seh_guarded_call<...>::operator()<...>() Unknown
ucrtbase.dll!common_exit() Unknown
ucrtbase.dll!_exit () Unknown
HangOdbc2.exe!__scrt_common_main_seh() Line 310 C++
kernel32.dll!@BaseThreadInitThunk@12 () Unknown
ntdll.dll!__RtlUserThreadStart() Unknown
ntdll.dll!__RtlUserThreadStart@8 () Unknown

second thread with ODBC driver:

ntdll.dll!_NtWaitForAlertByThreadId@8()
ntdll.dll!RtlpWaitOnAddressWithTimeout()
ntdll.dll!RtlpWaitOnCriticalSection()
ntdll.dll!RtlpEnterCriticalSectionContended()
ntdll.dll!_RtlEnterCriticalSection@4()
ucrtbase.dll!__crt_seh_guarded_call<...>::operator()<...>()
ucrtbase.dll!__execute_onexit_table()
msodbcsql17.dll!___scrt_dllmain_uninitialize_c()
msodbcsql17.dll!dllmain_crt_process_detach()
msodbcsql17.dll!dllmain_crt_dispatch()
msodbcsql17.dll!dllmain_dispatch()
msodbcsql17.dll!__DllMainCRTStartup@12()
ntdll.dll!_LdrxCallInitRoutine@16()
ntdll.dll!LdrpCallInitRoutine()
ntdll.dll!LdrpProcessDetachNode()
ntdll.dll!LdrpUnloadNode()
ntdll.dll!LdrpDecrementModuleLoadCountEx()
ntdll.dll!LdrUnloadDll()
KernelBase.dll!FreeLibrary()
odbc32.dll!FreeDriverInfo()
odbc32.dll!_FreeDriverList@0()
odbc32.dll!UninitializeDll()
odbc32.dll!SQLFreeHandle()
[Inline Frame] HangOdbc2.exe!SQLConnection::CloseConnection() Line 34
HangOdbc2.exe!SQLThread::Process() Line 57
[Inline Frame] HangOdbc2.exe!std::invoke(void(SQLThread::*)() &&) Line 1601
HangOdbc2.exe!std::thread::_Invoke<...>(void * _RawVals) Line 56
ucrtbase.dll!thread_start<...>()
kernel32.dll!@BaseThreadInitThunk@12()
ntdll.dll!__RtlUserThreadStart()
ntdll.dll!__RtlUserThreadStart@8()

The ODBC Driver 17 for SQL Server version 2017.0177.0001.01 ((DS_Main).170626-2112)

The same situation with 2017.0177.0002.01 ((DS_Main).170626-2112)

SQL Server
SQL Server
A family of Microsoft relational database management and analysis systems for e-commerce, line-of-business, and data warehousing solutions.
13,839 questions
C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,736 questions
{count} votes

7 answers

Sort by: Most helpful
  1. v-chojas 6 Reputation points
    2022-03-21T15:54:24.613+00:00

    Please see https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices
    for more information.

    ODBC 13 uses a different version of the runtime library, it is older and does not do thread-safe static initialisation. That may be why you don't see a deadlock. But the newer one does, so the atexit() handlers (which call global static destructors) are executed in a locked section. The main thread is waiting in a locked section for the secondary thread to exit, but the secondary thread is also waiting for that lock in order to run its uninitialisation.

    This is all just a long explanation for why you should not be doing anything but trivial initialisation for global statics.

    1 person found this answer helpful.
    0 comments No comments

  2. Repkin Dmitry 11 Reputation points
    2021-07-19T14:21:58.183+00:00

    When I try it to put source before I receive error: "WAF v2 has determined your request exceeded the normal web request and has blocked your request."


  3. Repkin Dmitry 11 Reputation points
    2021-07-19T14:57:31.777+00:00

    Code in BASE-64 is attached.

    115982-code.txt

    0 comments No comments

  4. Repkin Dmitry 11 Reputation points
    2021-07-19T14:58:59.197+00:00

    Here is a code preview:

    115930-image.png


  5. Viorel 117.6K Reputation points
    2021-07-19T15:01:45.803+00:00

    For convenience, this is your original code:

    #include <windows.h>
    #include <sqlext.h>
    #include <sql.h>
    #include <cstdio>
    #include <thread>
    
    
    class SQLConnection
    {
        SQLHANDLE henv;
        SQLHANDLE hdbc;
        SQLHANDLE hstmt;
    public:
        void InitConnection()
        {
            unsigned int cbCnxOut = 0;
            wchar_t szCnxOut[512] = {};
    
            int ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
            ret = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_UINTEGER);
            ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    
            const wchar_t connection_string[] = L"DRIVER={ODBC Driver 17 for SQL Server};NETWORK=dbmssocn;SERVER=SQL1;UID=sa;PWD=Pwd";
    //      const wchar_t connection_string[] = L"DRIVER={ODBC Driver 13 for SQL Server};NETWORK=dbmssocn;SERVER=SQL1;UID=sa;PWD=Pwd";
    
            ret = SQLDriverConnect(hdbc, NULL, (SQLWCHAR*)connection_string, SQL_NTS,
                (SQLWCHAR*)szCnxOut, _countof(szCnxOut), (SHORT*)&cbCnxOut, SQL_DRIVER_NOPROMPT);
        }
    
        void CloseConnection()
        {
            SQLDisconnect(hdbc);
            SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
            SQLFreeHandle(SQL_HANDLE_ENV, henv);
        }
    };
    
    class SQLThread
    {
        int exit_signal = 0;
        std::thread th;
    
    public:
        SQLThread() : th(&SQLThread::Process, this)
        {
        }
        void Process()
        {
            SQLConnection conn;
    
            conn.InitConnection();
    
            while (!exit_signal)
                Sle ep(100);
    
            conn.CloseConnection();
        }
    
        ~SQLThread()
        {
            exit_signal = 1;
            th.join();
        }
    
    } theThread;
    
    
    int main()
    {
        Sle ep(3000);
        return 0;
    }
    

    (But remove the space from "Sle ep").


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.