diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java b/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java index ed5591034..49f2b7dc0 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java @@ -39,6 +39,10 @@ public class ConfigurableRetryLogic { private static final String FORWARD_SLASH = "/"; private static final String EQUALS_SIGN = "="; private static final String RETRY_EXEC = "retryExec"; + private static final String RETRY_CONN = "retryConn"; + private static final String STATEMENT = "statement"; + private static final String CONNECTION = "connection"; + private static boolean replaceFlag = false; // Are we replacing the list of transient errors? /** * The time the properties file was last modified. */ @@ -52,14 +56,23 @@ public class ConfigurableRetryLogic { */ private static final AtomicReference lastQuery = new AtomicReference<>(""); /** - * The previously read rules from the connection string. + * The previously read statement rules from the connection string. */ - private static final AtomicReference prevRulesFromConnectionString = new AtomicReference<>(""); + private static final AtomicReference prevStmtRulesFromConnString = new AtomicReference<>(""); + /** + * The previously read connection rules from the connection string. + */ + private static final AtomicReference prevConnRulesFromConnString = new AtomicReference<>(""); /** * The list of statement retry rules. */ private static final AtomicReference> stmtRules = new AtomicReference<>( new HashMap<>()); + /** + * The list of connection retry rules. + */ + private static final AtomicReference> connRules = new AtomicReference<>( + new HashMap<>()); private static ConfigurableRetryLogic singleInstance; /** @@ -70,7 +83,8 @@ public class ConfigurableRetryLogic { */ private ConfigurableRetryLogic() throws SQLServerException { timeLastRead.compareAndSet(0, new Date().getTime()); - setUpRules(null); + setUpRules(null, STATEMENT); + setUpRules(null, CONNECTION); } /** @@ -102,7 +116,8 @@ public static ConfigurableRetryLogic getInstance() throws SQLServerException { /** * If it has been INTERVAL_BETWEEN_READS_IN_MS (30 secs) since last read, see if we last did a file read, if so - * only reread if the file has been modified. If no file read, set up rules using the prev. connection string rules. + * only reread if the file has been modified. If no file read, set up rules using the previous connection + * string (statement and connection) rules * * @throws SQLServerException * when an exception occurs @@ -116,10 +131,12 @@ private static void refreshRuleSet() throws SQLServerException { // If timeLastModified is set, we previously read from file, so we setUpRules also reading from file File f = new File(getCurrentClassPath()); if (f.lastModified() != timeLastModified.get()) { - setUpRules(null); + setUpRules(null, STATEMENT); + setUpRules(null, CONNECTION); } } else { - setUpRules(prevRulesFromConnectionString.get()); + setUpRules(prevStmtRulesFromConnString.get(), STATEMENT); + setUpRules(prevConnRulesFromConnString.get(), CONNECTION); } } } @@ -133,8 +150,10 @@ private static void refreshRuleSet() throws SQLServerException { * when an exception occurs */ void setFromConnectionString(String newRules) throws SQLServerException { - prevRulesFromConnectionString.set(newRules); - setUpRules(prevRulesFromConnectionString.get()); + prevStmtRulesFromConnString.set(newRules); + prevConnRulesFromConnString.set(newRules); + setUpRules(prevStmtRulesFromConnString.get(), STATEMENT); + setUpRules(prevConnRulesFromConnString.get(), CONNECTION); } /** @@ -160,23 +179,28 @@ String getLastQuery() { * Sets up rules based on either connection string option or file read. * * @param cxnStrRules - * if null, rules are constructed from file, else, this parameter is used to construct rules + * if null, rules are constructed from file, else, this parameter is used to construct rules + * @param ruleType + * either "statement" or "connection" for statement or connection rules respectively * @throws SQLServerException - * if an exception occurs + * if an exception occurs */ - private static void setUpRules(String cxnStrRules) throws SQLServerException { + private static void setUpRules(String cxnStrRules, String ruleType) throws SQLServerException { LinkedList temp; - - stmtRules.set(new HashMap<>()); lastQuery.set(""); if (cxnStrRules == null || cxnStrRules.isEmpty()) { - temp = readFromFile(); + if (ruleType.equals(STATEMENT)) { + temp = readFromFile(RETRY_EXEC); + } else { + temp = readFromFile(RETRY_CONN); + } + } else { temp = new LinkedList<>(); Collections.addAll(temp, cxnStrRules.split(SEMI_COLON)); } - createRules(temp); + createRules(temp, ruleType); } /** @@ -184,10 +208,13 @@ private static void setUpRules(String cxnStrRules) throws SQLServerException { * * @param listOfRules * the list of rules, as a String LinkedList + * @param ruleType + * the type of rule; either "statement" or "connection * @throws SQLServerException * if unable to create rules from the inputted list */ - private static void createRules(LinkedList listOfRules) throws SQLServerException { + private static void createRules(LinkedList listOfRules, String ruleType) throws SQLServerException { + connRules.set(new HashMap<>()); stmtRules.set(new HashMap<>()); for (String potentialRule : listOfRules) { @@ -198,10 +225,29 @@ private static void createRules(LinkedList listOfRules) throws SQLServer for (String retryError : arr) { ConfigurableRetryRule splitRule = new ConfigurableRetryRule(retryError, rule); - stmtRules.get().put(Integer.parseInt(splitRule.getError()), splitRule); + if (rule.isConnection) { +// if (rule.replaceExisting) { +// if (!replaceFlag) { +// connRules.set(new HashMap<>()); +// } +// replaceFlag = true; +// } + connRules.get().put(Integer.parseInt(splitRule.getError()), splitRule); + } else { + stmtRules.get().put(Integer.parseInt(splitRule.getError()), splitRule); + } + } } else { - stmtRules.get().put(Integer.parseInt(rule.getError()), rule); + if (rule.isConnection) { +// if (rule.replaceExisting) { +// connRules.set(new HashMap<>()); +// replaceFlag = true; +// } + connRules.get().put(Integer.parseInt(rule.getError()), rule); + } else { + stmtRules.get().put(Integer.parseInt(rule.getError()), rule); + } } } } @@ -241,7 +287,7 @@ private static String getCurrentClassPath() throws SQLServerException { * @throws SQLServerException * if unable to read from the file */ - private static LinkedList readFromFile() throws SQLServerException { + private static LinkedList readFromFile(String connectionStringProperty) throws SQLServerException { String filePath = getCurrentClassPath(); LinkedList list = new LinkedList<>(); @@ -250,7 +296,7 @@ private static LinkedList readFromFile() throws SQLServerException { try (BufferedReader buffer = new BufferedReader(new FileReader(f))) { String readLine; while ((readLine = buffer.readLine()) != null) { - if (readLine.startsWith(RETRY_EXEC)) { + if (readLine.startsWith(connectionStringProperty)) { // Either "retryExec" or "retryConn" String value = readLine.split(EQUALS_SIGN)[1]; Collections.addAll(list, value.split(SEMI_COLON)); } @@ -280,13 +326,25 @@ private static LinkedList readFromFile() throws SQLServerException { * @throws SQLServerException * when an exception occurs */ - ConfigurableRetryRule searchRuleSet(int ruleToSearchFor) throws SQLServerException { + ConfigurableRetryRule searchRuleSet(int ruleToSearchFor, String ruleSet) throws SQLServerException { refreshRuleSet(); - for (Map.Entry entry : stmtRules.get().entrySet()) { - if (entry.getKey() == ruleToSearchFor) { - return entry.getValue(); + if (ruleSet.equals(STATEMENT)) { + for (Map.Entry entry : stmtRules.get().entrySet()) { + if (entry.getKey() == ruleToSearchFor) { + return entry.getValue(); + } + } + } else { + for (Map.Entry entry : connRules.get().entrySet()) { + if (entry.getKey() == ruleToSearchFor) { + return entry.getValue(); + } } } return null; } + + boolean getReplaceFlag() { + return replaceFlag; + } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryRule.java b/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryRule.java index f52df8d8d..fd3b258da 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryRule.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryRule.java @@ -25,6 +25,8 @@ class ConfigurableRetryRule { private int retryCount = 1; private String retryQueries = ""; private String retryError; + boolean isConnection = false; + boolean replaceExisting = false; private ArrayList waitTimes = new ArrayList<>(); @@ -70,6 +72,18 @@ private void copyFromRule(ConfigurableRetryRule baseRule) { this.retryCount = baseRule.retryCount; this.retryQueries = baseRule.retryQueries; this.waitTimes = baseRule.waitTimes; + this.isConnection = baseRule.isConnection; + } + + private String appendOrReplace(String retryError) { + if (retryError.charAt(0) == '+') { + replaceExisting = false; + StringUtils.isNumeric(retryError.substring(1)); + return retryError.substring(1); + } else { + replaceExisting = true; + return retryError; + } } /** @@ -152,7 +166,12 @@ private void checkParameter(String value) throws SQLServerException { * if a rule or parameter has invalid inputs */ private void addElements(String[] rule) throws SQLServerException { - if (rule.length == 2 || rule.length == 3) { + if (rule.length == 1) { + String errorWithoutOptionalPrefix = appendOrReplace(rule[0]); + checkParameter(errorWithoutOptionalPrefix); + isConnection = true; + retryError = errorWithoutOptionalPrefix; + } else if (rule.length == 2 || rule.length == 3) { checkParameter(rule[0]); retryError = rule[0]; String[] timings = rule[1].split(COMMA); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java index 66fc33744..ec7067220 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java @@ -1363,6 +1363,22 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource { */ String getRetryExec(); + /** + * Returns value of 'retryConn' from Connection String. + * + * @param retryConn + * Set of rules used for connection retry + */ + void setRetryConn(String retryConn); + + /** + * Sets the value for 'retryConn' property + * + * @return retryConn + * String value + */ + String getRetryConn(); + /** * useFlexibleCallableStatements is temporarily removed. This is meant as a no-op. * diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 903adc0b0..543908b92 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1086,6 +1086,28 @@ public void setRetryExec(String retryExec) { this.retryExec = retryExec; } + private String retryConn = SQLServerDriverStringProperty.RETRY_CONN.getDefaultValue(); + + /** + * Returns the set of configurable connection retry rules set in retryConn + * + * @return + * A string containing statement retry rules. + */ + public String getRetryConn() { + return retryConn; + } + + /** + * Sets the list of configurable connection retry rules, for the given connection, in retryConn. + * + * @param retryConn + * The list of retry rules to set, as a string. + */ + public void setRetryConn(String retryConn) { + this.retryConn = retryConn; + } + /** Session Recovery Object */ private transient IdleConnectionResiliency sessionRecovery = new IdleConnectionResiliency(this); @@ -2024,10 +2046,23 @@ Connection connect(Properties propsIn, SQLServerPooledConnection pooledConnectio } throw e; } else { - // only retry if transient error + // Only retry if matches configured CRL rules, or transient error (if CRL is not in use) SQLServerError sqlServerError = e.getSQLServerError(); - if (!TransientError.isTransientError(sqlServerError)) { + if (null == sqlServerError) { throw e; + } else { + ConfigurableRetryRule rule = ConfigurableRetryLogic.getInstance() + .searchRuleSet(sqlServerError.getErrorNumber(), "connection"); + + if (null == rule) { + if (ConfigurableRetryLogic.getInstance().getReplaceFlag()) { + throw e; + } else { + if (!TransientError.isTransientError(sqlServerError)) { + throw e; + } + } + } } // check if there's time to retry, no point to wait if no time left diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index 6136cffa0..da7688e60 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -1383,16 +1383,48 @@ public boolean getCalcBigDecimalPrecision() { SQLServerDriverBooleanProperty.CALC_BIG_DECIMAL_PRECISION.getDefaultValue()); } + /** + * Sets the 'retryExec' setting. + * + * @param retryExec + * String property giving the custom statement retry rules to use for configurable retry logic + */ @Override public void setRetryExec(String retryExec) { setStringProperty(connectionProps, SQLServerDriverStringProperty.RETRY_EXEC.toString(), retryExec); } + /** + * Returns the value for 'retryExec'. + * + * @return retryExec String value + */ @Override public String getRetryExec() { return getStringProperty(connectionProps, SQLServerDriverStringProperty.RETRY_EXEC.toString(), null); } + /** + * Sets the 'retryConn' setting. + * + * @param retryConn + * String property giving the custom connection retry rules to use for configurable retry logic + */ + @Override + public void setRetryConn(String retryConn) { + setStringProperty(connectionProps, SQLServerDriverStringProperty.RETRY_CONN.toString(), retryConn); + } + + /** + * Returns the value for 'retryConn'. + * + * @return retryConn String value + */ + @Override + public String getRetryConn() { + return getStringProperty(connectionProps, SQLServerDriverStringProperty.RETRY_CONN.toString(), null); + } + /** * Sets a property string value. * diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 1c9abc000..e4b1d59ee 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -611,7 +611,8 @@ enum SQLServerDriverStringProperty { SERVER_CERTIFICATE("serverCertificate", ""), DATETIME_DATATYPE("datetimeParameterType", DatetimeType.DATETIME2.toString()), ACCESS_TOKEN_CALLBACK_CLASS("accessTokenCallbackClass", ""), - RETRY_EXEC("retryExec", ""); + RETRY_EXEC("retryExec", ""), + RETRY_CONN("retryConn", ""); private final String name; private final String defaultValue; @@ -855,6 +856,8 @@ public final class SQLServerDriver implements java.sql.Driver { SQLServerDriverStringProperty.ACCESS_TOKEN_CALLBACK_CLASS.getDefaultValue(), false, null), new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.RETRY_EXEC.toString(), SQLServerDriverStringProperty.RETRY_EXEC.getDefaultValue(), false, null), + new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.RETRY_CONN.toString(), + SQLServerDriverStringProperty.RETRY_CONN.getDefaultValue(), false, null), new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.REPLICATION.toString(), Boolean.toString(SQLServerDriverBooleanProperty.REPLICATION.getDefaultValue()), false, TRUE_FALSE), new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.toString(), diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 55a5a5fc0..c9d875e58 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -517,7 +517,8 @@ protected Object[][] getContents() { {"R_InvalidCSVQuotes", "Failed to parse the CSV file, verify that the fields are correctly enclosed in double quotes."}, {"R_TokenRequireUrl", "Token credentials require a URL using the HTTPS protocol scheme."}, {"R_calcBigDecimalPrecisionPropertyDescription", "Indicates whether the driver should calculate precision for big decimal values."}, - {"R_retryExecPropertyDescription", "List of rules to follow for configurable retry logic."}, + {"R_retryExecPropertyDescription", "List of statement retry rules to follow for configurable retry logic."}, + {"R_retryConnPropertyDescription", "List of connection retry rules to follow for configurable retry logic."}, {"R_maxResultBufferPropertyDescription", "Determines maximum amount of bytes that can be read during retrieval of result set"}, {"R_maxResultBufferInvalidSyntax", "Invalid syntax: {0} in maxResultBuffer parameter."}, {"R_maxResultBufferNegativeParameterValue", "MaxResultBuffer must have positive value: {0}."}, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 883320640..fef51bc6a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -256,7 +256,7 @@ final void executeStatement(TDSCommand newStmtCmd) throws SQLServerException, SQ ConfigurableRetryRule rule = null; if (null != sqlServerError) { - rule = crl.searchRuleSet(e.getSQLServerError().getErrorNumber()); + rule = crl.searchRuleSet(e.getSQLServerError().getErrorNumber(), "statement"); } // If there is a rule for this error AND we still have retries remaining THEN we can proceed, otherwise diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java index 3b7fe1dc7..33654216f 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java @@ -212,6 +212,9 @@ public void testDataSource() throws SQLServerException { ds.setRetryExec(stringPropValue); assertEquals(stringPropValue, ds.getRetryExec(), TestResource.getResource("R_valuesAreDifferent")); + ds.setRetryConn(stringPropValue); + assertEquals(stringPropValue, ds.getRetryConn(), TestResource.getResource("R_valuesAreDifferent")); + ds.setServerCertificate(stringPropValue); assertEquals(stringPropValue, ds.getServerCertificate(), TestResource.getResource("R_valuesAreDifferent")); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/configurableretry/ConfigurableRetryLogicTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/configurableretry/ConfigurableRetryLogicTest.java index 169986a0a..9e2c80c19 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/configurableretry/ConfigurableRetryLogicTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/configurableretry/ConfigurableRetryLogicTest.java @@ -5,9 +5,6 @@ package com.microsoft.sqlserver.jdbc.configurableretry; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - import java.io.File; import java.io.FileWriter; import java.sql.CallableStatement; @@ -30,9 +27,11 @@ import com.microsoft.sqlserver.testframework.AbstractSQLGenerator; import com.microsoft.sqlserver.testframework.AbstractTest; +import static org.junit.jupiter.api.Assertions.*; + /** - * Test statement retry for configurable retry logic. + * Test connection and statement retry for configurable retry logic. */ public class ConfigurableRetryLogicTest extends AbstractTest { /** @@ -66,6 +65,31 @@ public void testRetryExecConnectionStringOption() throws Exception { String test = conn.getRetryExec(); assertTrue(test.isEmpty()); conn.setRetryExec("{2714:3,2*2:CREATE;2715:1,3}"); + test = conn.getRetryExec(); + assertFalse(test.isEmpty()); + try { + PreparedStatement ps = conn.prepareStatement("create table " + CRLTestTable + " (c1 int null);"); + createTable(s); + ps.execute(); + Assertions.fail(TestResource.getResource("R_expectedFailPassed")); + } catch (SQLServerException e) { + assertTrue(e.getMessage().startsWith("There is already an object"), + TestResource.getResource("R_unexpectedExceptionContent") + ": " + e.getMessage()); + } finally { + dropTable(s); + } + } + } + + @Test + public void testRetryConnConnectionStringOption() throws Exception { + try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionString); + Statement s = conn.createStatement()) { + String test = conn.getRetryConn(); + assertTrue(test.isEmpty()); + conn.setRetryConn("{4060}"); + test = conn.getRetryConn(); + assertFalse(test.isEmpty()); try { PreparedStatement ps = conn.prepareStatement("create table " + CRLTestTable + " (c1 int null);"); createTable(s); @@ -172,6 +196,29 @@ public void testStatementRetryWithShortQueryTimeout(String addedRetryParams) thr } } + /** + * Tests connection retry. Used in other tests. + * + * @throws Exception + * if unable to connect or execute against db + */ + public void testConnectionRetry(String replacedDbName, String addedRetryParams) throws Exception { + String cxnString = connectionString + addedRetryParams; + cxnString = TestUtils.addOrOverrideProperty(cxnString, "database", replacedDbName); + + try (Connection conn = DriverManager.getConnection(cxnString); Statement s = conn.createStatement()) { + try { + fail(TestResource.getResource("R_expectedFailPassed")); + } catch (Exception e) { + System.out.println("blah"); + assertTrue(e.getMessage().startsWith("There is already an object"), + TestResource.getResource("R_unexpectedExceptionContent") + ": " + e.getMessage()); + } finally { + dropTable(s); + } + } + } + /** * Tests that the correct number of retries are happening for all statement scenarios. Tests are expected to take * a minimum of the sum of whatever has been defined for the waiting intervals, and maximum of the previous sum @@ -445,6 +492,94 @@ public void testRetryChange() throws Exception { } } + /** + * Tests that the correct number of retries are happening for all connection scenarios. Tests are expected to take + * a minimum of the sum of whatever has been defined for the waiting intervals, and maximum of the previous sum + * plus some amount of time to account for test environment slowness. + */ + @Test + public void connectionTimingTest() { + long totalTime; + long timerStart = System.currentTimeMillis(); + long expectedMaxTime = 10; + + // No retries since CRL rules override, expected time ~1 second + try { + testConnectionRetry("blah", "retryConn={9999};"); + } catch (Exception e) { + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase())), + e.getMessage()); + + if (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) { + // Only check the timing if the correct error, "cannot open database", is returned. + totalTime = System.currentTimeMillis() - timerStart; + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(expectedMaxTime), + "total time: " + totalTime + ", expected time: " + TimeUnit.SECONDS.toMillis(expectedMaxTime)); + } + } + + timerStart = System.currentTimeMillis(); + long expectedMinTime = 20; + expectedMaxTime = 30; + + // (0s attempt + 10s wait + 0s attempt) * 2 due to current driver bug = expected 20s execution time + try { + testConnectionRetry("blah", "retryConn={4060};"); + } catch (Exception e) { + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase())), + e.getMessage()); + + if (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) { + // Only check the timing if the correct error, "cannot open database", is returned. + totalTime = System.currentTimeMillis() - timerStart; + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(expectedMaxTime), "total time: " + totalTime + + ", expected max time: " + TimeUnit.SECONDS.toMillis(expectedMaxTime)); + assertTrue(totalTime > TimeUnit.SECONDS.toMillis(expectedMinTime), "total time: " + totalTime + + ", expected min time: " + TimeUnit.SECONDS.toMillis(expectedMinTime)); + } + } + + timerStart = System.currentTimeMillis(); + + // Append should work the same way + try { + testConnectionRetry("blah", "retryConn={+4060,4070};"); + } catch (Exception e) { + assertTrue( + (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) + || (TestUtils.getProperty(connectionString, "msiClientId") != null && e.getMessage() + .toLowerCase().contains(TestResource.getResource("R_loginFailedMI").toLowerCase())) + || ((isSqlAzure() || isSqlAzureDW()) && e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_connectTimedOut").toLowerCase())), + e.getMessage()); + + if (e.getMessage().toLowerCase() + .contains(TestResource.getResource("R_cannotOpenDatabase").toLowerCase())) { + // Only check the timing if the correct error, "cannot open database", is returned. + totalTime = System.currentTimeMillis() - timerStart; + assertTrue(totalTime < TimeUnit.SECONDS.toMillis(expectedMaxTime), "total time: " + totalTime + + ", expected max time: " + TimeUnit.SECONDS.toMillis(expectedMaxTime)); + assertTrue(totalTime > TimeUnit.SECONDS.toMillis(expectedMinTime), "total time: " + totalTime + + ", expected min time: " + TimeUnit.SECONDS.toMillis(expectedMinTime)); + } + } + } + /** * Creates table for use in ConfigurableRetryLogic tests. * diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java index 8d3a62788..402cccc8c 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java @@ -523,6 +523,8 @@ private List getVerifiedMethodNames() { verifiedMethodNames.add("removeBeforeReconnectListener"); verifiedMethodNames.add("getRetryExec"); verifiedMethodNames.add("setRetryExec"); + verifiedMethodNames.add("getRetryConn"); + verifiedMethodNames.add("setRetryConn"); verifiedMethodNames.add("getUseFlexibleCallableStatements"); verifiedMethodNames.add("setUseFlexibleCallableStatements"); return verifiedMethodNames;