Blog Post

Azure Database Support Blog
2 MIN READ

Lesson Learned #359: TCP Provider: Error code 0x68 (104) (SQLExecDirectW)

Jose_Manuel_Jurado's avatar
May 30, 2023

Today, we got a service request that our customer faced the following error message connecting to the database:  (pyodbc.OperationalError) ('08S01', '[08S01] [Microsoft][ODBC Driver 18 for SQL Server]TCP Provider: Error code 0x68 (104) (SQLExecDirectW)'). This customer is using Python in a Linux environment. Following I would like to share my lessons learned about this error message. 

 

Checking what does mean 0x68 is 104 in decimal, and that is a Connection Reset by Peer error (i.e. the server abruptly closed the connection) and in Linux errors are listed here: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h

 

Most probably, this error means that our customer is opening the connection using proxy policy Azure SQL Database connectivity architecture - Azure SQL Database and Azure Synapse Analytics | Microsoft Learn and it was in idle state for more than 30 minutes. In this situation after 30 minutes in idle state the connection will be closed by GatewayLesson Learned #214: Hands-On-Labs: How to manage an idle connection state issue - Microsoft Community Hub - Understanding Connectivity Issues in Azure SQL Database - Microsoft Community Hub if the application tries to use the connection without checking if the connection is closed you are going to have this error message

 

We have several options to mitigate this issue:

 

 

 

 

import pyodbc
import time

def PythonRetryLogicConnectToTheDB():
    try:
        nTimes=0
        while nTimes <5:
                nTimes=nTimes+1
                print("Connecting to the DB - Attempt Number: %i " % nTimes)
                start_time = time.time()    
                conn = ConnectToTheDB()
                if( conn != None ):
                    print("Connected to the Database %s seconds ---" % ((time.time() - start_time)) )
                    return conn    
                else:
                  print("------ Next Attempt ----- Waiting for 5 seconds ---")       
                  time.sleep(5)     
        return 
    except Exception as e:
        print("An error occurred connecting to the DB - " + format(e))
        return 


def ConnectToTheDB():
    try:
        return pyodbc.connect("DRIVER={ODBC Driver 18 for SQL Server};server=tcp:servername.database.windows.net,1433;UID=username;PWD=password;database=dbname;APP=Testing Connection;timeout=30");  
    except Exception as e:
        print("An error occurred connecting to the DB - " + format(e))
        return 


SQL = "select 1"
nLoop=1
while  nLoop<100:
    nLoop=nLoop+1
    conn = PythonRetryLogicConnectToTheDB()
    if( conn != None ):
        cursor = conn.cursor()
        start_time = time.time()    
        cursor.execute(SQL)
        row = cursor.fetchone() 
        print("---------------- Loop:%d - %s seconds ---" % (nLoop,(time.time() - start_time)) )
        conn.close()
    else:
        print("---------------- Loop:%d - (Not able to connect) " % (nLoop))   

 

 

 

Enjoy!

Updated May 30, 2023
Version 3.0
  • erfannariman's avatar
    erfannariman
    Copper Contributor

    Thank you for taking the time to write this down, we were facing this issue quite often in our Python Flask App which is deployed on an App Service. It was quite difficult to pin down why this was happening.

  • jimma72's avatar
    jimma72
    Copper Contributor

    Thanks Jose_Manuel_Jurado, this was useful for us to.  In our scenario we were using sqlalchemy (2.x), pyodbc and Azure MSSQL and had the same issues you highlighted above.  We resolved the issue by doing 2 distinct things:

    import pyodbc
    pyodbc.pooling = False​
    engine = create_engine(
        SQLALCHEMY_DATABASE_URL,
        connect_args={"check_same_thread": False},
        pool_recycle=1500
    )

     

  • flatrocks's avatar
    flatrocks
    Copper Contributor

    It sure seems like the example code is just creating (with retries) a new connection for each SQL request.  That should certainly work, but it seems like that could force a lot of unnecessary re-connections.  I am assuming there is some overhead associated with creating a new connection and cursor.

    By wrapping the cursor.execute() in a try/except and catching the pyodbc.OperationalError (and possibly test the error details) you could recreate the connection and cursor (and then retry the request) only when needed.  

    I also wonder if simply a simpler solution would be to keep track of how long the connection's been open and recreate it when it gets stale, similar to  how it's done in example code for MS Graph connections.

    OTOH, you cannot argue that catching all exceptions types (`except Exception as e:`) and retrying in 5 seconds will not solve a plethora of issues 🙂