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: throw fatally on page hang #6497

Merged
merged 5 commits into from
Nov 16, 2018
Merged

core: throw fatally on page hang #6497

merged 5 commits into from
Nov 16, 2018

Conversation

patrickhulce
Copy link
Collaborator

Summary
I've spent way too much time waffling back and forth on how to handle this, so I thought I'd just put it up for discussion. This PR throws a fatal error if, after our maxWaitForLoad is reached, the page doesn't seem to respond.

Right now this basically gets us earlier exit than trying to do all the gathering and not much else. I agree with @brendankenny that it's a bit dangerous to try to kill JavaScript and continue. If we do anything else I think it might be alternative 1, but still feels risky.

Alternatives:

  • Wait for maxWaitForLoad, kill JavaScript, wait and see if page is still hung, then either fatally throw or try to continue
  • Wait for maxWaitForLoad, kill JavaScript, and try to continue right away
  • Continuously check if page is hung, kill JavaScript as soon as we think it's hung, and do A or B

Yes, it just goes on and on my friends.
Some people started computing it, not knowing what it was.
And they'll continue computing it forever just because,
This is the function that never end.
Copy link
Member

Choose a reason for hiding this comment

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

🤣🤣 nicely done

Copy link
Member

Choose a reason for hiding this comment

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

(never ends :)

@@ -0,0 +1,19 @@
/**
* @license Copyright 2017 Google Inc. All Rights Reserved.
Copy link
Member

Choose a reason for hiding this comment

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

2018

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@@ -0,0 +1,16 @@
<html>
Copy link
Member

Choose a reason for hiding this comment

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

license

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

'use strict';

/**
* Expected Lighthouse audit values for sites with various errors.
Copy link
Member

Choose a reason for hiding this comment

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

🎉

@@ -98,10 +99,12 @@ function runLighthouse(url, configPath, isDebug) {
console.error(`STDERR: ${runResults.stderr}`);
}

const lhr = fs.readFileSync(outputPath, 'utf8');
const lhr = runResults.status === PAGE_HUNG_EXIT_CODE ?
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 don't really do anything else with it below (e.g. no path to debug output), could this be just an early return of the PAGE_HUNG LHR?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah good point, done!

this._nextProtocolTimeout = DEFAULT_PROTOCOL_TIMEOUT;
const timeout = typeof this._nextProtocolTimeout === 'undefined' ?
DEFAULT_PROTOCOL_TIMEOUT : this._nextProtocolTimeout;
this._nextProtocolTimeout = undefined;
Copy link
Member

Choose a reason for hiding this comment

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

hmm, I don't follow what this is doing. It seems the default state goes to undefined, which now gets interpreted as "use the default", but why was that needed for an extra signal?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

removed it all 👍

@brendankenny
Copy link
Member

re: alternatives, I personally vote for what's here and then ramping up in the future if we think we can get away with them

This is the function that never ends
Yes, it just goes on and on my friends.
Some people started computing it, not knowing what it was.
And they'll continue computing it forever just because,
Copy link
Member

Choose a reason for hiding this comment

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

🤣

@@ -0,0 +1,19 @@
/**
* @license Copyright 2017 Google Inc. All Rights Reserved.
Copy link
Member

Choose a reason for hiding this comment

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

its like finding a pokemon! got 'em!


if (await this.isPageHung()) {
log.warn('Driver', 'Page appears to be hung, killing JavaScript...');
await this.sendCommand('Runtime.terminateExecution');
Copy link
Member

Choose a reason for hiding this comment

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

i think we should also run setScriptDisabled({disabled: true}) at this point.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

async isPageHung() {
try {
this.setNextProtocolTimeout(5000);
await this.evaluateAsync('"ping"');
Copy link
Member

Choose a reason for hiding this comment

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

to simplify we could do a pure sendCommand('Runtime.evalute') here. we don't really need all that promise wrapping stuff with something so simple.

but i guess it also means we could probably avoid some of the nextTimeout juggling we have since _evaluateInContext has its own timeout.

double bonus?! :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

SG!

*/
async isPageHung() {
try {
this.setNextProtocolTimeout(5000);
Copy link
Member

Choose a reason for hiding this comment

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

5s feels generous. why not 1s

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

well for turning a run fatal it seemed like we wanted to be pretty sure it's hung and not just hitting a long task during a slow page load, but if you think 1s is enough and 45s pages be damned, I'm on board :)

@@ -749,7 +773,7 @@ class Driver {
loadPromise,
maxTimeoutPromise,
]);
cleanupFn();
await cleanupFn();
Copy link
Member

Choose a reason for hiding this comment

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

now necessary since we have the awaits inside of maxTimeoutPromise, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

well the awaits are inside the function that it returns too, so we want to wait to find out if the page is hung before moving on

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

order of execution is

  1. wait for earlier of load + quiet and maxWaitForLoad
  2. If we reached maxWaitForLoad then check if we're hung

we don't want to be doing our killing and hang check while we're trying to see if the page is quiet.

/* Used when the page stopped responding and did not finish loading. */
PAGE_HUNG: {
code: 'PAGE_HUNG',
message: strings.pageLoadFailed,
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 do a new string?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sure, the one I have is largely the same as pageLoadFailed though, so if you have particular hopes of what you want to convey, input welcome :)

@@ -148,6 +148,12 @@ const ERRORS = {
message: strings.pageLoadFailedInsecure,
lhrRuntimeError: true,
},
/* Used when the page stopped responding and did not finish loading. */
PAGE_HUNG: {
Copy link
Member

Choose a reason for hiding this comment

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

this'll need to go into the proto. :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

Copy link
Member

@paulirish paulirish left a comment

Choose a reason for hiding this comment

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

yes I think we should do this.

I will point out, I think it's possible for the hang to happen during mid-gathering, in which case we wouldn't catch this.

After landing, I think next step is to drop protocol timeout to 10-ish seconds. And then run isPageHung() between gatherers. Unfortch, this overkill is only necessary because of LR, but... yah.

@paulirish
Copy link
Member

@patrickhulce some of the smokehouse tests are broke. can you take a look

@patrickhulce
Copy link
Collaborator Author

some of the smokehouse tests are broke

sneaky, it was an extra empty object definition in the smoketests {} 😈

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.

3 participants