Skip to content
This repository has been archived by the owner on Oct 2, 2021. It is now read-only.

Block while parsing scripts to prevent sending 'removes' before 'adds' #274

Merged
merged 7 commits into from
Feb 2, 2018

Conversation

digeff
Copy link
Contributor

@digeff digeff commented Jan 29, 2018

We were running into an issue where we send 'remove' myscript.js before we sent the 'add' myscript.js when we went into a page, and quickly redirected into another page.

That happened because we have some async code in how we process onScriptParsed, so that processing can be on-flight, and onExecutionContextsCleared very quickly sends the 'remove' events, and we weren't waiting for the onScriptParsed events to be completed.
This fixes solve part of the issue.

I think there is another unrelated reason that I'm investigating after this.

@roblourens
Copy link
Member

I don't think this will fix your problem. await won't keep the code in onExecutionContextsCleared from running. Your change will keep the rest of the code in onScriptParsed from running before all the scriptParsed notifications are sent, but won't stop other event handlers.

You probably want a promise that you use as an async queue, like

private _scriptParsedQueue: Promise<any>;

...

onScriptParsed() {
  this._scriptParsedQueue = this._scriptParsedQueue.then(() => send load events)
}

onExecutionContextsCleared() {
  this._scriptParsedQueue.then(() => send delete events)
}

@roblourens
Copy link
Member

The problem is that we are getting a breakpointResolved event for the new script before you have processed the scriptLoaded event, so the breakpointResolved handler stops at this line: https://github.com/Microsoft/vscode-chrome-debug-core/blob/master/src/chrome/chromeDebugAdapter.ts#L930

Two options:

  1. Only delay the sending of the events to the client, not the whole onScriptParsed handler
  2. Have the breakpointResolved handler also wait on the scriptParsedQueue

I think the first one is better for perf, and would hopefully prevent other issues with things that depend on the _scriptsById map.

@roblourens
Copy link
Member

Could you add a test to the PR as well, that adds some scripts, refreshes the page, and ensures that the script load/unload events are sent in the proper order? You can take some inspiration from this failing test on how to do that.

@digeff
Copy link
Contributor Author

digeff commented Feb 1, 2018

Ran into a bug with the newest typescript: microsoft/TypeScript#21491
changing to an older version

@digeff digeff force-pushed the block_while_parsing_scripts branch from b2b9611 to 4a5ce3a Compare February 1, 2018 01:11
// Wait to finish loading sourcemaps from the initial scriptParsed events
if (this._initialSourceMapsP) {
const initialSourceMapsP = this._initialSourceMapsP;
this._initialSourceMapsP = null;

initialSourceMapsP.then(() => {
await initialSourceMapsP.then(async () => {
Copy link
Member

Choose a reason for hiding this comment

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

The mixing of async/await and promises is a little confusing here (await ... .then()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

this._session.sendEvent(new InitializedEvent());
this._earlyScripts.forEach(script => this.sendLoadedSourceEvent(script));
await Promise.all(this._earlyScripts.map(async script => await this.sendLoadedSourceEvent(script)));
Copy link
Member

Choose a reason for hiding this comment

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

The async/await inside map is redundant, just return the promise from this.sendLoadedSourceEvent

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

this._earlyScripts = null;
});
}
}

public doAfterProcessingSourceEvents(action: () => void): Promise<void> {
Copy link
Member

Choose a reason for hiding this comment

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

action returns a Promise

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The use inside onExecutionContextsCleared() doesn't return a promise


Promise.all(asyncOperations).then(() =>
this.clearTargetContext());
return Promise.all(asyncOperations).then(() =>
Copy link
Member

@roblourens roblourens Feb 2, 2018

Choose a reason for hiding this comment

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

This is confusing, asyncOperations will not be populated by this point because it's populated by an async function that we don't wait on. I'm not sure what's supposed to happen.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice catch... I missed this...

this._session.sendEvent(scriptEvent)));
});
private onExecutionContextsCleared(): Promise<void> {
const asyncOperations = [];
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 this method picked up an extra level of indentation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

});

Promise.all(asyncOperations).then(() =>
this.clearTargetContext());
return this.doAfterProcessingSourceEvents(async () => { // Execute after sending all the 'removed' events
Copy link
Member

Choose a reason for hiding this comment

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

This is a confusing flow now. The asyncOperations array isn't needed if doAfterProcessingSourceEvents is forcing this to happen after sending the script removed events are sent.

Also, you could iterate the scripts inside the doAfterProcessingSourceEvent. That will be faster because the script processing doesn't have to happen one by one. You can use the await Promise.all(scriptById.map(async () => ... pattern again.

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 rewrote this to use async/wait. What do you think?

Copy link
Member

@roblourens roblourens left a comment

Choose a reason for hiding this comment

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

I think this is right. Async code is hard :) Thanks!

@roblourens roblourens merged commit c07dc3f into microsoft:master Feb 2, 2018
@digeff digeff deleted the block_while_parsing_scripts branch February 2, 2018 21:26
@roblourens roblourens added this to the February 2018 milestone Mar 5, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants