-
Notifications
You must be signed in to change notification settings - Fork 22
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
Revamp retry and remove unintended exports #69
Conversation
simonz-bq
commented
Apr 14, 2021
- Refactored retry logic
- Handle retrying on failures during StartSession
- Removed some exports and accessibility to non intentional and undocumented functions and types
- Bumped aws-sdk dependency requirement to include CapacityExceededException
- Bumped driver version to 2.2.0 and added changelog
}, | ||
"peerDependencies": { | ||
"aws-sdk": "^2.815.0", | ||
"aws-sdk": "^2.841.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason we aren't bumping the devDependency aws-sdk to the same verison?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will fix
* Removed `Transaction` from the exports list. | ||
* Removed modules being accessible when importing from `amazon-qldb-driver-nodejs/src`. | ||
* TypeScript: Removed constructors in the API for some exported types. | ||
|
||
# 2.1.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add the date for when 2.1.1 was released
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we stopped doing that. The release date is also available on npm repository.
@@ -34,10 +34,10 @@ | |||
"sinon": "^7.3.2", | |||
"ts-node": "^8.3.0", | |||
"typedoc": "^0.15.0", | |||
"typescript": "^3.5.3" | |||
"typescript": "^3.8.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be called out in CHANGELOG?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a dev dependency so it doesn't matter to users
@@ -48,13 +54,15 @@ export function info(line: string): void { | |||
/** | |||
* @returns A boolean indicating whether a logger has been set within the AWS SDK. | |||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need @internal here?
Same with _prepend
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, because otherwise these are exported to the API
src/QldbDriver.ts
Outdated
|
||
retryConfig = (retryConfig == null) ? this._retryConfig : retryConfig; | ||
let session: QldbSession; | ||
let startNewSession: boolean = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct?
For the happy case where an execute is successful, the session is placed back into the pool. On another execute, it will create a new session instead of picking up the valid session based on getSession logic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
was fixed already but I didnt push it up yet
src/QldbDriver.ts
Outdated
if (e.isRetriable) { | ||
// Always retry on the first attempt if failure was caused by a stale session in the pool | ||
if (retryAttempt == 1 && e.isISE) { | ||
debug("Initial session received from pool invalid. Retrtying..."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initial session received from pool is invalid. Retrying...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
@@ -19,6 +19,8 @@ const HASH_SIZE: number = 32 | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add @internal to HASH SIZE? I see it in the docs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not exported in the typescript definition though.
src/Transaction.ts
Outdated
* An InvalidSessionException indicates that the parent session is dead, and a new transaction cannot be created | ||
* without a new (Pooled)QldbSession being created from the parent driver. | ||
* Every transaction is tied to a parent QldbSession, meaning that if the parent session is closed or invalidated, the | ||
* child transaction is automatically closed and cannot be used. Only one transaction can be active at any given time |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at any
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
* Fixed dev dependency version not matching peer dependency * Fixed a typo in docstrings * Updated README TypeScript versioning to match package.json
src/QldbDriver.ts
Outdated
@@ -156,36 +169,95 @@ export class QldbDriver { | |||
* @throws {@linkcode InvalidSessionException} When a session expires either due to a long running transaction or session being idle for long time. | |||
* @throws {@linkcode BadRequestException} When Amazon QLDB is not able to execute a query or transaction. | |||
*/ | |||
async executeLambda( | |||
transactionLambda: (transactionExecutor: TransactionExecutor) => any, | |||
async executeLambda<Type>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather make this a Promise<T>
. Using Type
like this is confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/QldbDriver.ts
Outdated
} | ||
|
||
// Acquire semaphore and get a session from the pool | ||
const getSession: (thisDriver: QldbDriver, startNewSession: boolean) => Promise<QldbSession> = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This extra typing information is probably unnecessary/can be inferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/QldbDriver.ts
Outdated
|
||
// Release semaphore and if the session is alive return it to the pool and return true | ||
const releaseSession: (thisDriver: QldbDriver, session: QldbSession) => boolean = | ||
function(thisDriver: QldbDriver, session: QldbSession): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typing information can be inferred
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
retryConfig = (retryConfig == null) ? this._retryConfig : retryConfig; | ||
let session: QldbSession; | ||
let startNewSession: boolean = false; | ||
for (let retryAttempt: number = 1; true; retryAttempt++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh? Why not a simple while(true)
or for (blah < whatever)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well we want to instantiate the retryAttempt and have it increment on every loop, so why not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks unusual to me, which doesn't seem worth it for saving one line of code. Your call to change or not.
return await session.executeLambda(transactionLambda); | ||
} catch (e) { | ||
if (e instanceof ExecuteError) { | ||
if (e.isRetriable) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we introduced this odd spelling here, let's not do that, otherwise I'll just be sad for a bit :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll immediately investigate if this can be done (see if we don't have any public facing API functions that say "retriable") after this PR is completed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks - can you create a GitHub issue to track this improvement?
src/QldbDriver.ts
Outdated
} | ||
await new Promise(resolve => setTimeout(resolve, backoffDelay)); | ||
|
||
info(`A recoverable error has occured. Attempting retry #${retryAttempt}.`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debug-level I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rest of the drivers have this on the info level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*occurred
throw new StartTransactionError(e); | ||
const isRetriable: boolean = isRetriableException(e); | ||
const isISE: boolean = isInvalidSessionException(e); | ||
if (isISE && !isTransactionExpiredException(e)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again expand the acronym please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cant do that here because isInvalidSessionException is imported from another module, so it's already defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What @alpian is referring to is the isISE variable, we should expand that to not be the abbrevation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was missed in the response here is that we cannot expand the name to match the function due to name resolution, and the feeling of keeping this local variable as an acronym would be acceptable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confused. Why can't const isISE
be const isSessionInvalid
?
src/errors/Errors.ts
Outdated
Object.setPrototypeOf(this, ExecuteError.prototype) | ||
this.cause = cause; | ||
this.isRetriable = isRetriable; | ||
this.isISE = isISE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing here, what is ISE?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -1,5 +1,5 @@ | |||
/* | |||
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is muddying the PR. Can we keep this cosmetic stuff separate please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was an intentional decision since almost all of the files were being touched anyways.
In the future will keep in a separate PR!
@@ -1,13 +1,14 @@ | |||
{ | |||
"compilerOptions": { | |||
"target": "es6", | |||
"target": "ES2015", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do this? These are the same afaik,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consistency. We had a reference to es2015 and es6 in the same file, which can be somewhat confusing.
Official TS docs also says the "allowed" string is capitalized, so might as well do an easy clean-up.
@@ -15,12 +15,12 @@ from the ~/.aws/config file. | |||
|
|||
See [Setting Region](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-region.html) page for more information. | |||
|
|||
### TypeScript 3.5.x | |||
### TypeScript 3.8.x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is 3.8 needed, or can it stay at 3.5?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly we don't even need 3.5. The real version we need is some undetermined previous version.
Figured we'd align up our documentation and all and 3.8 was what I was playing with testing hash private functions. This ultimately doesn't matter because it's an independent dev dependency.
src/QldbDriver.ts
Outdated
session.endSession(); | ||
} | ||
} | ||
this._isClosed = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably mark the driver as closed before starting to empty the pool so no new items can be opened (no matter that this shouldn't be possible given the single-threaded nature).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, but yes it doesn't matter in this case.
close(): void { | ||
while (this._sessionPool.length > 0) { | ||
const session: QldbSession = this._sessionPool.pop(); | ||
if (session != undefined) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this possible, that there can be undefined items in the pool?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure as node is supposedly single threaded so this code block shouldn't have multiple things accessing it, but popping from an empty array gives you undefined. This safety check is a case of better safe than sorry.
src/QldbDriver.ts
Outdated
`Getting session. Current free session count: ${thisDriver._sessionPool.length}. ` + | ||
`Currently available permit count: ${thisDriver._availablePermits}.` | ||
); | ||
if (thisDriver._semaphore.tryAcquire()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is purely stylistic, but when there's an error case my preference is to put it within the if block and have the throw there, and leave the rest of the code not indented. Here, this would mean checking on !tryAcquire() and throwing, while leaving the rest of the code not indented.
Purely stylistic.
let startNewSession: boolean = false; | ||
for (let retryAttempt: number = 1; true; retryAttempt++) { | ||
try { | ||
session = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the assignment to null before assigning on getSession?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevents the session from the last loop being passed to releaseSession twice in the case that startSession fails
src/QldbDriver.ts
Outdated
if (e.isRetriable) { | ||
// Always retry on the first attempt if failure was caused by a stale session in the pool | ||
if (retryAttempt == 1 && e.isISE) { | ||
debug("Initial session received from pool is invalid. Retrtying..."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the typo in Retrying...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed!
throw e.cause; | ||
} | ||
|
||
const backoffFunction: BackoffFunction = retryConfig.getBackoffFunction(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deprecation would need a notice for at least a release, and can be done via: microsoft/TypeScript#390
} catch (e) { | ||
if (isBadRequestException(e)) { | ||
throw new StartTransactionError(e); | ||
const isRetriable: boolean = isRetriableException(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this spelling makes Ian sad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. Let me check after this PR whether or not we exposed any function names or parameters as "Retriable" to see if we can fix this.
src/QldbSession.ts
Outdated
return new Transaction(this._communicator, startTransactionResult.TransactionId); | ||
} | ||
|
||
private async _tryAbort(): Promise<void> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally a tryX function would return true or false depending on if it was successful at the try or not.
@@ -167,8 +165,20 @@ export function isBadRequestException(e: AWSError): boolean { | |||
* Is the exception a retriable exception? | |||
* @param e The client error caught. | |||
* @returns True if the exception is a retriable exception. False otherwise. | |||
* | |||
* @internal | |||
*/ | |||
export function isRetriableException(e: AWSError): boolean { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that these are no longer exported, we're probably free to change the spelling?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps adding a separate PR for cosmetic items, as Ian suggested below (dates and spelling changes).
* Infer type of scoped functions in execute * Expand IsISE to full name * Rename tryAbort since it doesn't return a bool * Fix a typo in log message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved with comments
retryConfig = (retryConfig == null) ? this._retryConfig : retryConfig; | ||
let session: QldbSession; | ||
let startNewSession: boolean = false; | ||
for (let retryAttempt: number = 1; true; retryAttempt++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks unusual to me, which doesn't seem worth it for saving one line of code. Your call to change or not.
throw new StartTransactionError(e); | ||
const isRetriable: boolean = isRetriableException(e); | ||
const isISE: boolean = isInvalidSessionException(e); | ||
if (isISE && !isTransactionExpiredException(e)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confused. Why can't const isISE
be const isSessionInvalid
?