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

track enabled debugger domains for safe simultaneous use #1474

Merged
merged 2 commits into from
Jan 19, 2017
Merged

Conversation

brendankenny
Copy link
Member

@brendankenny brendankenny commented Jan 14, 2017

gatherRunner, driver, and gatherers enable and disable domains at will, and we currently have no system for tracking the state of them so that they don't interfere with each other. This has happened a few times in the past and it's difficult to track down why e.g. an event never fires because someone else disabled the domain you were listening to. Most recently this came up in #1421 where the styles and css-usage gatherers interfere when cleaning up after themselves.

This PR tracks enable and disable commands sent through the driver, eliminates redundant calls and prevents disabling by one user of the domain if others have also enabled it but have not yet disabled it.

The second commit is removing some of the places I've remembered us guarding against ourselves which is less necessary after this change.

@brendankenny
Copy link
Member Author

There are still a few gotchas that we're living with that this doesn't solve. A big one is for domains where we listen to an event and then enable the domain. We rely on the protocol to have buffered all previous events and to replay them all once the domain is enabled. We use this in quite a few places (getSecurityState, getServiceWorkerVersions, etc). If some other gatherer has already enabled that domain then all previous events have already replayed and the next one won't receive them. We don't guard against that now. I added a driver.domainEnabled(domain) method so we can at least check if a domain is already enabled, something we couldn't do before, but that still relies on us being disciplined in interactions between gatherers.

This also doesn't check who is doing the enabling and disabling, so in theory gatherer2 could close the domain gatherer1 just opened and still cause an interaction bug. Tokens for enable/disable didn't really seem worth it, but I'm open to it.

I'm also thinking in a followup PR we could add a "clear domain state" step between passes in gatherRunner, with a suitable log.warn/log.error for any domains accidentally left open. We haven't always been strict about this except for the serious-overhead domains, but we probably should be.

@@ -100,7 +100,8 @@ class Connection {
// handleRawError returns or throws synchronously; wrap to put into promise chain.
return callback.resolve(Promise.resolve().then(_ => {
if (object.error) {
return this.handleRawError(object.error, callback.method);
log.formatProtocol('method <= browser ERR', {method: callback.method}, 'error');
throw new Error(`Protocol error (${callback.method}): ${object.error.message}`);
Copy link
Member Author

@brendankenny brendankenny Jan 14, 2017

Choose a reason for hiding this comment

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

moving this back is basically undoing #861 and #895. DOM.disable returns an error if DOM is already disabled (see details in #861), but with this change the disable command is never sent if it's already disabled, so we never get the error in the first place.

Copy link
Contributor

Choose a reason for hiding this comment

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

Am I right that some domains throw and error if you call disable twice and some do not? Might be worth bringing up with the DT times. That seems inconsistent at best.

Copy link
Member Author

Choose a reason for hiding this comment

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

definitely inconsistent. From #861 is this cs link, currently showing only DOM complains if you disable when already disabled: https://cs.chromium.org/search/?q=if.*!enabled+file:%5Esrc/third_party/WebKit/Source/core/inspector/+case:yes&sq=package:chromium&type=cs

@patrickhulce @paulirish if they want to pursue a debugger protocol bug :)

// Enable Page domain to wait for Page.loadEventFired
.then(_ => this.sendCommand('Page.enable'))
// Check any domains that could interfere with or add overhead to the trace.
if (this.domainEnabled('Debugger')) {
Copy link
Member Author

@brendankenny brendankenny Jan 14, 2017

Choose a reason for hiding this comment

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

this moves these to just being a log error message. Disabling these domains after beforePass has executed if a trace is being taken means that e.g. if someone adds a DBW gatherer that needs a trace then any DBW gatherer that enabled Debugger, CSS, or DOM in beforePass will be broken, possibly fairly subtly. Better to warn on these...or even throw an Error?

Copy link
Contributor

Choose a reason for hiding this comment

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

the log error message will propagate all the way into the report right? If it were an error, the DBW audit/gatherer would surface a -1 in the report?

Copy link
Contributor

Choose a reason for hiding this comment

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

If we're no longer disabling these before a trace, won't that change perf results?

Copy link
Member Author

Choose a reason for hiding this comment

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

If we're no longer disabling these before a trace, won't that change perf results?

In the current config these domains aren't enabled when we take a trace, so there's no change currently. If we add a gatherer that does enable one of these domains before a trace, though, it could cause a difference, which is why I kept a warning message. Maybe that's not enough, though.

the log error message will propagate all the way into the report right? If it were an error, the DBW audit/gatherer would surface a -1 in the report?

log.error only logs it in red to the CLI/console, which hopefully someone catches :)

The problem with the approach on master is that it introduces errors but in the opposite way: it undercuts any gatherers that may unknowingly be running in the same pass as a trace being taken. The result may be subtle and not an error. e.g. a gather is listening for a CSS event that never comes because driver has disabled the CSS domain out from under it, but the lack of an event may just mean whatever it was gathering didn't happen on this particular page.

Maybe as suggested we do want to assert the domains are disabled and throw if they aren't, with a nice error message saying that the current config has gatherers interfering with a trace

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm specifically thinking of CRX users who wouldn't see cli/console logs. But if the error surfaces up at the end (or as a failure during the CRX run), then it's all good.

assert the domains are disabled and throw if they aren't

Yea. That would be a good way for use to verify no one tries do so anything crazy during a trace in the future.

Copy link
Member Author

Choose a reason for hiding this comment

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

turned into thrown errors

@@ -530,9 +588,6 @@ class Driver {

_readTraceFromStream(streamHandle) {
return new Promise((resolve, reject) => {
// COMPAT: We've found `result` not retaining its value in this scenario when it's
Copy link
Member Author

Choose a reason for hiding this comment

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

obsolete comment

// return driver.sendCommand('CSS.disable')
// .then(_ => driver.sendCommand('DOM.disable'))
// .then(_ => resolve(styleHeaders));

Copy link
Member Author

Choose a reason for hiding this comment

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

commented out in #1421. Verified that bringing this back without tracking domains failed the DBW smoke test (in the unused-css-rules audit), but with this change it now passes correctly

* @param {string} domain
* @return {boolean}
*/
domainEnabled(domain) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

just curious here, what are your thoughts in general on isX prefixing for all boolean values/functions?

Copy link
Contributor

Choose a reason for hiding this comment

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

I would be in favor of us "standardizing" on a convention. Been wrestling with this myself.

Copy link
Member Author

Choose a reason for hiding this comment

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

sometimes I find it redundant but in this case I think it helps clarity. done

Copy link
Collaborator

@patrickhulce patrickhulce left a comment

Choose a reason for hiding this comment

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

interesting route! what do you think about having gatherers declare the domains they use as an approach to solve both this and the still lingering buffered events issue?

* @return {boolean}
* @private
*/
_checkDomainToggle(domain, action) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe something slightly more explicit for the name like _shouldSendDomainCommand or _shouldToggleDomain?

Copy link
Member Author

Choose a reason for hiding this comment

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

sounds good

@brendankenny
Copy link
Member Author

interesting route! what do you think about having gatherers declare the domains they use as an approach to solve both this and the still lingering buffered events issue?

It seems the main problem with this approach is how you define a failure case. e.g. it's fine for the styles and css-usage gatherers to both enable the CSS domain simultaneously, and in fact they should each enable it, as they can't depend on being run in the same pass for all time.

Taking a different tack, there's not really a way to limit the domain declarations to gatherers who need to say "I'm going to enable this domain and it better be disabled when I enable it", because some other gatherer might need that domain but not need the events from it, so doesn't need that guarantee.

It really seems like manually checking that a domain is disabled when you need that behavior, and throwing an unrecoverable error otherwise, is the best way to catch this, but also the least automatically testable. I guess we could wrap that up for folks, driver.assertDomainDisabled('Security') or whatever, though not sure how helpful that really is vs them just doing the conditional throw themselves

@patrickhulce
Copy link
Collaborator

It really seems like manually checking that a domain is disabled when you need that behavior, and throwing an unrecoverable error otherwise, is the best way to catch this, but also the least automatically testable. I guess we could wrap that up for folks, driver.assertDomainDisabled('Security')

I like that. In cases where they care the gatherer can assert and flip on manually, but in cases where they don't need it to be disabled at start it can be just declared as needed.

* @private
*/
_checkDomainToggle(domain, action) {
const enable = action === 'enable';
Copy link
Contributor

Choose a reason for hiding this comment

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

should action just be a boolean instead of passing string around?

Copy link
Member Author

Choose a reason for hiding this comment

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

it's slightly more awkward but probably better. Done.

* @param {string} domain
* @return {boolean}
*/
domainEnabled(domain) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would be in favor of us "standardizing" on a convention. Been wrestling with this myself.

// Enable Page domain to wait for Page.loadEventFired
.then(_ => this.sendCommand('Page.enable'))
// Check any domains that could interfere with or add overhead to the trace.
if (this.domainEnabled('Debugger')) {
Copy link
Contributor

Choose a reason for hiding this comment

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

the log error message will propagate all the way into the report right? If it were an error, the DBW audit/gatherer would surface a -1 in the report?

// Enable Page domain to wait for Page.loadEventFired
.then(_ => this.sendCommand('Page.enable'))
// Check any domains that could interfere with or add overhead to the trace.
if (this.domainEnabled('Debugger')) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're no longer disabling these before a trace, won't that change perf results?

@brendankenny
Copy link
Member Author

updated. PTAL

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.

None yet

3 participants