-
Notifications
You must be signed in to change notification settings - Fork 442
Configurable Retry Logic I - Statement Retry #2396
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
Merged
Merged
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 74e007a
Adding back retryExec
Jeffery-Wasty 726fc85
More
Jeffery-Wasty b463d81
Missing null check
Jeffery-Wasty 1ffc13e
Next to final
Jeffery-Wasty d18b021
Removed mssql-jdbc.properties
Jeffery-Wasty 5ff2737
Set up should start fresh + remove passwords to pass on pipeline
Jeffery-Wasty 9b3863d
Minor cleanup
Jeffery-Wasty 16e0224
Minor cleanup
Jeffery-Wasty 632c168
Another missing null check
Jeffery-Wasty be55749
Fix for timeout tests
Jeffery-Wasty 68c683a
Added timing tests + test comments
Jeffery-Wasty 9730690
Formatting
Jeffery-Wasty d9d9b38
Added a multiple rules test
Jeffery-Wasty 07fd965
Trying on a new branch b/c I keep getting build failures and I have n…
Jeffery-Wasty 03aa1b7
More changes
Jeffery-Wasty 83fecee
Undo LimitEscapeTest changes
Jeffery-Wasty 1f3e891
Remove redundant files
Jeffery-Wasty e2b2bd0
Final?
Jeffery-Wasty 2cb9650
Remove mssql-jdpc.properties file
Jeffery-Wasty 4844288
sync --> lock
Jeffery-Wasty bb3b253
Remove problematic test
Jeffery-Wasty 00c3545
Since error is unclear, try removing last test
Jeffery-Wasty 1e79a1f
Adding back connection test
Jeffery-Wasty f8273ea
I need debugging
Jeffery-Wasty a74cffe
Fix for MI
Jeffery-Wasty 5d80ecd
if condition for min time assertion
Jeffery-Wasty 11e84cc
Leftover debug code, cleanup
Jeffery-Wasty 141fc0c
Mistaken changes committed
Jeffery-Wasty 9f24ce4
More liberal time windows
Jeffery-Wasty 04aff85
Remove connection part
Jeffery-Wasty 193620f
Missed some parts where connection retry was still included.
Jeffery-Wasty 5e5858f
Forgot one more part
Jeffery-Wasty fdb38d8
Added (most) PR comment revisions.
Jeffery-Wasty 6465a95
Add comments for specified and public facing methods
Jeffery-Wasty ff374b0
Merge branch 'main' into CRL2
Jeffery-Wasty 46951f1
Added a missing test
Jeffery-Wasty eb1d508
Merge branch 'CRL2' of https://github.com/microsoft/mssql-jdbc into CRL2
Jeffery-Wasty 0be2a3b
More tests
Jeffery-Wasty 5d45f4d
Added more missing tests
Jeffery-Wasty cb24aa8
Resolve retryCount test failure
Jeffery-Wasty 7634c39
Remove eaten exceptions
Jeffery-Wasty 57fac74
Removed the file not found exception as we read for file in all cases…
Jeffery-Wasty 151d40f
Added a proper file read
Jeffery-Wasty 7aebfb2
Delete mssql-jdbc.properties
Jeffery-Wasty 763144d
Added more coverage and minor fixes, ready for review again
Jeffery-Wasty cbdc239
Merge branch 'CRL2' of https://github.com/microsoft/mssql-jdbc into CRL2
Jeffery-Wasty 7e267b1
Fixed read file test
Jeffery-Wasty a50fdf9
Addressed recent pr comments
Jeffery-Wasty 728866d
Merge branch 'main' into CRL2
Jeffery-Wasty 04c25f9
Remove double locking
Jeffery-Wasty c4e6153
Merge branch 'CRL2' of https://github.com/microsoft/mssql-jdbc into CRL2
Jeffery-Wasty c0fa29a
Remove unneeded variable
Jeffery-Wasty cc1540c
Revisions after PR review
Jeffery-Wasty 9269c18
PR review update
Jeffery-Wasty bbcdf8d
Rename R_AKVURLInvalid as its use is no longer AKV specific
Jeffery-Wasty 091ed78
Add back logging
Jeffery-Wasty 9aa47ca
Typo
Jeffery-Wasty 49b7b44
Removed unneeded comment
Jeffery-Wasty 34cfa4b
Make static variables thread-safe
Jeffery-Wasty df67a98
Timing
Jeffery-Wasty 7c7fc92
JavaDoc cleanup.
Jeffery-Wasty File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
223 changes: 223 additions & 0 deletions
223
src/main/java/com/microsoft/sqlserver/jdbc/ConfigurableRetryLogic.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | ||
|
|
||
| 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(); | ||
| } | ||
| } 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); | ||
| } | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
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; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.