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

[BUGFIX release] mark handled errors and dont reraise #12549

Conversation

minasmart
Copy link

Ember 2.1 introduced a regression where all errors during a transition
were thrown, even when those errors had been handled in a route's error
action. This PR adds a test to identify the regression, and adds some
logic and state to identify errors that have been handled, and not
reraise them, while maintaining the desired behaviour of throwing errors
that aren't handled.

This is especially valuable in any application that tests error
handling. Since all errors were being thrown regardless of handling,
failed assertions were being registered by qunit, and it became
impossible to test application flow that entered the error hook.

Further details about this issue are here: #12547

@minasmart minasmart changed the title Bugfix mark handled errors and dont reraise [BUGFIX] mark handled errors and dont reraise Nov 3, 2015
@@ -865,6 +877,10 @@ function triggerEvent(handlerInfos, ignoreFailure, args) {
if (handler.actions[name].apply(handler, args) === true) {
eventWasHandled = true;
} else {
// Should only hit here if a non-bubbling error action is triggered on a route.
if (name === 'error') {
markErrorAsHandled(args[0]);
Copy link
Member

Choose a reason for hiding this comment

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

What is args[0] here?

Copy link
Author

Choose a reason for hiding this comment

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

args[0] is the error. It's the first argument to the error action. Should I call it out as a var?

Copy link
Member

Choose a reason for hiding this comment

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

It isn't necessary to make it a variable, but I do not think that the following does what we want:

e1 = new Error('foo');
e2 = new Error('foo');
obj = {};
obj[e1] = true;
obj[e2] = false;

obj[e1] // => false WAT

screenshot

@rwjblue
Copy link
Member

rwjblue commented Nov 3, 2015

Discussed in slack with @minasmart, I believe we have a path forward.

Also, this will be a [BUGFIX release].

@minasmart minasmart force-pushed the BUGFIX-mark-handled-errors-and-dont-reraise branch from 8fba3c2 to 1217cdb Compare November 3, 2015 15:48
@minasmart minasmart changed the title [BUGFIX] mark handled errors and dont reraise [BUGFIX release] mark handled errors and dont reraise Nov 3, 2015
@rwjblue
Copy link
Member

rwjblue commented Nov 3, 2015

Looks like this is causing a failure in the node test suite.

You can reproduce via:

ember server

# in another terminal
node bin/run-node-tests.js

@minasmart
Copy link
Author

Will take a look. 

@minasmart
Copy link
Author

This branch is based off of master, and this test seems to consistently fail on master too.

@minasmart
Copy link
Author

Actually, that's wrong. It logs out the error but doesn't fail an assertion. Are errors not sent up with throw inside the node process?

@rwjblue
Copy link
Member

rwjblue commented Nov 3, 2015

Which test is throwing?

@minasmart
Copy link
Author

"FastBoot: route error"

The error is being thrown from ember-runtime/ext/rsvp inside the onerrorDefault method. And it looks like that's the desired outcome. It's just that the catch on fastbootVisit isn't completely intercepting the error.

@minasmart
Copy link
Author

Without understanding a little bit more about how App.visit works, I'm not sure this is within my ability to fix. It gets even weirder. If I wrap the promises in assert.throws there's still an issue. Same if I put a .catch on the RSVP.all which indicates that this is a promise that's being resolved outside the scope of what's invoked in the test. The odd thing is, I can't figure out how to tie it back to the change that I made.

@minasmart
Copy link
Author

More research, if I just don't throw the error in the catch for node, then all the node tests pass. Which also feels wrong.

@rwjblue
Copy link
Member

rwjblue commented Nov 4, 2015

Yes, we definitely need to rethrow unhandled errors.

@minasmart minasmart force-pushed the BUGFIX-mark-handled-errors-and-dont-reraise branch from 1774576 to 25c14ab Compare November 4, 2015 18:28
@minasmart
Copy link
Author

Remaining error is a sauce/IE 10 error, and I don't know how to check the actual output of the test suite for that one.

@rwjblue
Copy link
Member

rwjblue commented Nov 4, 2015

I restarted the sauce labs tests. Likely it is a timeout issue (we have been fighting them for a while).

@minasmart
Copy link
Author

Let's hope.

@rwjblue
Copy link
Member

rwjblue commented Nov 4, 2015

Looks good to me.

@stefanpenner is going to review this when he gets a minute to help ensure we have thought through all the angles on this one...

@minasmart
Copy link
Author

Great! Thanks again for all your help!

@stefanpenner
Copy link
Member

Ember 2.1 introduced a regression where all errors during a transition

has someone isolated what code regressed this?

@stefanpenner
Copy link
Member

ah 854d3be

@@ -705,6 +706,24 @@ var EmberRouter = EmberObject.extend(Evented, {
run.cancel(this._slowTransitionTimer);
}
this._slowTransitionTimer = null;
},

_handledErrors: computed(function() {
Copy link
Member

Choose a reason for hiding this comment

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

If we go down this path, it seems simpler to just brand the given error with a hasBeenHandled symbol. No need to keep these data-structures around.

Copy link
Member

Choose a reason for hiding this comment

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

error[hasBeenHandledSymbol] = true

although this wont work with primitive values, i think thats fine.

Copy link
Member

Choose a reason for hiding this comment

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

Directly branding the error seems ok to me (it really isn't a big change for this PR as the basic infrastructure to brand is done), but I believe that we have a few tests in Ember itself that reject("asdf") or throw "asdf" which will need to be changed.

Copy link
Author

Choose a reason for hiding this comment

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

That was my first attempt, but the problem I hit was that there's no insurance that the error isn't a primitive type, and there are several tests where it IS a primitive type. This wouldn't be a difficult change (to fire an assertion of the type isn't an object, and update the tests) but it would probably be a problem for a lot of apps. It would need to be noted/documented/blogged about or something.

@stefanpenner
Copy link
Member

I am slightly concerned that a "handled error" that is then re-thrown, will be incorrectly branded as handled and will be eaten.

I'll try to carve out some time shortly, to do a deeper dive.
My hunch, is that we have something else sideways forcing us down this specific path.

@rwjblue
Copy link
Member

rwjblue commented Nov 4, 2015

@stefanpenner - _markErrorAsHandled is only called in the scenario that an error action was found and does not return true. This means that the error handler has handled the error (return true meaning do not bubble further). We are not re-throwing a handled error.

@stefanpenner
Copy link
Member

@rwjblue nothing prevents the user from re-throwing, or passing the exception through another promise chain, that may or may not rethrow it later. At which point, it would be totally and unexpectedly ignored.

@minasmart
Copy link
Author

@stefanpenner another promise chain would resolve outside of the transition, so it shouldn't be a problem there (handled errors are only checked as part of the transition's promise). The only way the error could be re-injected into the same transition would be bubbling up. Unless I'm missing a step where after the error hook gets fired Ember makes an effort to resolve more promises as part of the same transition.

@minasmart
Copy link
Author

@stefanpenner Also, just checked. If I have an error hook like this:

error(e) {
  throw e;
  return false;
}

The error isn't swallowed.

Similarly, if the error is thrown as part of a separate promise chain, or other asynchronous task like this:

error(e) {
  Ember.run.next(function() {
    throw e;
  });
  return false;
}

The error is still not swallowed. As part of the transition, it's not re-raised, it gets thrown after the promise completes.

@minasmart
Copy link
Author

Also, @stefanpenner I can write more tests to model the cases you're talking about, if you want more insurance!

@rwjblue
Copy link
Member

rwjblue commented Nov 11, 2015

nothing prevents the user from re-throwing, or passing the exception through another promise chain, that may or may not rethrow it later. At which point, it would be totally and unexpectedly ignored.

We are catching and ignoring the error only if their error action hook returns anything but true. If they throw in the action hook or do additional chaining and later throw, it will not be ignored.

Also, @stefanpenner I can write more tests to model the cases you're talking about, if you want more insurance!

@minasmart - A couple tests to show that my above statement is true would definitely be nice.

@minasmart
Copy link
Author

Should have time to get tests up later this week.

@rwjblue
Copy link
Member

rwjblue commented Nov 16, 2015

@minasmart - That would be awesome, thank you!

@rwjblue
Copy link
Member

rwjblue commented Nov 24, 2015

@minasmart - Have you been able to make any progress here?

@minasmart
Copy link
Author

@rwjblue been extremely busy. Will push them up tomorrow.

@jkeen
Copy link

jkeen commented Nov 24, 2015

@minasmart Thanks so much for identifying this problem and fixing it! Had been struggling for days to track down what was going on with some failures until @rwjblue pointed me here.

@rwjblue
Copy link
Member

rwjblue commented Nov 24, 2015

@minasmart - I definitely understand that, thanks for keeping at this!

Ember 2.1 introduced a regression where all errors during a transition
were thrown, even when those errors had been handled in a route's error
action. This PR adds a test to identify the regression, and adds some
logic and state to identify errors that have been handled, and not
reraise them, while maintaining the desired behaviour of throwing errors
that aren't handled.

This is especially valuable in any application that tests error
handling. Since all errors were being thrown regardless of handling,
failed assertions were being registered by qunit, and it became
impossible to test application flow that entered the error hook.

Further details about this issue are here:
emberjs#12547

Remove leftover debugger

This addresses an issue where handled errors are still thrown

The added test passes on Ember 2.0. It fails on Ember 2.1 because even though
the error is not bubbled, the application throws the handled error.

This is contrary to what the documentation indicates.

Full details about this issue are here: emberjs#12547

This PR also adds a very naive implementation of how to flag an error as having
been handled and prevents reraising, while retaining the functionality of throwing errors
that aren't caught, or are bubbled from the application route

Make error checking methods a part of the router instance

Attach error caching methods to EmberRouter, and use guids for errors

Errors used as keys were just getting `.toString()`ed, so multiple
`Error`s or hashes would all have the same key. This update uses ember
`guidFor` to uniquely identify errors. Thanks for the help @rwjblue!

When creating a new promise, resolve it. Don't fork the promise

Tests expecting errors to bubble up, should bubble errors
@minasmart minasmart force-pushed the BUGFIX-mark-handled-errors-and-dont-reraise branch from 25c14ab to dd160a6 Compare November 26, 2015 17:28
This provides insurance that handled errors aren't swallowed
@minasmart
Copy link
Author

More tests added! Lots of rethrowing different ways.

@rwjblue
Copy link
Member

rwjblue commented Nov 26, 2015

Thank you!

@stefanpenner - Can you re-review? We can add more scenario tests if you still think there are missing scenarios here...

@minasmart
Copy link
Author

It looks like sauce tests timing out again 😞

@stefanpenner
Copy link
Member

cal reminder set for tomorrow morning.

@swalkinshaw
Copy link
Contributor

@stefanpenner friendly reminder about this :) would love to see it get into 2.3.0

@rwjblue
Copy link
Member

rwjblue commented Dec 21, 2015

@swalkinshaw - Agreed.

@rwjblue
Copy link
Member

rwjblue commented Dec 27, 2015

Rebased and slightly tweaked in #12752. Planning to land into 2.3.0 and current stable branch (2.2.x).

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.

5 participants