Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable Retry Logic II - Connection Retry #2519

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 82 additions & 24 deletions src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
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.
*/
Expand All @@ -52,14 +56,23 @@
*/
private static final AtomicReference<String> lastQuery = new AtomicReference<>("");
/**
* The previously read rules from the connection string.
* The previously read statement rules from the connection string.
*/
private static final AtomicReference<String> prevRulesFromConnectionString = new AtomicReference<>("");
private static final AtomicReference<String> prevStmtRulesFromConnString = new AtomicReference<>("");
/**
* The previously read connection rules from the connection string.
*/
private static final AtomicReference<String> prevConnRulesFromConnString = new AtomicReference<>("");
/**
* The list of statement retry rules.
*/
private static final AtomicReference<HashMap<Integer, ConfigurableRetryRule>> stmtRules = new AtomicReference<>(
new HashMap<>());
/**
* The list of connection retry rules.
*/
private static final AtomicReference<HashMap<Integer, ConfigurableRetryRule>> connRules = new AtomicReference<>(
new HashMap<>());
private static ConfigurableRetryLogic singleInstance;

/**
Expand All @@ -70,7 +83,8 @@
*/
private ConfigurableRetryLogic() throws SQLServerException {
timeLastRead.compareAndSet(0, new Date().getTime());
setUpRules(null);
setUpRules(null, STATEMENT);
setUpRules(null, CONNECTION);
}

/**
Expand Down Expand Up @@ -102,7 +116,8 @@

/**
* 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
Expand All @@ -116,10 +131,12 @@
// 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);
}
}
}
Expand All @@ -133,8 +150,10 @@
* 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);
}

/**
Expand All @@ -160,34 +179,42 @@
* 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<String> 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);
}

/**
* Creates and stores rules based on the inputted list of rules.
*
* @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<String> listOfRules) throws SQLServerException {
private static void createRules(LinkedList<String> listOfRules, String ruleType) throws SQLServerException {
connRules.set(new HashMap<>());
stmtRules.set(new HashMap<>());

for (String potentialRule : listOfRules) {
Expand All @@ -198,10 +225,29 @@

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);

Check warning on line 235 in src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java#L235

Added line #L235 was not covered by tests
} 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);

Check warning on line 247 in src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java#L247

Added line #L247 was not covered by tests
} else {
stmtRules.get().put(Integer.parseInt(rule.getError()), rule);
}
}
}
}
Expand Down Expand Up @@ -241,7 +287,7 @@
* @throws SQLServerException
* if unable to read from the file
*/
private static LinkedList<String> readFromFile() throws SQLServerException {
private static LinkedList<String> readFromFile(String connectionStringProperty) throws SQLServerException {
String filePath = getCurrentClassPath();
LinkedList<String> list = new LinkedList<>();

Expand All @@ -250,7 +296,7 @@
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));
}
Expand Down Expand Up @@ -280,13 +326,25 @@
* @throws SQLServerException
* when an exception occurs
*/
ConfigurableRetryRule searchRuleSet(int ruleToSearchFor) throws SQLServerException {
ConfigurableRetryRule searchRuleSet(int ruleToSearchFor, String ruleSet) throws SQLServerException {
refreshRuleSet();
for (Map.Entry<Integer, ConfigurableRetryRule> entry : stmtRules.get().entrySet()) {
if (entry.getKey() == ruleToSearchFor) {
return entry.getValue();
if (ruleSet.equals(STATEMENT)) {
for (Map.Entry<Integer, ConfigurableRetryRule> entry : stmtRules.get().entrySet()) {
if (entry.getKey() == ruleToSearchFor) {
return entry.getValue();
}
}
} else {
for (Map.Entry<Integer, ConfigurableRetryRule> entry : connRules.get().entrySet()) {
if (entry.getKey() == ruleToSearchFor) {
return entry.getValue();

Check warning on line 340 in src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java#L340

Added line #L340 was not covered by tests
}
}
}
return null;
}

boolean getReplaceFlag() {
return replaceFlag;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
private int retryCount = 1;
private String retryQueries = "";
private String retryError;
boolean isConnection = false;
boolean replaceExisting = false;

private ArrayList<Integer> waitTimes = new ArrayList<>();

Expand Down Expand Up @@ -70,6 +72,18 @@
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);

Check warning on line 82 in src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryRule.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryRule.java#L80-L82

Added lines #L80 - L82 were not covered by tests
} else {
replaceExisting = true;
return retryError;

Check warning on line 85 in src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryRule.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryRule.java#L84-L85

Added lines #L84 - L85 were not covered by tests
}
}

/**
Expand Down Expand Up @@ -152,7 +166,12 @@
* 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;

Check warning on line 173 in src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryRule.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryRule.java#L170-L173

Added lines #L170 - L173 were not covered by tests
} else if (rule.length == 2 || rule.length == 3) {
checkParameter(rule[0]);
retryError = rule[0];
String[] timings = rule[1].split(COMMA);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,28 @@
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);

Expand Down Expand Up @@ -2024,10 +2046,23 @@
}
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;

Check warning on line 2059 in src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L2059 was not covered by tests
} else {
if (!TransientError.isTransientError(sqlServerError)) {
throw e;
}
}
}
}

// check if there's time to retry, no point to wait if no time left
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Loading
Loading