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 I - Statement Retry #2396

Merged
merged 62 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
50f3f18
Trying on a new branch b/c I keep getting build failures and I have n…
Jeffery-Wasty Apr 15, 2024
74e007a
Adding back retryExec
Jeffery-Wasty Apr 15, 2024
726fc85
More
Jeffery-Wasty Apr 15, 2024
b463d81
Missing null check
Jeffery-Wasty Apr 17, 2024
1ffc13e
Next to final
Jeffery-Wasty Apr 22, 2024
d18b021
Removed mssql-jdbc.properties
Jeffery-Wasty Apr 22, 2024
5ff2737
Set up should start fresh + remove passwords to pass on pipeline
Jeffery-Wasty Apr 22, 2024
9b3863d
Minor cleanup
Jeffery-Wasty Apr 22, 2024
16e0224
Minor cleanup
Jeffery-Wasty Apr 22, 2024
632c168
Another missing null check
Jeffery-Wasty Apr 22, 2024
be55749
Fix for timeout tests
Jeffery-Wasty Apr 23, 2024
68c683a
Added timing tests + test comments
Jeffery-Wasty Apr 23, 2024
9730690
Formatting
Jeffery-Wasty Apr 23, 2024
d9d9b38
Added a multiple rules test
Jeffery-Wasty Apr 23, 2024
07fd965
Trying on a new branch b/c I keep getting build failures and I have n…
Jeffery-Wasty Apr 15, 2024
03aa1b7
More changes
Jeffery-Wasty Jun 7, 2024
83fecee
Undo LimitEscapeTest changes
Jeffery-Wasty Jun 7, 2024
1f3e891
Remove redundant files
Jeffery-Wasty Jun 7, 2024
e2b2bd0
Final?
Jeffery-Wasty Jun 10, 2024
2cb9650
Remove mssql-jdpc.properties file
Jeffery-Wasty Jun 10, 2024
4844288
sync --> lock
Jeffery-Wasty Jun 10, 2024
bb3b253
Remove problematic test
Jeffery-Wasty Jun 10, 2024
00c3545
Since error is unclear, try removing last test
Jeffery-Wasty Jun 10, 2024
1e79a1f
Adding back connection test
Jeffery-Wasty Jun 10, 2024
f8273ea
I need debugging
Jeffery-Wasty Jun 10, 2024
a74cffe
Fix for MI
Jeffery-Wasty Jun 10, 2024
5d80ecd
if condition for min time assertion
Jeffery-Wasty Jun 11, 2024
11e84cc
Leftover debug code, cleanup
Jeffery-Wasty Jun 13, 2024
141fc0c
Mistaken changes committed
Jeffery-Wasty Jun 13, 2024
9f24ce4
More liberal time windows
Jeffery-Wasty Jun 13, 2024
04aff85
Remove connection part
Jeffery-Wasty Jun 18, 2024
193620f
Missed some parts where connection retry was still included.
Jeffery-Wasty Jun 18, 2024
5e5858f
Forgot one more part
Jeffery-Wasty Jun 18, 2024
fdb38d8
Added (most) PR comment revisions.
Jeffery-Wasty Aug 11, 2024
6465a95
Add comments for specified and public facing methods
Jeffery-Wasty Aug 11, 2024
ff374b0
Merge branch 'main' into CRL2
Jeffery-Wasty Aug 11, 2024
46951f1
Added a missing test
Jeffery-Wasty Aug 12, 2024
eb1d508
Merge branch 'CRL2' of https://github.com/microsoft/mssql-jdbc into CRL2
Jeffery-Wasty Aug 12, 2024
0be2a3b
More tests
Jeffery-Wasty Aug 12, 2024
5d45f4d
Added more missing tests
Jeffery-Wasty Aug 13, 2024
cb24aa8
Resolve retryCount test failure
Jeffery-Wasty Aug 13, 2024
7634c39
Remove eaten exceptions
Jeffery-Wasty Aug 13, 2024
57fac74
Removed the file not found exception as we read for file in all cases…
Jeffery-Wasty Aug 13, 2024
151d40f
Added a proper file read
Jeffery-Wasty Aug 13, 2024
7aebfb2
Delete mssql-jdbc.properties
Jeffery-Wasty Aug 13, 2024
763144d
Added more coverage and minor fixes, ready for review again
Jeffery-Wasty Aug 14, 2024
cbdc239
Merge branch 'CRL2' of https://github.com/microsoft/mssql-jdbc into CRL2
Jeffery-Wasty Aug 14, 2024
7e267b1
Fixed read file test
Jeffery-Wasty Aug 14, 2024
a50fdf9
Addressed recent pr comments
Jeffery-Wasty Aug 15, 2024
728866d
Merge branch 'main' into CRL2
Jeffery-Wasty Aug 23, 2024
04c25f9
Remove double locking
Jeffery-Wasty Aug 23, 2024
c4e6153
Merge branch 'CRL2' of https://github.com/microsoft/mssql-jdbc into CRL2
Jeffery-Wasty Aug 23, 2024
c0fa29a
Remove unneeded variable
Jeffery-Wasty Aug 23, 2024
cc1540c
Revisions after PR review
Jeffery-Wasty Sep 18, 2024
9269c18
PR review update
Jeffery-Wasty Sep 19, 2024
bbcdf8d
Rename R_AKVURLInvalid as its use is no longer AKV specific
Jeffery-Wasty Sep 19, 2024
091ed78
Add back logging
Jeffery-Wasty Sep 20, 2024
9aa47ca
Typo
Jeffery-Wasty Sep 20, 2024
49b7b44
Removed unneeded comment
Jeffery-Wasty Sep 20, 2024
34cfa4b
Make static variables thread-safe
Jeffery-Wasty Sep 20, 2024
df67a98
Timing
Jeffery-Wasty Sep 20, 2024
7c7fc92
JavaDoc cleanup.
Jeffery-Wasty Sep 22, 2024
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
223 changes: 223 additions & 0 deletions src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
/*
* Microsoft JDBC Driver for SQL Server Copyright(c) Microsoft Corporation All rights reserved. This program is made
* available under the terms of the MIT License. See the LICENSE file in the project root for more information.
*/

package com.microsoft.sqlserver.jdbc;
Jeffery-Wasty marked this conversation as resolved.
Show resolved Hide resolved

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
* Allows configurable statement retry through the use of the 'retryExec' connection property. Each rule read in is
* converted to ConfigRetryRule objects, which are stored and referenced during statement retry.
*
*/
public class ConfigurableRetryLogic {
Jeffery-Wasty marked this conversation as resolved.
Show resolved Hide resolved
Jeffery-Wasty marked this conversation as resolved.
Show resolved Hide resolved
private static final int INTERVAL_BETWEEN_READS_IN_MS = 30000;
private static final String DEFAULT_PROPS_FILE = "mssql-jdbc.properties";
private static final Lock CRL_LOCK = new ReentrantLock();
private static final java.util.logging.Logger CONFIGURABLE_RETRY_LOGGER = java.util.logging.Logger
.getLogger("com.microsoft.sqlserver.jdbc.ConfigurableRetryLogic");
private static final String SEMI_COLON = ";";
private static final String COMMA = ",";
private static final String FORWARD_SLASH = "/";
private static final String EQUALS_SIGN = "=";
private static final String RETRY_EXEC = "retryExec";
private static final AtomicLong timeLastModified = new AtomicLong(0);
private static final AtomicLong timeLastRead = new AtomicLong(0);
private static final AtomicReference<String> lastQuery
= new AtomicReference<>(""); // The last query executed (used when rule is process-dependent)
private static final AtomicReference<String> prevRulesFromConnectionString = new AtomicReference<>("");
private static final AtomicReference<HashMap<Integer, ConfigurableRetryRule>> stmtRules
= new AtomicReference<>(new HashMap<>());
private static ConfigurableRetryLogic singleInstance;


private ConfigurableRetryLogic() throws SQLServerException {
timeLastRead.compareAndSet(0, new Date().getTime());
setUpRules(null);
}

/**
* Fetches the static instance of ConfigurableRetryLogic, instantiating it if it hasn't already been.
* Each time the instance is fetched, we check if a re-read is needed, and do so if properties should be re-read.
*
* @return The static instance of ConfigurableRetryLogic
* @throws SQLServerException
* an exception
*/
public static ConfigurableRetryLogic getInstance() throws SQLServerException {
Jeffery-Wasty marked this conversation as resolved.
Show resolved Hide resolved
if (singleInstance == null) {
CRL_LOCK.lock();
try {
if (singleInstance == null) {
singleInstance = new ConfigurableRetryLogic();
} else {
refreshRuleSet();

Check warning on line 73 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#L73

Added line #L73 was not covered by tests
}
} finally {
CRL_LOCK.unlock();
}
} else {
refreshRuleSet();
}

return singleInstance;
}

/**
* 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.
*
* @throws SQLServerException
* when an exception occurs
*/
private static void refreshRuleSet() throws SQLServerException {
long currentTime = new Date().getTime();
if ((currentTime - timeLastRead.get()) >= INTERVAL_BETWEEN_READS_IN_MS) {
timeLastRead.set(currentTime);
if (timeLastModified.get() != 0) {
// If timeLastModified has been set, we have previously read from a file, so we setUpRules
// reading from file
File f = new File(getCurrentClassPath());
if (f.lastModified() != timeLastModified.get()) {
setUpRules(null);
}
} else {
setUpRules(prevRulesFromConnectionString.get());
}
}
}

void setFromConnectionString(String newRules) throws SQLServerException {
prevRulesFromConnectionString.set(newRules);
setUpRules(prevRulesFromConnectionString.get());
}

void storeLastQuery(String newQueryToStore) {
lastQuery.set(newQueryToStore.toLowerCase());
}

String getLastQuery() {
return lastQuery.get();
}

/**
* 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
* @throws SQLServerException
* If an exception occurs
*/
private static void setUpRules(String cxnStrRules) throws SQLServerException {
stmtRules.set(new HashMap<>());
lastQuery.set("");
LinkedList<String> temp;

if (cxnStrRules == null || cxnStrRules.isEmpty()) {
temp = readFromFile();
} else {
temp = new LinkedList<>();
Collections.addAll(temp, cxnStrRules.split(SEMI_COLON));
}
createRules(temp);
}

private static void createRules(LinkedList<String> listOfRules) throws SQLServerException {
stmtRules.set(new HashMap<>());

for (String potentialRule : listOfRules) {
ConfigurableRetryRule rule = new ConfigurableRetryRule(potentialRule);

if (rule.getError().contains(COMMA)) {
String[] arr = rule.getError().split(COMMA);

for (String retryError : arr) {
ConfigurableRetryRule splitRule = new ConfigurableRetryRule(retryError, rule);
stmtRules.get().put(Integer.parseInt(splitRule.getError()), splitRule);
}
} else {
stmtRules.get().put(Integer.parseInt(rule.getError()), rule);
}
}
}

private static String getCurrentClassPath() throws SQLServerException {
String location = "";
String className = "";

try {
className = new Object() {}.getClass().getEnclosingClass().getName();
location = Class.forName(className).getProtectionDomain().getCodeSource().getLocation().getPath();
location = location.substring(0, location.length() - 16);
URI uri = new URI(location + FORWARD_SLASH);
return uri.getPath() + DEFAULT_PROPS_FILE; // For now, we only allow "mssql-jdbc.properties" as file name.
} catch (URISyntaxException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_URLInvalid"));
Object[] msgArgs = {location + FORWARD_SLASH};
throw new SQLServerException(form.format(msgArgs), null, 0, e);
} catch (ClassNotFoundException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnableToFindClass"));
Object[] msgArgs = {className};
throw new SQLServerException(form.format(msgArgs), null, 0, e);

Check warning on line 180 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#L173-L180

Added lines #L173 - L180 were not covered by tests
}
}

private static LinkedList<String> readFromFile() throws SQLServerException {
String filePath = getCurrentClassPath();
LinkedList<String> list = new LinkedList<>();

try {
File f = new File(filePath);
try (BufferedReader buffer = new BufferedReader(new FileReader(f))) {
String readLine;
while ((readLine = buffer.readLine()) != null) {
if (readLine.startsWith(RETRY_EXEC)) {
String value = readLine.split(EQUALS_SIGN)[1];
Collections.addAll(list, value.split(SEMI_COLON));
}
}
}
timeLastModified.set(f.lastModified());
} catch (FileNotFoundException e) {
// If the file is not found either A) We're not using CRL OR B) the path is wrong. Do not error out, instead
// log a message.
if (CONFIGURABLE_RETRY_LOGGER.isLoggable(java.util.logging.Level.FINER)) {
CONFIGURABLE_RETRY_LOGGER.finest("File not found at path - \"" + filePath + "\"");
}
} catch (IOException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorReadingStream"));
Object[] msgArgs = {e.getMessage() + ", from path - \"" + filePath + "\""};
throw new SQLServerException(form.format(msgArgs), null, 0, e);

Check warning on line 209 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#L206-L209

Added lines #L206 - L209 were not covered by tests
}
Jeffery-Wasty marked this conversation as resolved.
Show resolved Hide resolved
return list;
}

ConfigurableRetryRule searchRuleSet(int ruleToSearchFor) throws SQLServerException {
refreshRuleSet();
Jeffery-Wasty marked this conversation as resolved.
Show resolved Hide resolved
for (Map.Entry<Integer, ConfigurableRetryRule> entry : stmtRules.get().entrySet()) {
if (entry.getKey() == ruleToSearchFor) {
return entry.getValue();
}
}
return null;
}
}
Loading
Loading