Skip to content

Commit 2be395a

Browse files
committed
fix: avoid blocking or activating the BP predictor when unnecessary
Previously, we always waited on the breakpoint predictor unless it was explicitly disabled. This was unnecessary in two cases: - If we were able to set an instrumentation breakpoint, this was redundant since we use that to ensure breakpoints get set before scripts run. - We used it even if the source in question was already loaded. This is, while _technically_ useful if a source was present in multiple scripts (e.g. present in multiple webpack bundles), usually not practically necessary, as we do still set the breakpoint if/when that script is parsed. Fixes microsoft/vscode#153470
1 parent ec4063b commit 2be395a

File tree

4 files changed

+33
-19
lines changed

4 files changed

+33
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This changelog records changes to stable releases since 1.50.2. "TBA" changes he
44

55
## Nightly (only)
66

7-
Nothing (yet)
7+
- fix: performance improvements for setting breakpoints in large projects ([vscode#153470](https://github.com/microsoft/vscode/issues/153470))
88

99
## v1.69 (June 2022)
1010

src/adapter/breakpoints.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,12 @@ export class BreakpointManager {
116116
return this._byPath;
117117
}
118118

119-
private _sourceMapHandlerInstalled = false;
119+
/**
120+
* Object set once the source map handler is installed. Contains a promise
121+
* that resolves to true/false based on whether a sourcemap instrumentation
122+
* breakpoint (or equivalent) was able to be set.
123+
*/
124+
private _sourceMapHandlerInstalled?: { entryBpSet: Promise<boolean> };
120125

121126
/**
122127
* User-defined breakpoints by `sourceReference`.
@@ -458,34 +463,29 @@ export class BreakpointManager {
458463
}
459464
}
460465

461-
setSourceMapPauseDisabledForTest() {
462-
// this._sourceMapPauseDisabledForTest = disabled;
463-
}
464-
465466
setPredictorDisabledForTest(disabled: boolean) {
466467
this._predictorDisabledForTest = disabled;
467468
}
468469

469-
private async _installSourceMapHandler(thread: Thread) {
470-
this._sourceMapHandlerInstalled = true;
470+
private _installSourceMapHandler(thread: Thread) {
471471
const perScriptSm =
472472
(this.launchConfig as IChromiumBaseConfiguration).perScriptSourcemaps === 'yes';
473473

474474
if (perScriptSm) {
475-
await Promise.all([
475+
return Promise.all([
476476
this.updateEntryBreakpointMode(thread, EntryBreakpointMode.Greedy),
477477
thread.setScriptSourceMapHandler(false, this._scriptSourceMapHandler),
478-
]);
478+
]).then(() => true);
479479
} else if (this._breakpointsPredictor && !this.launchConfig.pauseForSourceMap) {
480-
await thread.setScriptSourceMapHandler(false, this._scriptSourceMapHandler);
480+
return thread.setScriptSourceMapHandler(false, this._scriptSourceMapHandler);
481481
} else {
482-
await thread.setScriptSourceMapHandler(true, this._scriptSourceMapHandler);
482+
return thread.setScriptSourceMapHandler(true, this._scriptSourceMapHandler);
483483
}
484484
}
485485

486486
private async _uninstallSourceMapHandler(thread: Thread) {
487487
thread.setScriptSourceMapHandler(false);
488-
this._sourceMapHandlerInstalled = false;
488+
this._sourceMapHandlerInstalled = undefined;
489489
}
490490

491491
private _setBreakpoint(b: Breakpoint, thread: Thread): void {
@@ -501,26 +501,33 @@ export class BreakpointManager {
501501
ids: number[],
502502
): Promise<Dap.SetBreakpointsResult> {
503503
if (!this._sourceMapHandlerInstalled && this._thread && params.breakpoints?.length) {
504-
await this._installSourceMapHandler(this._thread);
504+
this._sourceMapHandlerInstalled = { entryBpSet: this._installSourceMapHandler(this._thread) };
505505
}
506506

507+
const wasEntryBpSet = await this._sourceMapHandlerInstalled?.entryBpSet;
507508
params.source.path = urlUtils.platformPathToPreferredCase(params.source.path);
508509

509510
// If we see we want to set breakpoints in file by source reference ID but
510511
// it doesn't exist, they were probably from a previous section. The
511512
// references for scripts just auto-increment per session and are entirely
512513
// ephemeral. Remove the reference so that we fall back to a path if possible.
514+
const containedSource = this._sourceContainer.source(params.source);
513515
if (
514516
params.source.sourceReference /* not (undefined or 0=on disk) */ &&
515517
params.source.path &&
516-
!this._sourceContainer.source(params.source)
518+
!containedSource
517519
) {
518520
params.source.sourceReference = undefined;
519521
}
520522

521523
// Wait until the breakpoint predictor finishes to be sure that we
522-
// can place correctly in breakpoint.set().
523-
if (!this._predictorDisabledForTest && this._breakpointsPredictor) {
524+
// can place correctly in breakpoint.set(), if:
525+
// 1) We don't have a instrumentation bp, which will be able
526+
// to pause before we hit the breakpoint
527+
// 2) We already have loaded the source at least once in the runtime.
528+
// It's possible the source can be loaded again from a different script,
529+
// but we'd prefer to verify the breakpoint ASAP.
530+
if (!wasEntryBpSet && this._breakpointsPredictor && !containedSource) {
524531
const promise = this._breakpointsPredictor.predictBreakpoints(params);
525532
this.addLaunchBlocker(promise);
526533
await promise;

src/adapter/threads.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1663,10 +1663,16 @@ export class Thread implements IVariableStoreLocationProvider {
16631663
);
16641664
}
16651665

1666+
/**
1667+
* Based on whether `pause` is true, sets or unsets an instrumentation
1668+
* breakpoint in the runtime that is hit before sources with scripts.
1669+
* Returns true if the breakpoint was able to be set, which is usually
1670+
* (always?) `false` in Node runtimes.
1671+
*/
16661672
public async setScriptSourceMapHandler(
16671673
pause: boolean,
16681674
handler?: ScriptWithSourceMapHandler,
1669-
): Promise<void> {
1675+
): Promise<boolean> {
16701676
this._scriptWithSourceMapHandler = handler;
16711677

16721678
const needsPause =
@@ -1681,6 +1687,8 @@ export class Thread implements IVariableStoreLocationProvider {
16811687
this._pauseOnSourceMapBreakpointId = undefined;
16821688
await this._cdp.Debugger.removeBreakpoint({ breakpointId });
16831689
}
1690+
1691+
return !!this._pauseOnSourceMapBreakpointId;
16841692
}
16851693

16861694
/**

src/test/breakpoints/breakpointsTest.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ describe('breakpoints', () => {
121121
itIntegrates('source map predicted', async ({ r }) => {
122122
// Breakpoint in source mapped script set before launch use breakpoints predictor.
123123
const p = await r.launchUrl('browserify/pause.html');
124-
p.adapter.breakpointManager.setSourceMapPauseDisabledForTest();
125124
p.adapter.breakpointManager.setPredictorDisabledForTest(false);
126125
const source: Dap.Source = {
127126
path: p.workspacePath('web/browserify/module2.ts'),

0 commit comments

Comments
 (0)