-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[core-amqp] MessagingError changes #6673
[core-amqp] MessagingError changes #6673
Conversation
} | ||
if (retryableErrors.indexOf(error.name) === -1) { | ||
if (error.code && retryableErrors.indexOf(error.code) === -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.
What if by this point we don't have a code
?
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 this point I was treating errors without a code as neither an AmqpError
nor a SystemError
, so do not alter them. They currently don't have a retryable
field so are effectively not retryable.
This could change based on the thread you started at https://github.com/Azure/azure-sdk-for-js/pull/6673/files#r362364248
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.
While isAmqpError()
ensures that the error has the condition property, we still won't have a code
if there was an error with an amqp condition for which we have no mapping. Such errors will be treated as retryable.
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.
Currently, if there isn't a code, we will treat the error as retryable since retryable is set to true in the MessagingError constructor and we don't change it to false here. That seems consistent with the behavior prior to this PR, since if no condition was found we'd set the name
to MessagingError
, and any error with the name MessagingError
was treated as retryable.
So, I think the question you're getting at is whether we should set error.code
if there is no mapping or not. My preference would be to not do this, because then it would be a breaking change if we wanted to change the code in the future for an error we learned more about but weren't handling before. I think we can safely add a code if it never existed in the 1st place, but not change it if there already was one for an error.
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.
That seems consistent with the behavior prior to this PR, since if no condition was found we'd set the name to MessagingError, and any error with the name MessagingError was treated as retryable.
Not true... retryableErrors
would not have an entry for MessagingError
and so retryable
would be set to false...
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.
"MessagingError", |
retryableErrors does have an entry for MessagingError today though...
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.
ah! I missed that :)
sdk/core/core-amqp/src/errors.ts
Outdated
@@ -464,9 +468,23 @@ export class MessagingError extends Error { | |||
info?: any; | |||
/** | |||
* @param {string} message The error message that provides more information about the error. | |||
* @param originalError An error whose properties will be copied to the MessagingError if the | |||
* propert doesn't already exist. |
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 copy only if the property already exists and not copy all? They will get overriden appropriately after the constructor call right?
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.
Since the properties are copied after the super
call, we don't want to end up overwriting the message
, name
, and stack
properties with those from the original error. This also means if the error had translated
or retryable
properties already, they would not overwrite those values set in the constructor (true
for both).
Maybe we should only update the retryable
field if the original error doesn't have one (if someone passes an error with this already set should we honor it?). Since message
is passed in explicitly, name
is always MessagingError
, and stack
should indicate the line where this error is created, I don't think those should be overwritten.
Edit: I see that we preserve the stack when we convert an AmqpError to a MessagingError ...so depending on our intent maybe it's useful to preserve the stack at least.
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.
Also, should be noted that this copies properties if they don't exist on MessagingError
, not if they do.
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.
Since we're wrapping errors and re-throwing, copying the stack property over might be more correct. Just make sure the stack that comes out is maximally useful. Aside from that, I think I agree with your intiution above - keep retryable if the original error had one, and message and name don't need to be copied.
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 made a late edit that it might be better to copy the stack over as well, so I'll go ahead and make that change.
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.
Overall seems good to me. Left some comments.
sdk/core/core-amqp/src/errors.ts
Outdated
/** | ||
* The amqp error condition. | ||
*/ | ||
amqpCondition?: string; |
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.
Thoughts about not exposing amqpCondition
for now? I'm not entirely convinced it's helpful beyond the code
, but do try to convince me 😀
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.
@chradek had a good idea: log the amqp error instead.
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.
Hmm, now I'm less sure about just logging the amqp error since we will want to log the translated error as well (that tells us if it is retryable), that would make the logs noisy since in many cases both errors would be logged to verbose mode.
I can still do that, but what about just logging condition alongside the translated error? We could also leave it out for now, there only appear to be 2 cases where 2 amqp errors map to the same code: UnauthorizedError
and PreconditionFailedError
and I'm not sure how useful it would be to know the specific condition in those cases.
sdk/core/core-amqp/src/errors.ts
Outdated
@@ -464,9 +468,23 @@ export class MessagingError extends Error { | |||
info?: any; | |||
/** | |||
* @param {string} message The error message that provides more information about the error. | |||
* @param originalError An error whose properties will be copied to the MessagingError if the | |||
* propert doesn't already exist. |
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.
Since we're wrapping errors and re-throwing, copying the stack property over might be more correct. Just make sure the stack that comes out is maximally useful. Aside from that, I think I agree with your intiution above - keep retryable if the original error had one, and message and name don't need to be copied.
sdk/core/core-amqp/src/errors.ts
Outdated
return; | ||
} | ||
|
||
// can consider using a mixin instead but not sure this gives us much value over casting to 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.
Yeah... not so much a big fan of the sloppy types here. It seems good to know which properties we will copy and which we won't. Not sure what you mean by mixin though?
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 struggled with what to do here since I wanted to make sure we preserved anything the original error may have had even if we didn't know about it.
That said, we're now only converting AmqpErrors and SystemError, so I could add the properties from SystemError to MessagingError as optional properties:
- address If present, the address to which a network connection failed
- code The string error code
- dest If present, the file path destination when reporting a file system error
- errno | The system-provided error number
- info If present, extra details about the error condition
- message A system-provided human-readable description of the error
- path If present, the file path when reporting a file system error
- port If present, the network connection port that is not available
- syscall The name of the system call that triggered the error
Edit: I can probably omit the filesystem-related properties as well to make the list shorter.
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 got rid of the use of any
here.
sdk/core/core-amqp/src/errors.ts
Outdated
@@ -519,8 +537,10 @@ export function isSystemError(err: any): boolean { | |||
if ( | |||
err.code && |
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 factor this massive conditional out of the if's test expression?
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 can remove the if
statement and just return the result instead? 😄
I'll make this more readable.
if (condition) { | ||
const amqpErrorCondition = SystemErrorConditionMapper[condition as keyof typeof SystemErrorConditionMapper]; | ||
error.name = ConditionErrorNameMapper[amqpErrorCondition as keyof typeof ConditionErrorNameMapper]; | ||
const amqpErrorCondition = |
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.
Idle speculation: if this cast is valid, then perhaps code
could be typed as keyof typeof SystemErrorConditionMapper
to begin with?
translatedError.name.should.equal(ehError.name); | ||
translatedError.retryable.should.equal(ehError.retryable); | ||
translatedError.message.should.equal(ehError.message); | ||
it("Does not touch errors that are neither AmqpError or SystemError", function() { |
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.
either neither and nor or either and or (so not neither and or or either and nor).
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 make the change, though this took me on a fun journey :).
const testError = new RangeError("Out of range!!"); | ||
const translatedError = Errors.translate(testError); | ||
translatedError.name.should.equal(testError.name); | ||
translatedError.message.should.equal(testError.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.
Since the above 3 error cases should go untouched, can we do a deep check to ensure that the input and the output for translate()
is exactly the same?
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.
Updated to use deep equality.
/azp run js - eventhubs-client - tests |
Azure Pipelines successfully started running 1 pipeline(s). |
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 integration tests for Event Hubs using core-amqp from this PR is hung.
Few follow ups needed to have the tests passing for Event Hubs
- Test code that checks for error.name to be updated to use error.code instead
- The err argument for
processError
is of typeError
. Consider usingError | MessagingError
instead. - Ensure the integration tests for Event Hubs all pass.
Pushed a commit to make the change from name to code. |
/azp run js - eventhubs-client - tests |
Azure Pipelines successfully started running 1 pipeline(s). |
/azp run js - eventhubs-client - tests |
Azure Pipelines successfully started running 1 pipeline(s). |
Fixes #6669
There's one test that's failing still (the amqp error test), but otherwise everything else is green. Let's use this as a starting point for the linked issue.
/cc @ramya-rao-a @richardpark-msft