Skip to content

Commit 880b9b8

Browse files
authored
Automatically adjust the default connectRetryCount against Azure endpoints (#2274)
1 parent 4dc1be0 commit 880b9b8

File tree

4 files changed

+231
-81
lines changed

4 files changed

+231
-81
lines changed

src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java

Lines changed: 165 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,52 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
230230
/** Engine Edition 11 = Azure Synapse serverless SQL pool */
231231
private static final int ENGINE_EDITION_SQL_AZURE_SYNAPSE_SERVERLESS_SQL_POOL = 11;
232232

233+
/**
234+
* Azure SQL server endpoints
235+
*/
236+
enum AzureSQLServerEndpoints {
237+
AZURE_GENERIC_ENDPOINT(".database.windows.net"),
238+
AZURE_GERMAN_ENDPOINT(".database.cloudapi.de"),
239+
AZURE_USGOV_ENDPOINT(".database.usgovcloudapi.net"),
240+
AZURE_CHINA_ENDPOINT(".database.china.cloudapi.cn");
241+
242+
private static final String ON_DEMAND_PREFIX = "-ondemand";
243+
private static final String AZURE_SYNAPSE = "-ondemand.sql.azuresynapse.";
244+
245+
private final String endpoint;
246+
247+
private AzureSQLServerEndpoints(String endpoint) {
248+
this.endpoint = endpoint;
249+
}
250+
251+
static boolean isAzureSqlServerEndpoint(String endpoint) {
252+
for (AzureSQLServerEndpoints e : AzureSQLServerEndpoints.values()) {
253+
if (endpoint.endsWith(e.toString())) {
254+
return true;
255+
}
256+
}
257+
return false;
258+
}
259+
260+
static boolean isAzureSynapseOnDemandEndpoint(String endpoint) {
261+
if (endpoint.contains(AZURE_SYNAPSE)) {
262+
return true;
263+
}
264+
265+
for (AzureSQLServerEndpoints e : AzureSQLServerEndpoints.values()) {
266+
if (endpoint.endsWith(ON_DEMAND_PREFIX + e.toString())) {
267+
return true;
268+
}
269+
}
270+
return false;
271+
}
272+
273+
@Override
274+
public String toString() {
275+
return endpoint;
276+
}
277+
}
278+
233279
/** flag indicating whether server is Azure */
234280
private Boolean isAzure = null;
235281

@@ -665,6 +711,13 @@ private enum State {
665711
*/
666712
private final static int INTERMITTENT_TLS_MAX_RETRY = 5;
667713

714+
/**
715+
* Defaults for Azure SQL Server retry counts
716+
*
717+
*/
718+
private final static int AZURE_SERVER_ENDPOINT_RETRY_COUNT_DEFAULT = 2;
719+
private final static int AZURE_SYNAPSE_ONDEMAND_ENDPOINT_RETRY_COUNT_DEFAFULT = 5;
720+
668721
/** Indicates if we received a routing ENVCHANGE in the current connection attempt */
669722
private boolean isRoutedInCurrentAttempt = false;
670723

@@ -997,7 +1050,8 @@ public void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDate
9971050
this.ignoreOffsetOnDateTimeOffsetConversion = ignoreOffsetOnDateTimeOffsetConversion;
9981051
}
9991052

1000-
private boolean calcBigDecimalPrecision = SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_PRECISION.getDefaultValue();
1053+
private boolean calcBigDecimalPrecision = SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_PRECISION
1054+
.getDefaultValue();
10011055

10021056
@Override
10031057
public boolean getCalcBigDecimalPrecision() {
@@ -1838,6 +1892,21 @@ void validateMaxSQLLoginName(String propName, String propValue) throws SQLServer
18381892
}
18391893
}
18401894

1895+
/**
1896+
* sleep for ms interval
1897+
*
1898+
* @param interval
1899+
* in ms
1900+
*/
1901+
private void sleepForInterval(long interval) {
1902+
try {
1903+
Thread.sleep(interval);
1904+
} catch (InterruptedException e) {
1905+
// re-interrupt the current thread, in order to restore the thread's interrupt status.
1906+
Thread.currentThread().interrupt();
1907+
}
1908+
}
1909+
18411910
Connection connect(Properties propsIn, SQLServerPooledConnection pooledConnection) throws SQLServerException {
18421911
int loginTimeoutSeconds = SQLServerDriverIntProperty.LOGIN_TIMEOUT.getDefaultValue();
18431912
if (propsIn != null) {
@@ -1936,12 +2005,8 @@ Connection connect(Properties propsIn, SQLServerPooledConnection pooledConnectio
19362005
+ sqlServerError.getErrorNumber() + ". Wait for connectRetryInterval("
19372006
+ connectRetryInterval + ")s before retry.");
19382007
}
1939-
try {
1940-
Thread.sleep(TimeUnit.SECONDS.toMillis(connectRetryInterval));
1941-
} catch (InterruptedException ex) {
1942-
// re-interrupt the current thread, in order to restore the thread's interrupt status.
1943-
Thread.currentThread().interrupt();
1944-
}
2008+
2009+
sleepForInterval(TimeUnit.SECONDS.toMillis(connectRetryInterval));
19452010
}
19462011
}
19472012
}
@@ -2045,6 +2110,63 @@ int validateTimeout(SQLServerDriverIntProperty property) throws SQLServerExcepti
20452110
return timeout;
20462111
}
20472112

2113+
// Helper to validate connection retry properties
2114+
void validateConnectionRetry() throws SQLServerException {
2115+
// validate retry count
2116+
connectRetryCount = SQLServerDriverIntProperty.CONNECT_RETRY_COUNT.getDefaultValue();
2117+
String sPropValue = activeConnectionProperties
2118+
.getProperty(SQLServerDriverIntProperty.CONNECT_RETRY_COUNT.toString());
2119+
if (null != sPropValue && sPropValue.length() > 0) {
2120+
try {
2121+
connectRetryCount = Integer.parseInt(sPropValue);
2122+
if (!SQLServerDriverIntProperty.CONNECT_RETRY_COUNT.isValidValue(connectRetryCount)) {
2123+
MessageFormat form = new MessageFormat(
2124+
SQLServerException.getErrString("R_invalidConnectRetryCount"));
2125+
Object[] msgArgs = {sPropValue};
2126+
SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
2127+
}
2128+
2129+
} catch (NumberFormatException e) {
2130+
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidConnectRetryCount"));
2131+
Object[] msgArgs = {sPropValue};
2132+
SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
2133+
}
2134+
} else {
2135+
// if property was not set, detect and increase for Azure endpoints
2136+
if (connectRetryCount == 1) {
2137+
2138+
// Set to larger default value for Azure connections to greatly improve recovery
2139+
if (isAzureSynapseOnDemandEndpoint()) {
2140+
connectRetryCount = AZURE_SERVER_ENDPOINT_RETRY_COUNT_DEFAULT;
2141+
} else if (isAzureSqlServerEndpoint()) {
2142+
connectRetryCount = AZURE_SYNAPSE_ONDEMAND_ENDPOINT_RETRY_COUNT_DEFAFULT;
2143+
}
2144+
}
2145+
}
2146+
2147+
// validate retry interval
2148+
connectRetryInterval = SQLServerDriverIntProperty.CONNECT_RETRY_INTERVAL.getDefaultValue();
2149+
sPropValue = activeConnectionProperties
2150+
.getProperty(SQLServerDriverIntProperty.CONNECT_RETRY_INTERVAL.toString());
2151+
if (null != sPropValue && sPropValue.length() > 0) {
2152+
try {
2153+
connectRetryInterval = Integer.parseInt(sPropValue);
2154+
if (!SQLServerDriverIntProperty.CONNECT_RETRY_INTERVAL.isValidValue(connectRetryInterval)) {
2155+
MessageFormat form = new MessageFormat(
2156+
SQLServerException.getErrString("R_invalidConnectRetryInterval"));
2157+
Object[] msgArgs = {sPropValue};
2158+
SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
2159+
}
2160+
2161+
} catch (NumberFormatException e) {
2162+
MessageFormat form = new MessageFormat(
2163+
SQLServerException.getErrString("R_invalidConnectRetryInterval"));
2164+
Object[] msgArgs = {sPropValue};
2165+
SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
2166+
}
2167+
}
2168+
}
2169+
20482170
/**
20492171
* Establish a physical database connection based on the user specified connection properties. Logon to the
20502172
* database.
@@ -3045,46 +3167,7 @@ else if (0 == requestedPacketSize)
30453167

30463168
String mirror = (null == fo) ? failOverPartnerPropertyValue : null;
30473169

3048-
connectRetryCount = SQLServerDriverIntProperty.CONNECT_RETRY_COUNT.getDefaultValue();
3049-
sPropValue = activeConnectionProperties
3050-
.getProperty(SQLServerDriverIntProperty.CONNECT_RETRY_COUNT.toString());
3051-
if (null != sPropValue && sPropValue.length() > 0) {
3052-
try {
3053-
connectRetryCount = Integer.parseInt(sPropValue);
3054-
} catch (NumberFormatException e) {
3055-
MessageFormat form = new MessageFormat(
3056-
SQLServerException.getErrString("R_invalidConnectRetryCount"));
3057-
Object[] msgArgs = {sPropValue};
3058-
SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
3059-
}
3060-
if (connectRetryCount < 0 || connectRetryCount > 255) {
3061-
MessageFormat form = new MessageFormat(
3062-
SQLServerException.getErrString("R_invalidConnectRetryCount"));
3063-
Object[] msgArgs = {sPropValue};
3064-
SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
3065-
}
3066-
}
3067-
3068-
connectRetryInterval = SQLServerDriverIntProperty.CONNECT_RETRY_INTERVAL.getDefaultValue();
3069-
sPropValue = activeConnectionProperties
3070-
.getProperty(SQLServerDriverIntProperty.CONNECT_RETRY_INTERVAL.toString());
3071-
if (null != sPropValue && sPropValue.length() > 0) {
3072-
try {
3073-
connectRetryInterval = Integer.parseInt(sPropValue);
3074-
} catch (NumberFormatException e) {
3075-
MessageFormat form = new MessageFormat(
3076-
SQLServerException.getErrString("R_invalidConnectRetryInterval"));
3077-
Object[] msgArgs = {sPropValue};
3078-
SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
3079-
}
3080-
3081-
if (connectRetryInterval < 1 || connectRetryInterval > 60) {
3082-
MessageFormat form = new MessageFormat(
3083-
SQLServerException.getErrString("R_invalidConnectRetryInterval"));
3084-
Object[] msgArgs = {sPropValue};
3085-
SQLServerException.makeFromDriverError(this, this, form.format(msgArgs), null, false);
3086-
}
3087-
}
3170+
validateConnectionRetry();
30883171

30893172
long startTime = System.currentTimeMillis();
30903173
sessionRecovery.setLoginParameters(instanceValue, nPort, fo,
@@ -3148,7 +3231,7 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu
31483231

31493232
final boolean isDBMirroring = null != mirror || null != foActual;
31503233

3151-
int sleepInterval = BACKOFF_INTERVAL; // milliseconds to sleep (back off) between attempts.
3234+
int fedauthRetryInterval = BACKOFF_INTERVAL; // milliseconds to sleep (back off) between attempts.
31523235

31533236
long timeoutUnitInterval;
31543237

@@ -3407,7 +3490,7 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu
34073490
// Check sleep interval to make sure we won't exceed the timeout
34083491
// Do this in the catch block so we can re-throw the current exception
34093492
long remainingMilliseconds = timerRemaining(timerExpire);
3410-
if (remainingMilliseconds <= sleepInterval) {
3493+
if (remainingMilliseconds <= fedauthRetryInterval) {
34113494

34123495
if (loggerResiliency.isLoggable(Level.FINER)) {
34133496
loggerResiliency.finer(toString() + " Connection open - connection failed on attempt: "
@@ -3441,13 +3524,9 @@ private void login(String primary, String primaryInstanceName, int primaryPortNu
34413524
+ ". Wait for connectRetryInterval(" + connectRetryInterval + ")s before retry #"
34423525
+ attemptNumber);
34433526
}
3444-
try {
3445-
Thread.sleep(sleepInterval);
3446-
} catch (InterruptedException e) {
3447-
// re-interrupt the current thread, in order to restore the thread's interrupt status.
3448-
Thread.currentThread().interrupt();
3449-
}
3450-
sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
3527+
3528+
sleepForInterval(fedauthRetryInterval);
3529+
fedauthRetryInterval = (fedauthRetryInterval < 500) ? fedauthRetryInterval * 2 : 1000;
34513530
}
34523531

34533532
// Update timeout interval (but no more than the point where we're supposed to fail: timerExpire)
@@ -5896,7 +5975,7 @@ private SqlAuthenticationToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throw
58965975

58975976
String user = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString());
58985977

5899-
// No:of milliseconds to sleep for the initial back off.
5978+
// No of milliseconds to sleep for the initial back off.
59005979
int sleepInterval = BACKOFF_INTERVAL;
59015980

59025981
if (!msalContextExists()
@@ -6025,12 +6104,7 @@ private SqlAuthenticationToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throw
60256104
+ millisecondsRemaining + " milliseconds.");
60266105
}
60276106

6028-
try {
6029-
Thread.sleep(sleepInterval);
6030-
} catch (InterruptedException e1) {
6031-
// re-interrupt the current thread, in order to restore the thread's interrupt status.
6032-
Thread.currentThread().interrupt();
6033-
}
6107+
sleepForInterval(sleepInterval);
60346108
sleepInterval = sleepInterval * 2;
60356109
}
60366110
}
@@ -8286,6 +8360,34 @@ boolean isAzureMI() {
82868360
return isAzureMI;
82878361
}
82888362

8363+
boolean isAzureSqlServerEndpoint() {
8364+
String serverName = activeConnectionProperties
8365+
.getProperty(SQLServerDriverStringProperty.SERVER_NAME.toString());
8366+
if (null != serverName && serverName.length() > 0) {
8367+
// serverName without named instance
8368+
int px = serverName.indexOf('\\');
8369+
String parsedServerName = (px >= 0) ? serverName.substring(0, px) : serverName;
8370+
8371+
return AzureSQLServerEndpoints.isAzureSqlServerEndpoint(parsedServerName);
8372+
}
8373+
8374+
return false;
8375+
}
8376+
8377+
boolean isAzureSynapseOnDemandEndpoint() {
8378+
String serverName = activeConnectionProperties
8379+
.getProperty(SQLServerDriverStringProperty.SERVER_NAME.toString());
8380+
if (null != serverName && serverName.length() > 0) {
8381+
// serverName without named instance
8382+
int px = serverName.indexOf('\\');
8383+
String parsedServerName = (px >= 0) ? serverName.substring(0, px) : serverName;
8384+
8385+
return AzureSQLServerEndpoints.isAzureSqlServerEndpoint(parsedServerName);
8386+
}
8387+
8388+
return false;
8389+
}
8390+
82898391
/**
82908392
* Checks if the connection established to server supports transactions.
82918393
*

0 commit comments

Comments
 (0)