Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion scripts/generate-cdp-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ async function generate() {
result.push(` * Auto-generated by generate-cdp-api.js, do not edit manually. *`);
result.push(` ****************************************************************/`);
result.push(``);
result.push(`import { IDisposable } from '../common/disposable'; `);
result.push(``);
result.push(`export namespace Cdp {`);
result.push(` export type integer = number;`);
interfaceSeparator();
Expand Down Expand Up @@ -104,7 +106,7 @@ async function generate() {
for (const event of events) {
apiSeparator();
appendText(event.description, ' ');
result.push(` on(event: '${event.name}', listener: (event: ${name}.${toTitleCase(event.name)}Event) => void): void;`);
result.push(` on(event: '${event.name}', listener: (event: ${name}.${toTitleCase(event.name)}Event) => void): IDisposable;`);
}
result.push(` }`);

Expand Down
100 changes: 100 additions & 0 deletions src/adapter/asyncStackPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import { IDisposable, noOpDisposable, DisposableList } from '../common/disposable';
import Cdp from '../cdp/api';
import { AsyncStackMode } from '../configuration';
import { EventEmitter } from '../common/events';

/**
* Controls when async stack traces are enabled in the debugee, either
* at start or only when we resolve a breakpoint.
*
* This is useful because requesting async stacktraces increases bookkeeping
* that V8 needs to do and can cause significant slowdowns.
*/
export interface IAsyncStackPolicy {
/**
* Installs the policy on the given CDP API.
*/
connect(cdp: Cdp.Api): Promise<IDisposable>;
}

const disabled: IAsyncStackPolicy = { connect: async () => noOpDisposable };

const eager = (maxDepth: number): IAsyncStackPolicy => ({
async connect(cdp) {
await cdp.Debugger.setAsyncCallStackDepth({ maxDepth });
return noOpDisposable;
},
});

const onceBp = (maxDepth: number): IAsyncStackPolicy => {
const onEnable: EventEmitter<void> | undefined = new EventEmitter<void>();
let enabled = false;
const tryEnable = () => {
if (!enabled) {
enabled = true;
onEnable.fire();
}
};

return {
async connect(cdp) {
if (enabled) {
await cdp.Debugger.setAsyncCallStackDepth({ maxDepth });
return noOpDisposable;
}

const disposable = new DisposableList();

disposable.push(
// Another session enabled breakpoints. Turn this on as well, e.g. if
// we have a parent page and webworkers, when we debug the webworkers
// should also have their async stacks turned on.
onEnable.event(() => {
disposable.dispose();
cdp.Debugger.setAsyncCallStackDepth({ maxDepth });
}),
// when a breakpoint resolves, turn on stacks because we're likely to
// pause sooner or later
cdp.Debugger.on('breakpointResolved', tryEnable),
// start collecting on a pause event. This can be from source map
// instrumentation, entrypoint breakpoints, debugger statements, or user
// defined breakpoints. Instrumentation points happen all the time and
// can be ignored. For others, including entrypoint breaks (which
// indicate there's a user break somewhere in the file) we should turn on.
cdp.Debugger.on('paused', evt => {
if (evt.reason !== 'instrumentation') {
tryEnable();
}
}),
);

return disposable;
},
};
};

const defaultPolicy = eager(32);

export const getAsyncStackPolicy = (mode: AsyncStackMode) => {
if (mode === false) {
return disabled;
}

if (mode === true) {
return defaultPolicy;
}

if ('onAttach' in mode) {
return eager(mode.onAttach);
}

if ('onceBreakpointResolved' in mode) {
return onceBp(mode.onceBreakpointResolved);
}

return defaultPolicy;
};
17 changes: 13 additions & 4 deletions src/adapter/debugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

import { IDisposable } from '../common/events';
import * as nls from 'vscode-nls';
import Dap from '../dap/api';
import * as sourceUtils from '../common/sourceUtils';
Expand All @@ -23,6 +22,10 @@ import { join } from 'path';
import { IDeferred, getDeferred } from '../common/promiseUtil';
import { SourceMapCache } from './sourceMapCache';
import { logPerf } from '../telemetry/performance';
import { IAsyncStackPolicy } from './asyncStackPolicy';
import { logger } from '../common/logging/logger';
import { LogTag } from '../common/logging';
import { DisposableList } from '../common/disposable';

const localize = nls.loadMessageBundle();

Expand All @@ -32,7 +35,7 @@ export class DebugAdapter {
readonly dap: Dap.Api;
readonly sourceContainer: SourceContainer;
readonly breakpointManager: BreakpointManager;
private _disposables: IDisposable[] = [];
private _disposables = new DisposableList();
private _pauseOnExceptionsState: PauseOnExceptionsState = 'none';
private _customBreakpoints = new Set<string>();
private _thread: Thread | undefined;
Expand All @@ -42,6 +45,7 @@ export class DebugAdapter {
dap: Dap.Api,
rootPath: string | undefined,
sourcePathResolver: ISourcePathResolver,
private readonly asyncStackPolicy: IAsyncStackPolicy,
private readonly launchConfig: AnyLaunchConfiguration,
private readonly _rawTelemetryReporter: IRawTelemetryReporter,
) {
Expand Down Expand Up @@ -283,6 +287,12 @@ export class DebugAdapter {
);
for (const breakpoint of this._customBreakpoints)
this._thread.updateCustomBreakpoint(breakpoint, true);

this.asyncStackPolicy
.connect(cdp)
.then(d => this._disposables.push(d))
.catch(err => logger.error(LogTag.Internal, 'Error enabling async stacks', err));

this._thread.setPauseOnExceptionsState(this._pauseOnExceptionsState);
this.breakpointManager.setThread(this._thread);
return this._thread;
Expand Down Expand Up @@ -357,7 +367,6 @@ export class DebugAdapter {
}

dispose() {
for (const disposable of this._disposables) disposable.dispose();
this._disposables = [];
this._disposables.dispose();
}
}
3 changes: 0 additions & 3 deletions src/adapter/threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,6 @@ export class Thread implements IVariableStoreDelegate {

this._ensureDebuggerEnabledAndRefreshDebuggerId();
this._delegate.initialize();
if (this.launchConfig.showAsyncStacks) {
this._cdp.Debugger.setAsyncCallStackDepth({ maxDepth: 32 });
}
const scriptSkipper = this._delegate.skipFiles();
if (scriptSkipper) {
// Note: here we assume that source container does only have a single thread.
Expand Down
8 changes: 8 additions & 0 deletions src/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import * as errors from './dap/errors';
import { ILauncher, ILaunchResult, ITarget } from './targets/targets';
import { RawTelemetryReporterToDap } from './telemetry/telemetryReporter';
import { filterErrorsReportedToTelemetry } from './telemetry/unhandledErrorReporter';
import { IAsyncStackPolicy, getAsyncStackPolicy } from './adapter/asyncStackPolicy';

const localize = nls.loadMessageBundle();

Expand All @@ -47,6 +48,7 @@ export class Binder implements IDisposable {
private _launchParams?: AnyLaunchConfiguration;
private _rawTelemetryReporter: RawTelemetryReporterToDap | undefined;
private _clientCapabilities: Dap.InitializeParams | undefined;
private _asyncStackPolicy?: IAsyncStackPolicy;

constructor(
delegate: IBinderDelegate,
Expand Down Expand Up @@ -240,10 +242,16 @@ export class Binder implements IDisposable {
if (!cdp) return;
const connection = await this._delegate.acquireDap(target);
const dap = await connection.dap();

if (!this._asyncStackPolicy) {
this._asyncStackPolicy = getAsyncStackPolicy(this._launchParams!.showAsyncStacks);
}

const debugAdapter = new DebugAdapter(
dap,
this._launchParams?.rootPath || undefined,
target.sourcePathResolver(),
this._asyncStackPolicy,
this._launchParams!,
this._rawTelemetryReporter!,
);
Expand Down
26 changes: 25 additions & 1 deletion src/build/generate-contributions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,33 @@ const baseConfigurationAttributes: ConfigurationAttributes<IBaseConfiguration> =
default: false,
},
showAsyncStacks: {
type: 'boolean',
description: refString('node.showAsyncStacks.description'),
default: true,
oneOf: [
{
type: 'boolean',
},
{
type: 'object',
required: ['onAttach'],
properties: {
onAttach: {
type: 'number',
default: 32,
},
},
},
{
type: 'object',
required: ['onceBreakpointResolved'],
properties: {
onceBreakpointResolved: {
type: 'number',
default: 32,
},
},
},
],
},
skipFiles: {
type: 'array',
Expand Down
Loading