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

Configurable Retry Logic I - Statement Retry #2396

merged 62 commits into from
Sep 23, 2024

Conversation

Jeffery-Wasty
Copy link
Contributor

@Jeffery-Wasty Jeffery-Wasty commented Apr 23, 2024

ConfigurableRetryLogic

Rules can be defined either in the connection string or the mssql-jdbc.properties file. In both cases, the rules follow this format: retryExec={rule1;rule2;rule3}.

During connection initialization, the static instance of ConfigurableRetryLogic is fetched (getInstance()). Since the instance is null, a reread is triggered (reread()).

Within the reread, the lastTimeRead is set up, this is to ensure that in the future, the CRL instance is not updated all the time, only when the interval has been reached (default 30 seconds).

Next the rules are setup (setUpRules()). If there are rules to read from the connection string, they are used to populate the internal ruleset, if not, then rules are read from the mssql-jdbc.properties file. That is, the connection string takes priority over the properties file. After the rules are read, they are parsed into ConfigRetryRule objects in createRules().

In createRules(), the list of String rules are used to create a HashMap of ConfigRetryRule objects

ConfigRetryRule

  • The rules are parsed in parse() removing extra elements that could remain after reading from connection string or file. As well the rules is split based on a :.

  • Valid rules can have the following formats:

    • {error(s):numberOfRetries}----------------------------------------------------i.e. {2714:1}
    • {error(s):numberOfRetries,initialRetryTime}----------------------------------i.e. {2714:1,3}
    • {error(s):numberOfRetries,intialRetryTime(operand)}------------------------i.e. {2714,1,3+}
    • {error(s):numberOfRetries,intitialRetryTime(operand)retryChange}---------i.e. {2714,1,3+2}
    • {error(s):numberOfRetries:query}---------------------------------------------i.e. {2714:1:CREATE}
    • {error(s):numberOfRetries,intialRetryTime:query}----------------------------i.e. {2714,1,3:CREATE}
    • {error(s):numberOfRetries,intialRetryTime(operand):query}-----------------i.e. {2714,1,3+:CREATE}
    • {error(s):numberOfRetries,intialRetryTime(operand)retryChange:query}----i.e. {2714,1,3+2:CREATE}
  • The elements from each rule are fetched using addElement. If a rule is <2 or >3 parts, an error is returned. Otherwise, the first part is set as the retryError, and the second part (the timings) are parsed further. The first part of the timings is always the retryCount. We check if its an integer and greater than 0, if so it is set as retryCount. If there are two parts to the timing, then the second part can be the initalRetryTime (initial time to wait between retries), the initialRetryTime + operand (the change we make to the retry time each retry, * or +), or the initialRetryTime + operand + retryChange (the numeric change that is applied to each wait time).

  • The second part of timings is parsed, if it contains a *, this is the multiplicative change. We store this in operand. The value proceeding the operand is the initialRetryTime, and and value following the operand is the retryChange. If it doesn't exist, its set to the default.

  • The same applies to +. If there is neither operand, then we know we only have the initialRetryTime and so we set initialRetryTime from the value left in the timings.

  • If the rule is 3 parts, then the optional query part has been included (apply this rule to only specific queries). This part of the rules is then set under retryQueries.

  • Wait time is calculated from the elements we parsed (calcWaitTimes) and stored in an ArrayList waitTimes.

  • With this the rule has been turned into a ConfigRetryRule. Another check has to be done to ensure that there is only one retryError per rule. If multiple errors were passed in (separated by a comma), then we need to create new rules from the rule, each containing only one retryError. Finally all of the rules are put into the stmtRules HashMap.

SQLServerStatement

  • Execution of the statement is tried. If there is an error, previously the error was thrown.
  • Now, we set a continue flag (cont) to false, and attempt to determine whether we can retry. We attempt to fetch the ConfigRetryRule that corresponds to the sqlServerError. If there is none, behavior continues as previously defined.
  • If there is a match, we check if there are retries remaining. Statement retries are tracked via retryAttempt and compared to the ConfigRetryRule's retryCount.
  • If there are retries remaining, we need to ensure this error also matches in regards to the query. The last query was stored prior to execution (see SQLServerStatement 860 and SQLServerPreparedStatement 583). If there is no query defined for the rule, or the query matches, we proceed.
  • We then have to wait until we can retry again. The wait times were calculated previously and can easily be fetched using the retryAttempt.
  • After waiting we increment retryAttempt, and set the cont flag to true, allowing us to repeat and try executing again.
  • We exit out of this retry in the following ways:
    • On successful execution
    • If we do not find a perfect match in the list of Conifgurable Retry Logic errors (retryError and query).
    • If we run out of retries

@Jeffery-Wasty Jeffery-Wasty marked this pull request as ready for review April 23, 2024 17:50
@Jeffery-Wasty Jeffery-Wasty added this to the 12.7.1 milestone May 8, 2024
@Jeffery-Wasty Jeffery-Wasty self-assigned this May 22, 2024
@Jeffery-Wasty Jeffery-Wasty changed the title Configurable Retry Logic I Configurable Retry Logic Jun 10, 2024
@Jeffery-Wasty Jeffery-Wasty reopened this Sep 19, 2024
@microsoft microsoft deleted a comment from codecov bot Sep 19, 2024
lilgreenbird
lilgreenbird previously approved these changes Sep 20, 2024
Copy link

codecov bot commented Sep 20, 2024

Codecov Report

Attention: Patch coverage is 90.65041% with 23 lines in your changes missing coverage. Please review.

Project coverage is 50.95%. Comparing base (fe56e25) to head (7c7fc92).
Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
...crosoft/sqlserver/jdbc/ConfigurableRetryLogic.java 83.50% 13 Missing and 3 partials ⚠️
...m/microsoft/sqlserver/jdbc/SQLServerStatement.java 86.11% 3 Missing and 2 partials ⚠️
...icrosoft/sqlserver/jdbc/ConfigurableRetryRule.java 98.92% 0 Missing and 1 partial ⚠️
...QLServerColumnEncryptionAzureKeyVaultProvider.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #2396      +/-   ##
============================================
+ Coverage     50.66%   50.95%   +0.28%     
- Complexity     3826     3890      +64     
============================================
  Files           145      147       +2     
  Lines         33133    33368     +235     
  Branches       5558     5592      +34     
============================================
+ Hits          16788    17002     +214     
- Misses        13944    13960      +16     
- Partials       2401     2406       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

barryw-mssql
barryw-mssql previously approved these changes Sep 20, 2024
@Jeffery-Wasty Jeffery-Wasty merged commit 4ec4d3b into main Sep 23, 2024
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Closed/Merged PRs
Development

Successfully merging this pull request may close these issues.

5 participants