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

[core-amqp] MessagingError changes #6673

Merged
merged 20 commits into from
Jan 6, 2020

Conversation

chradek
Copy link
Contributor

@chradek chradek commented Dec 21, 2019

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

}
if (retryableErrors.indexOf(error.name) === -1) {
if (error.code && retryableErrors.indexOf(error.code) === -1) {
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

@ramya-rao-a ramya-rao-a Jan 6, 2020

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@ramya-rao-a ramya-rao-a Jan 6, 2020

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...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retryableErrors does have an entry for MessagingError today though...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah! I missed that :)

@@ -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.
Copy link
Contributor

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?

Copy link
Contributor Author

@chradek chradek Jan 2, 2020

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

@bterlson bterlson left a 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.

/**
* The amqp error condition.
*/
amqpCondition?: string;
Copy link
Member

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 😀

Copy link
Member

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.

Copy link
Contributor Author

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.

@@ -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.
Copy link
Member

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.

return;
}

// can consider using a mixin instead but not sure this gives us much value over casting to any
Copy link
Member

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?

Copy link
Contributor Author

@chradek chradek Jan 2, 2020

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.

Copy link
Contributor Author

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.

@@ -519,8 +537,10 @@ export function isSystemError(err: any): boolean {
if (
err.code &&
Copy link
Member

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?

Copy link
Contributor Author

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 =
Copy link
Member

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() {
Copy link
Member

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).

Copy link
Contributor Author

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 :).

@chradek chradek marked this pull request as ready for review January 3, 2020 00:09
const testError = new RangeError("Out of range!!");
const translatedError = Errors.translate(testError);
translatedError.name.should.equal(testError.name);
translatedError.message.should.equal(testError.message);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@ramya-rao-a
Copy link
Contributor

/azp run js - eventhubs-client - tests

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Contributor

@ramya-rao-a ramya-rao-a left a 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 type Error. Consider using Error | MessagingError instead.
  • Ensure the integration tests for Event Hubs all pass.

@ramya-rao-a
Copy link
Contributor

ramya-rao-a commented Jan 6, 2020

Pushed a commit to make the change from name to code.

@ramya-rao-a
Copy link
Contributor

/azp run js - eventhubs-client - tests

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@ramya-rao-a
Copy link
Contributor

/azp run js - eventhubs-client - tests

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@chradek chradek merged commit 099e515 into Azure:master Jan 6, 2020
@chradek chradek deleted the core-amqp-messaging-error-changes branch January 6, 2020 17:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Event Hubs] Use code instead of name to detect different kind of errors
3 participants