From 64980ea1f3f532c82bb6c28d27bba9ef2c5b4463 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 13 Sep 2019 13:00:45 +0200 Subject: [PATCH] debug: better cancelation support fixes #80374 --- .../api/browser/mainThreadDebugService.ts | 11 ++-- .../contrib/debug/browser/debugSession.ts | 50 +++++++++++++---- .../contrib/debug/browser/rawDebugSession.ts | 54 +++++++++---------- .../workbench/contrib/debug/browser/repl.ts | 2 +- .../debug/common/abstractDebugAdapter.ts | 10 +++- .../workbench/contrib/debug/common/debug.ts | 10 ++-- .../contrib/debug/common/debugModel.ts | 18 ++++--- .../contrib/debug/common/replModel.ts | 2 +- .../contrib/debug/node/debugAdapter.ts | 7 +-- .../debug/test/browser/baseDebugView.test.ts | 4 +- .../contrib/debug/test/common/mockDebug.ts | 2 +- 11 files changed, 105 insertions(+), 65 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index 721187994a6ac..fcdfb0e417888 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -361,23 +361,24 @@ class ExtensionHostDebugAdapter extends AbstractDebugAdapter { super(); } - public fireError(handle: number, err: Error) { + fireError(handle: number, err: Error) { this._onError.fire(err); } - public fireExit(handle: number, code: number, signal: string) { + fireExit(handle: number, code: number, signal: string) { this._onExit.fire(code); } - public startSession(): Promise { + startSession(): Promise { return Promise.resolve(this._proxy.$startDASession(this._handle, this._ds.getSessionDto(this._session))); } - public sendMessage(message: DebugProtocol.ProtocolMessage): void { + sendMessage(message: DebugProtocol.ProtocolMessage): void { this._proxy.$sendDAMessage(this._handle, convertToDAPaths(message, true)); } - public stopSession(): Promise { + async stopSession(): Promise { + await this.cancelPendingRequests(); return Promise.resolve(this._proxy.$stopDASession(this._handle)); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 3a3add8c50229..42da79e250d52 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -33,6 +33,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { variableSetEmitter } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; export class DebugSession implements IDebugSession { @@ -43,6 +44,7 @@ export class DebugSession implements IDebugSession { private sources = new Map(); private threads = new Map(); + private cancellationMap = new Map(); private rawListeners: IDisposable[] = []; private fetchThreadsScheduler: RunOnceScheduler | undefined; private repl: ReplModel; @@ -235,6 +237,7 @@ export class DebugSession implements IDebugSession { */ terminate(restart = false): Promise { if (this.raw) { + this.cancelAllRequests(); if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') { return this.raw.terminate(restart).then(response => { return undefined; @@ -252,6 +255,7 @@ export class DebugSession implements IDebugSession { */ disconnect(restart = false): Promise { if (this.raw) { + this.cancelAllRequests(); return this.raw.disconnect(restart).then(response => { return undefined; }); @@ -264,6 +268,7 @@ export class DebugSession implements IDebugSession { */ restart(): Promise { if (this.raw) { + this.cancelAllRequests(); return this.raw.restart().then(() => undefined); } return Promise.reject(new Error('no debug adapter')); @@ -380,7 +385,8 @@ export class DebugSession implements IDebugSession { stackTrace(threadId: number, startFrame: number, levels: number): Promise { if (this.raw) { - return this.raw.stackTrace({ threadId, startFrame, levels }); + const token = this.getNewCancellationToken(threadId); + return this.raw.stackTrace({ threadId, startFrame, levels }, token); } return Promise.reject(new Error('no debug adapter')); } @@ -402,16 +408,18 @@ export class DebugSession implements IDebugSession { return Promise.reject(new Error('no debug adapter')); } - scopes(frameId: number): Promise { + scopes(frameId: number, threadId: number): Promise { if (this.raw) { - return this.raw.scopes({ frameId }); + const token = this.getNewCancellationToken(threadId); + return this.raw.scopes({ frameId }, token); } return Promise.reject(new Error('no debug adapter')); } - variables(variablesReference: number, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise { + variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise { if (this.raw) { - return this.raw.variables({ variablesReference, filter, start, count }); + const token = threadId ? this.getNewCancellationToken(threadId) : undefined; + return this.raw.variables({ variablesReference, filter, start, count }, token); } return Promise.reject(new Error('no debug adapter')); } @@ -541,14 +549,14 @@ export class DebugSession implements IDebugSession { return Promise.reject(new Error('no debug adapter')); } - completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number): Promise { + completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise { if (this.raw) { return this.raw.completions({ frameId, text, column: position.column, - line: position.lineNumber - }).then(response => { + line: position.lineNumber, + }, token).then(response => { const result: CompletionItem[] = []; if (response && response.body && response.body.targets) { @@ -757,6 +765,16 @@ export class DebugSession implements IDebugSession { this.rawListeners.push(this.raw.onDidContinued(event => { const threadId = event.body.allThreadsContinued !== false ? undefined : event.body.threadId; + if (threadId) { + const tokens = this.cancellationMap.get(threadId); + this.cancellationMap.delete(threadId); + if (tokens) { + tokens.forEach(t => t.cancel()); + } + } else { + this.cancelAllRequests(); + } + this.model.clearThreads(this.getId(), false, threadId); this._onDidChangeState.fire(); })); @@ -787,7 +805,7 @@ export class DebugSession implements IDebugSession { source: this.getSource(event.body.source) } : undefined; if (event.body.variablesReference) { - const container = new ExpressionContainer(this, event.body.variablesReference, generateUuid()); + const container = new ExpressionContainer(this, undefined, event.body.variablesReference, generateUuid()); outpuPromises.push(container.getChildren().then(children => { return Promise.all(waitFor).then(() => children.forEach(child => { // Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names) @@ -896,6 +914,20 @@ export class DebugSession implements IDebugSession { return source; } + private getNewCancellationToken(threadId: number): CancellationToken { + const tokenSource = new CancellationTokenSource(); + const tokens = this.cancellationMap.get(threadId) || []; + tokens.push(tokenSource); + this.cancellationMap.set(threadId, tokens); + + return tokenSource.token; + } + + private cancelAllRequests(): void { + this.cancellationMap.forEach(tokens => tokens.forEach(t => t.cancel())); + this.cancellationMap.clear(); + } + private getUriKey(uri: URI): string { // TODO: the following code does not make sense if uri originates from a different platform return platform.isLinux ? uri.toString() : uri.toString().toLowerCase(); diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 28e19bf76684f..2e8ce0cf55e5f 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -19,6 +19,7 @@ import { IProcessEnvironment } from 'vs/base/common/platform'; import { env as processEnv } from 'vs/base/common/process'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { CancellationToken } from 'vs/base/common/cancellation'; /** * This interface represents a single command line argument split into a "prefix" and a "path" half. @@ -112,8 +113,6 @@ export class RawDebugSession implements IDisposable { } })); - this.toDispose.push(this.onDidContinued(() => this.cancelPendingRequests())); - this.debugAdapter.onEvent(event => { switch (event.event) { case 'initialized': @@ -346,9 +345,9 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('restartFrame not supported')); } - completions(args: DebugProtocol.CompletionsArguments): Promise { + completions(args: DebugProtocol.CompletionsArguments, token: CancellationToken): Promise { if (this.capabilities.supportsCompletionsRequest) { - return this.send('completions', args); + return this.send('completions', args, token); } return Promise.reject(new Error('completions not supported')); } @@ -389,8 +388,8 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('configurationDone not supported')); } - stackTrace(args: DebugProtocol.StackTraceArguments): Promise { - return this.send('stackTrace', args); + stackTrace(args: DebugProtocol.StackTraceArguments, token: CancellationToken): Promise { + return this.send('stackTrace', args, token); } exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): Promise { @@ -400,12 +399,12 @@ export class RawDebugSession implements IDisposable { return Promise.reject(new Error('exceptionInfo not supported')); } - scopes(args: DebugProtocol.ScopesArguments): Promise { - return this.send('scopes', args); + scopes(args: DebugProtocol.ScopesArguments, token: CancellationToken): Promise { + return this.send('scopes', args, token); } - variables(args: DebugProtocol.VariablesArguments): Promise { - return this.send('variables', args); + variables(args: DebugProtocol.VariablesArguments, token?: CancellationToken): Promise { + return this.send('variables', args, token); } source(args: DebugProtocol.SourceArguments): Promise { @@ -482,7 +481,7 @@ export class RawDebugSession implements IDisposable { if (!this.inShutdown) { this.inShutdown = true; if (this.debugAdapter) { - return this.send('disconnect', { restart }, 500).then(() => { + return this.send('disconnect', { restart }, undefined, 500).then(() => { this.stopAdapter(error); }, () => { // ignore error @@ -494,23 +493,10 @@ export class RawDebugSession implements IDisposable { return Promise.resolve(undefined); } - private cancelPendingRequests(): void { - if (this.debugAdapter) { - if (this.capabilities.supportsCancelRequest) { - this.debugAdapter.getPendingRequestIds().forEach(requestId => { - this.cancel({ requestId }); - }); - } else { - this.debugAdapter.cancelPendingRequests(); - } - } - } - private stopAdapter(error?: Error): Promise { if (this.debugAdapter) { const da = this.debugAdapter; this.debugAdapter = null; - this.cancelPendingRequests(); return da.stopSession().then(_ => { this.debugAdapterStopped = true; this.fireAdapterExitEvent(error); @@ -642,20 +628,34 @@ export class RawDebugSession implements IDisposable { return this.windowsService.openExtensionDevelopmentHostWindow(args, env); } - private send(command: string, args: any, timeout?: number): Promise { + private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { return new Promise((completeDispatch, errorDispatch) => { if (!this.debugAdapter) { errorDispatch(new Error('no debug adapter found')); return; } - this.debugAdapter.sendRequest(command, args, (response: R) => { + let cancelationListener: IDisposable; + const requestId = this.debugAdapter.sendRequest(command, args, (response: R) => { + if (cancelationListener) { + cancelationListener.dispose(); + } + if (response.success) { completeDispatch(response); } else { errorDispatch(response); } }, timeout); - }).then(response => response, err => Promise.reject(this.handleErrorResponse(err))); + + if (token) { + cancelationListener = token.onCancellationRequested(() => { + cancelationListener.dispose(); + if (this.capabilities.supportsCancelRequest) { + this.cancel({ requestId }); + } + }); + } + }).then(undefined, err => Promise.reject(this.handleErrorResponse(err))); } private handleErrorResponse(errorResponse: DebugProtocol.Response): Error { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 3b32a852e44f0..ba438198881b1 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -149,7 +149,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati const text = model.getLineContent(position.lineNumber); const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined; - const suggestions = await session.completions(frameId, text, position, overwriteBefore); + const suggestions = await session.completions(frameId, text, position, overwriteBefore, token); return { suggestions }; } diff --git a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts index f288628f31431..56a9de64e264d 100644 --- a/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts +++ b/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts @@ -71,7 +71,7 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { } } - sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void, timeout?: number): void { + sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void, timeout?: number): number { const request: any = { command: command }; @@ -101,6 +101,8 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { // store callback for this request this.pendingRequests.set(request.seq, clb); } + + return request.seq; } acceptMessage(message: DebugProtocol.ProtocolMessage): void { @@ -137,7 +139,11 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { this.sendMessage(message); } - async cancelPendingRequests(): Promise { + protected async cancelPendingRequests(): Promise { + if (this.pendingRequests.size === 0) { + return Promise.resolve(); + } + const pending = new Map void>(); this.pendingRequests.forEach((value, key) => pending.set(key, value)); await timeout(500); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 7900963c47c96..8f09afa616517 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -210,8 +210,8 @@ export interface IDebugSession extends ITreeElement { stackTrace(threadId: number, startFrame: number, levels: number): Promise; exceptionInfo(threadId: number): Promise; - scopes(frameId: number): Promise; - variables(variablesReference: number, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise; + scopes(frameId: number, threadId: number): Promise; + variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise; evaluate(expression: string, frameId?: number, context?: string): Promise; customRequest(request: string, args: any): Promise; @@ -225,7 +225,7 @@ export interface IDebugSession extends ITreeElement { pause(threadId: number): Promise; terminateThreads(threadIds: number[]): Promise; - completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number): Promise; + completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number, token: CancellationToken): Promise; setVariable(variablesReference: number | undefined, name: string, value: string): Promise; loadSource(resource: uri): Promise; getLoadedSources(): Promise; @@ -504,10 +504,8 @@ export interface IDebugAdapter extends IDisposable { startSession(): Promise; sendMessage(message: DebugProtocol.ProtocolMessage): void; sendResponse(response: DebugProtocol.Response): void; - sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void, timeout?: number): void; + sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void, timeout?: number): number; stopSession(): Promise; - cancelPendingRequests(): void; - getPendingRequestIds(): number[]; } export interface IDebugAdapterFactory extends ITerminalLauncher { diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 8adaccd5b0ff4..c0830fb7e6047 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -37,6 +37,7 @@ export class ExpressionContainer implements IExpressionContainer { constructor( protected session: IDebugSession | undefined, + protected threadId: number | undefined, private _reference: number | undefined, private id: string, public namedVariables: number | undefined = 0, @@ -85,7 +86,7 @@ export class ExpressionContainer implements IExpressionContainer { for (let i = 0; i < numberOfChunks; i++) { const start = (this.startOfVariables || 0) + i * chunkSize; const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize); - children.push(new Variable(this.session, this, this.reference, `[${start}..${start + count - 1}]`, '', '', undefined, count, { kind: 'virtual' }, undefined, true, start)); + children.push(new Variable(this.session, this.threadId, this, this.reference, `[${start}..${start + count - 1}]`, '', '', undefined, count, { kind: 'virtual' }, undefined, true, start)); } return children; @@ -109,12 +110,12 @@ export class ExpressionContainer implements IExpressionContainer { } private fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise { - return this.session!.variables(this.reference || 0, filter, start, count).then(response => { + return this.session!.variables(this.reference || 0, this.threadId, filter, start, count).then(response => { return response && response.body && response.body.variables ? distinct(response.body.variables.filter(v => !!v && isString(v.name)), (v: DebugProtocol.Variable) => v.name).map((v: DebugProtocol.Variable) => - new Variable(this.session, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type)) + new Variable(this.session, this.threadId, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type)) : []; - }, (e: Error) => [new Variable(this.session, this, 0, e.message, e.message, '', 0, 0, { kind: 'virtual' }, undefined, false)]); + }, (e: Error) => [new Variable(this.session, this.threadId, this, 0, e.message, e.message, '', 0, 0, { kind: 'virtual' }, undefined, false)]); } // The adapter explicitly sents the children count of an expression only if there are lots of children which should be chunked. @@ -171,7 +172,7 @@ export class Expression extends ExpressionContainer implements IExpression { public available: boolean; constructor(public name: string, id = generateUuid()) { - super(undefined, 0, id); + super(undefined, undefined, 0, id); this.available = false; // name is not set if the expression is just being added // in that case do not set default value to prevent flashing #14499 @@ -196,6 +197,7 @@ export class Variable extends ExpressionContainer implements IExpression { constructor( session: IDebugSession | undefined, + threadId: number | undefined, public parent: IExpressionContainer, reference: number | undefined, public name: string, @@ -208,7 +210,7 @@ export class Variable extends ExpressionContainer implements IExpression { public available = true, startOfVariables = 0 ) { - super(session, reference, `variable:${parent.getId()}:${name}`, namedVariables, indexedVariables, startOfVariables); + super(session, threadId, reference, `variable:${parent.getId()}:${name}`, namedVariables, indexedVariables, startOfVariables); this.value = value || ''; } @@ -248,7 +250,7 @@ export class Scope extends ExpressionContainer implements IScope { indexedVariables?: number, public range?: IRange ) { - super(stackFrame.thread.session, reference, `scope:${name}:${index}`, namedVariables, indexedVariables); + super(stackFrame.thread.session, stackFrame.thread.threadId, reference, `scope:${name}:${index}`, namedVariables, indexedVariables); } toString(): string { @@ -276,7 +278,7 @@ export class StackFrame implements IStackFrame { getScopes(): Promise { if (!this.scopes) { - this.scopes = this.thread.session.scopes(this.frameId).then(response => { + this.scopes = this.thread.session.scopes(this.frameId, this.thread.threadId).then(response => { return response && response.body && response.body.scopes ? response.body.scopes.map((rs, index) => new Scope(this, index, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables, rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined)) : []; diff --git a/src/vs/workbench/contrib/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts index 4d60ecea55a51..7808f9e6a0d78 100644 --- a/src/vs/workbench/contrib/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -97,7 +97,7 @@ export class ReplEvaluationInput implements IReplElement { export class ReplEvaluationResult extends ExpressionContainer implements IReplElement { constructor() { - super(undefined, 0, generateUuid()); + super(undefined, undefined, 0, generateUuid()); } toString(): string { diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index c13409c0603ec..84ec75a9c842c 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -127,8 +127,8 @@ export class SocketDebugAdapter extends StreamDebugAdapter { }); } - stopSession(): Promise { - + async stopSession(): Promise { + await this.cancelPendingRequests(); if (this.socket) { this.socket.end(); this.socket = undefined; @@ -249,7 +249,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { } } - stopSession(): Promise { + async stopSession(): Promise { if (!this.serverProcess) { return Promise.resolve(undefined); @@ -258,6 +258,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { // when killing a process in windows its child // processes are *not* killed but become root // processes. Therefore we use TASKKILL.EXE + await this.cancelPendingRequests(); if (platform.isWindows) { return new Promise((c, e) => { const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess!.pid}`, function (err, stdout, stderr) { diff --git a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts index dd89032131988..48e2c89f0bc2d 100644 --- a/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts @@ -76,7 +76,7 @@ suite('Debug - Base Debug View', () => { const stackFrame = new StackFrame(thread, 1, null!, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: undefined!, endColumn: undefined! }, 0); const scope = new Scope(stackFrame, 1, 'local', 1, false, 10, 10); - let variable = new Variable(session, scope, 2, 'foo', 'bar.foo', undefined!, 0, 0, {}, 'string'); + let variable = new Variable(session, 1, scope, 2, 'foo', 'bar.foo', undefined!, 0, 0, {}, 'string'); let expression = $('.'); let name = $('.'); let value = $('.'); @@ -104,7 +104,7 @@ suite('Debug - Base Debug View', () => { assert.ok(value.querySelector('a')); assert.equal(value.querySelector('a')!.textContent, variable.value); - variable = new Variable(session, scope, 2, 'console', 'console', '5', 0, 0, { kind: 'virtual' }); + variable = new Variable(session, 1, scope, 2, 'console', 'console', '5', 0, 0, { kind: 'virtual' }); expression = $('.'); name = $('.'); value = $('.'); diff --git a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts index f70b8aee72912..0abec14fb2100 100644 --- a/src/vs/workbench/contrib/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -264,7 +264,7 @@ export class MockSession implements IDebugSession { scopes(frameId: number): Promise { throw new Error('Method not implemented.'); } - variables(variablesReference: number, filter: 'indexed' | 'named', start: number, count: number): Promise { + variables(variablesReference: number, threadId: number | undefined, filter: 'indexed' | 'named', start: number, count: number): Promise { throw new Error('Method not implemented.'); } evaluate(expression: string, frameId: number, context?: string): Promise {