Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a6ed286

Browse files
committed
improved resiliency code
1 parent f1675d4 commit a6ed286

File tree

3 files changed

+82
-38
lines changed

3 files changed

+82
-38
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,4 @@ ASALocalRun/
333333
venv/
334334
env/
335335
env.*
336+
.env

app.py

+78-37
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import sys
22
import os
3-
from flask import Flask
4-
from flask_restful import reqparse, abort, Api, Resource
53
import json
64
import pyodbc
75
import socket
6+
from flask import Flask
7+
from flask_restful import reqparse, abort, Api, Resource
88
from threading import Lock
9+
from tenacity import *
910
from opencensus.ext.azure.trace_exporter import AzureExporter
1011
from opencensus.ext.flask.flask_middleware import FlaskMiddleware
1112
from opencensus.trace.samplers import ProbabilitySampler
12-
13-
pyodbc.pooling=False
13+
import logging
1414

1515
# Initialize Flask
1616
app = Flask(__name__)
@@ -27,6 +27,7 @@
2727
parser = reqparse.RequestParser()
2828
parser.add_argument('customer')
2929

30+
3031
# Implement manual connection pooling
3132
class ConnectionManager(object):
3233
__instance = None
@@ -39,50 +40,81 @@ def __new__(cls):
3940
ConnectionManager.__instance = object.__new__(cls)
4041
return ConnectionManager.__instance
4142

42-
def __getConnection(self, conn_index):
43-
application_name = ";APP={0}-{1}".format(socket.gethostname(), conn_index)
44-
conn = pyodbc.connect(os.environ['SQLAZURECONNSTR_WWIF'] + application_name)
45-
return conn
43+
def is_retriable(self, value):
44+
RETRY_CODES = [
45+
8001,
46+
42000,
47+
1204, # The instance of the SQL Server Database Engine cannot obtain a LOCK resource at this time. Rerun your statement when there are fewer active users. Ask the database administrator to check the lock and memory configuration for this instance, or to check for long-running transactions.
48+
1205, # Transaction (Process ID) was deadlocked on resources with another process and has been chosen as the deadlock victim. Rerun the transaction
49+
1222, # Lock request time out period exceeded.
50+
49918, # Cannot process request. Not enough resources to process request.
51+
49919, # Cannot process create or update request. Too many create or update operations in progress for subscription "%ld".
52+
49920, # Cannot process request. Too many operations in progress for subscription "%ld".
53+
4060, # Cannot open database "%.*ls" requested by the login. The login failed.
54+
4221, # Login to read-secondary failed due to long wait on 'HADR_DATABASE_WAIT_FOR_TRANSITION_TO_VERSIONING'. The replica is not available for login because row versions are missing for transactions that were in-flight when the replica was recycled. The issue can be resolved by rolling back or committing the active transactions on the primary replica. Occurrences of this condition can be minimized by avoiding long write transactions on the primary.
55+
56+
40143, # The service has encountered an error processing your request. Please try again.
57+
40613, # Database '%.*ls' on server '%.*ls' is not currently available. Please retry the connection later. If the problem persists, contact customer support, and provide them the session tracing ID of '%.*ls'.
58+
40501, # The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d.
59+
40540, # The service has encountered an error processing your request. Please try again.
60+
40197, # The service has encountered an error processing your request. Please try again. Error code %d.
61+
10929, # Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. However, the server is currently too busy to support requests greater than %d for this database. For more information, see http://go.microsoft.com/fwlink/?LinkId=267637. Otherwise, please try again later.
62+
10928, # Resource ID: %d. The %s limit for the database is %d and has been reached. For more information, see http://go.microsoft.com/fwlink/?LinkId=267637.
63+
10060, # An error has occurred while establishing a connection to the server. When connecting to SQL Server, this failure may be caused by the fact that under the default settings SQL Server does not allow remote connections. (provider: TCP Provider, error: 0 - A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.) (Microsoft SQL Server, Error: 10060)
64+
10054, # The data value for one or more columns overflowed the type used by the provider.
65+
10053, # Could not convert the data value due to reasons other than sign mismatch or overflow.
66+
233, # A connection was successfully established with the server, but then an error occurred during the login process. (provider: Shared Memory Provider, error: 0 - No process is on the other end of the pipe.) (Microsoft SQL Server, Error: 233)
67+
64,
68+
20,
69+
0
70+
]
71+
result = value in RETRY_CODES
72+
return result
73+
74+
@retry(stop=stop_after_attempt(3), wait=wait_fixed(10), after=after_log(app.logger, logging.DEBUG))
75+
def __createConnection(self, idx):
76+
try:
77+
application_name = ";APP={0}-{1}".format(socket.gethostname(), idx)
78+
conn = pyodbc.connect(os.environ['SQLAZURECONNSTR_WWIF'] + application_name)
79+
except Exception as e:
80+
if isinstance(e,pyodbc.ProgrammingError) or isinstance(e,pyodbc.OperationalError):
81+
if self.is_retriable(int(e.args[0])):
82+
raise
4683

84+
return conn
4785

48-
def getCursor(self):
86+
def __getConnection(self):
4987
self.__lock.acquire()
50-
self.__conn_index += 1
88+
idx = self.__conn_index + 1
5189

52-
if self.__conn_index > 9:
53-
self.__conn_index = 0
54-
55-
if not self.__conn_index in self.__conn_dict.keys():
56-
new_conn = self.__getConnection(self.__conn_index)
57-
self.__conn_dict.update( { self.__conn_index: new_conn } )
58-
59-
conn = self.__conn_dict[self.__conn_index]
90+
if idx > 9:
91+
idx = 0
6092

61-
try:
62-
cursor = conn.cursor()
63-
except e:
64-
if e.__class__ == pyodbc.ProgrammingError:
65-
conn == self.__getConnection(self.__conn_index)
66-
self.__conn_dict[self.__conn_index] = conn
67-
cursor = conn.cursor()
68-
93+
if not idx in self.__conn_dict.keys():
94+
application_name = ";APP={0}-{1}".format(socket.gethostname(), idx)
95+
conn = pyodbc.connect(os.environ['SQLAZURECONNSTR_WWIF'] + application_name)
96+
self.__conn_dict.update( { idx: conn } )
97+
else:
98+
conn = self.__conn_dict[idx]
99+
100+
self.__conn_index = idx
69101
self.__lock.release()
70102

71-
return (self.__conn_index, cursor)
103+
return (idx, conn)
72104

73-
def removeConnection(self, idx):
105+
def __removeConnection(self, idx):
74106
self.__lock.acquire()
75107
if idx in self.__conn_dict.keys():
76108
del(self.__conn_dict[idx])
77109
self.__lock.release()
78110

79-
class Queryable(Resource):
80-
def executeQueryJson(self, verb, payload=None):
111+
@retry(stop=stop_after_attempt(3), wait=wait_fixed(10), after=after_log(app.logger, logging.DEBUG))
112+
def executeQueryJSON(self, procedure, payload=None):
81113
result = {}
82-
(idx, cursor) = ConnectionManager().getCursor()
83-
entity = type(self).__name__.lower()
84-
procedure = f"web.{verb}_{entity}"
114+
(idx, conn) = self.__getConnection()
85115
try:
116+
cursor = conn.cursor()
117+
86118
if payload:
87119
cursor.execute(f"EXEC {procedure} ?", json.dumps(payload))
88120
else:
@@ -96,13 +128,22 @@ def executeQueryJson(self, verb, payload=None):
96128
result = {}
97129

98130
cursor.commit()
99-
except:
100-
ConnectionManager().removeConnection(idx)
101-
finally:
102-
cursor.close()
131+
except Exception as e:
132+
self.__removeConnection(idx)
133+
if isinstance(e,pyodbc.ProgrammingError) or isinstance(e,pyodbc.OperationalError):
134+
if self.is_retriable(int(e.args[0])):
135+
raise
103136

104137
return result
105138

139+
class Queryable(Resource):
140+
def executeQueryJson(self, verb, payload=None):
141+
result = {}
142+
entity = type(self).__name__.lower()
143+
procedure = f"web.{verb}_{entity}"
144+
result = ConnectionManager().executeQueryJSON(procedure, payload)
145+
return result
146+
106147
# Customer Class
107148
class Customer(Queryable):
108149
def get(self, customer_id):

requirements.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ pyodbc
22
flask
33
flask-restful
44
opencensus-ext-azure
5-
opencensus-ext-flask
5+
opencensus-ext-flask
6+
tenacity
7+
python-dotenv

0 commit comments

Comments
 (0)