question

DmitryRepkin avatar image
1 Vote"
DmitryRepkin asked vchojas-9046 answered

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

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-generalc++
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

You're going to need to post the replication code. You can do that directly inline using the code block editor.

0 Votes 0 ·

If this system does not allow you to post the code, you can write it to OneDrive, GitHub, etc., and show the address.


0 Votes 0 ·
DmitryRepkin avatar image
0 Votes"
DmitryRepkin answered DmitryRepkin edited

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

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Please add the C++ tag to your original post so people know you need help with your C++ code.

0 Votes 0 ·
DmitryRepkin avatar image
0 Votes"
DmitryRepkin answered

Code in BASE-64 is attached.

115982-code.txt



code.txt (1.8 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

DmitryRepkin avatar image
0 Votes"
DmitryRepkin answered DmitryRepkin commented

Here is a code preview:

115930-image.png



image.png (62.5 KiB)
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.


Does it work when theThread is a local variable of main?


0 Votes 0 ·

Issue appeared only when ODBC deinitialization runs after main function (i.e. global objects).
It seems issue caused by some deadlocking in crt_seh_guarded_call at_exit function.

There was no such issues in previous version of SQL ODBC driver (2016/2014/2012/2005/2000).

If in code example "ODBC Driver 17" will be replaced by "ODBC Driver 13" application will not hang.

0 Votes 0 ·
Viorel-1 avatar image
0 Votes"
Viorel-1 answered DmitryRepkin commented

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


· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

JohnGVollaro-6849 avatar image
0 Votes"
JohnGVollaro-6849 answered DmitryRepkin commented

Was this problem resolved? I am having the exact same issue

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Issue is still unresolved.

The same with "ODBC Driver 18 for SQL Server"; version 2018.0180.0001.01 ((DS_Main).170626-2112)

0 Votes 0 ·
v-kevinwong avatar image
0 Votes"
v-kevinwong answered DmitryRepkin commented

From your code, it seems you're trying to create a thread from static initialization. Please see https://docs.microsoft.com/en-us/windows/win32/dlls/dllmain

Static initialization often happens in DllMain, and as mentioned in the link above, should only be used for simple tasks. With that being the case, creating a thread with DllMain by using a static constructor is not recommended. This is not a problem with the ODBC Driver, and the fact that it worked in ODBC Version 13 but not 17 is coincidental due to the fact that the driver was updated to use a newer runtime between that time.

· 5
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

The sample is not a DLL but a main executable.

0 Votes 0 ·

Hi Dmitry,

The sample is not a DLL, but using ODBC driver functions will load the driver manager. In your call stack, you can see that odbc32.dll is being used, which is the driver manager.

0 Votes 0 ·

See no problem in test sample code.
The issue is in msodbcsql17.dll; possible related to uCRT usage.

0 Votes 0 ·
Show more comments
vchojas-9046 avatar image
0 Votes"
vchojas-9046 answered

Please see https://docs.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.

5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.