提交 73b7f479 编写于 作者: A Andre Weinand

debt: clean up DA error handling; fixes #57810

上级 a1ac197b
......@@ -167,7 +167,7 @@ export interface ISession extends ITreeElement, IDisposable {
/**
* Allows to register on DA events.
*/
onDidExitAdapter: Event<void>;
onDidExitAdapter: Event<Error>;
}
export interface IThread extends ITreeElement {
......
......@@ -400,6 +400,10 @@ export class DebugService implements IDebugService {
});
}
private isExtensionHostDebugging(config: IConfig) {
return equalsIgnoreCase(config.type, 'extensionhost');
}
private attachExtensionHost(session: Session, port: number): TPromise<void> {
session.configuration.request = 'attach';
......@@ -418,18 +422,25 @@ export class DebugService implements IDebugService {
const session = this.instantiationService.createInstance(Session, configuration, root, this.model);
this.allSessions.set(session.getId(), session);
// register listeners as the very first thing!
this.registerSessionListeners(session);
// since the Session is now properly registered under its ID and hooked, we can announce it
// this event doesn't go to extensions
this._onWillNewSession.fire(session);
const resolved = configuration.resolved;
resolved.__sessionId = session.getId();
const dbgr = this.configurationManager.getDebugger(resolved.type);
return session.initialize(dbgr).then(() => {
this.registerSessionListeners(session);
const raw = <RawDebugSession>session.raw;
// pass the sessionID for EH debugging
if (this.isExtensionHostDebugging(resolved)) {
resolved.__sessionId = session.getId();
}
return (resolved.request === 'attach' ? raw.attach(resolved) : raw.launch(resolved)).then(result => {
if (raw.disconnected) {
......@@ -438,6 +449,7 @@ export class DebugService implements IDebugService {
this.focusStackFrame(undefined, undefined, session);
// since the initialized response has arrived announce the new Session (including extensions)
this._onDidNewSession.fire(session);
const internalConsoleOptions = resolved.internalConsoleOptions || this.configurationService.getValue<IDebugConfiguration>('debug').internalConsoleOptions;
......@@ -460,6 +472,7 @@ export class DebugService implements IDebugService {
return this.telemetryDebugSessionStart(root, resolved.type, dbgr.extensionDescription);
}).then(() => session, (error: Error | string) => {
if (session) {
session.dispose();
}
......@@ -483,13 +496,18 @@ export class DebugService implements IDebugService {
}
return undefined;
});
}).then(undefined, err => {
}).then(undefined, error => {
if (session) {
session.dispose();
}
return TPromise.wrapError(err);
if (errors.isPromiseCanceledError(error)) {
// Do not show 'canceled' error messages to the user #7906
return TPromise.as(null);
}
return TPromise.wrapError(error);
});
}
......@@ -502,10 +520,14 @@ export class DebugService implements IDebugService {
this.onStateChange();
}));
this.toDispose.push(session.onDidExitAdapter(() => {
this.toDispose.push(session.onDidExitAdapter(err => {
if (err) {
this.notificationService.error(nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly ({0})", err.message || err.toString()));
}
// 'Run without debugging' mode VSCode must terminate the extension host. More details: #3905
if (equalsIgnoreCase(session.configuration.type, 'extensionhost') && session.state === State.Running && session.configuration.noDebug) {
if (this.isExtensionHostDebugging(session.configuration) && session.state === State.Running && session.configuration.noDebug) {
this.broadcastService.broadcast({
channel: EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL,
payload: [session.root.uri.toString()]
......@@ -553,7 +575,7 @@ export class DebugService implements IDebugService {
// Do not run preLaunch and postDebug tasks for automatic restarts
this.skipRunningTask = !!restartData;
if (equalsIgnoreCase(session.configuration.type, 'extensionHost') && session.root) {
if (this.isExtensionHostDebugging(session.configuration) && session.root) {
return this.broadcastService.broadcast({
channel: EXTENSION_RELOAD_BROADCAST_CHANNEL,
payload: [session.root.uri.toString()]
......
......@@ -41,7 +41,7 @@ export class Session implements ISession {
private readonly _onDidLoadedSource = new Emitter<LoadedSourceEvent>();
private readonly _onDidCustomEvent = new Emitter<DebugProtocol.Event>();
private readonly _onDidChangeState = new Emitter<State>();
private readonly _onDidExitAdapter = new Emitter<void>();
private readonly _onDidExitAdapter = new Emitter<Error>();
constructor(
private _configuration: { resolved: IConfig, unresolved: IConfig },
......@@ -93,149 +93,25 @@ export class Session implements ISession {
return this._onDidChangeState.event;
}
getSourceForUri(modelUri: uri): Source {
return this.sources.get(modelUri.toString());
}
getSource(raw: DebugProtocol.Source): Source {
let source = new Source(raw, this.getId());
if (this.sources.has(source.uri.toString())) {
source = this.sources.get(source.uri.toString());
source.raw = mixin(source.raw, raw);
if (source.raw && raw) {
// Always take the latest presentation hint from adapter #42139
source.raw.presentationHint = raw.presentationHint;
}
} else {
this.sources.set(source.uri.toString(), source);
}
return source;
}
getThread(threadId: number): Thread {
return this.threads.get(threadId);
}
getAllThreads(): IThread[] {
const result: IThread[] = [];
this.threads.forEach(t => result.push(t));
return result;
}
getLoadedSources(): TPromise<Source[]> {
return this._raw.loadedSources({}).then(response => {
return response.body.sources.map(src => this.getSource(src));
}, () => {
return [];
});
}
get onDidLoadedSource(): Event<LoadedSourceEvent> {
return this._onDidLoadedSource.event;
}
get onDidCustomEvent(): Event<DebugProtocol.Event> {
return this._onDidCustomEvent.event;
}
get onDidExitAdapter(): Event<void> {
get onDidExitAdapter(): Event<Error> {
return this._onDidExitAdapter.event;
}
rawUpdate(data: IRawModelUpdate): void {
if (data.thread && !this.threads.has(data.threadId)) {
// A new thread came in, initialize it.
this.threads.set(data.threadId, new Thread(this, data.thread.name, data.thread.id));
} else if (data.thread && data.thread.name) {
// Just the thread name got updated #18244
this.threads.get(data.threadId).name = data.thread.name;
}
if (data.stoppedDetails) {
// Set the availability of the threads' callstacks depending on
// whether the thread is stopped or not
if (data.stoppedDetails.allThreadsStopped) {
this.threads.forEach(thread => {
thread.stoppedDetails = thread.threadId === data.threadId ? data.stoppedDetails : { reason: undefined };
thread.stopped = true;
thread.clearCallStack();
});
} else if (this.threads.has(data.threadId)) {
// One thread is stopped, only update that thread.
const thread = this.threads.get(data.threadId);
thread.stoppedDetails = data.stoppedDetails;
thread.clearCallStack();
thread.stopped = true;
}
}
}
clearThreads(removeThreads: boolean, reference: number = undefined): void {
if (reference !== undefined && reference !== null) {
if (this.threads.has(reference)) {
const thread = this.threads.get(reference);
thread.clearCallStack();
thread.stoppedDetails = undefined;
thread.stopped = false;
if (removeThreads) {
this.threads.delete(reference);
}
}
} else {
this.threads.forEach(thread => {
thread.clearCallStack();
thread.stoppedDetails = undefined;
thread.stopped = false;
});
if (removeThreads) {
this.threads.clear();
ExpressionContainer.allValues.clear();
}
}
}
completions(frameId: number, text: string, position: Position, overwriteBefore: number): TPromise<ISuggestion[]> {
if (!this._raw.capabilities.supportsCompletionsRequest) {
return TPromise.as([]);
}
return this._raw.completions({
frameId,
text,
column: position.column,
line: position.lineNumber
}).then(response => {
const result: ISuggestion[] = [];
if (response && response.body && response.body.targets) {
response.body.targets.forEach(item => {
if (item && item.label) {
result.push({
label: item.label,
insertText: item.text || item.label,
type: item.type,
filterText: item.start && item.length && text.substr(item.start, item.length).concat(item.label),
overwriteBefore: item.length || overwriteBefore
});
}
});
}
return result;
}, () => []);
}
initialize(dbgr: Debugger): TPromise<void> {
if (this._raw) {
// If there was already a connection make sure to remove old listeners
this.dispose();
// if there was already a connection make sure to remove old listeners
this.dispose(); // TODO: do not use dispose for this!
}
return dbgr.getCustomTelemetryService().then(customTelemetryService => {
this._raw = this.instantiationService.createInstance(RawDebugSession, this._configuration.resolved.debugServer, dbgr, customTelemetryService, this.root);
this.registerListeners();
return this._raw.initialize({
......@@ -249,7 +125,7 @@ export class Session implements ISession {
supportsVariablePaging: true, // #9537
supportsRunInTerminalRequest: true, // #10574
locale: platform.locale
}).then(() => {
}).then(response => {
this.model.addSession(this);
this.state = State.Running;
this.model.setExceptionBreakpoints(this._raw.capabilities.exceptionBreakpointFilters);
......@@ -258,6 +134,7 @@ export class Session implements ISession {
}
private registerListeners(): void {
this.rawListeners.push(this._raw.onDidInitialize(() => {
aria.status(nls.localize('debuggingStarted', "Debugging started."));
const sendConfigurationDone = () => {
......@@ -415,7 +292,152 @@ export class Session implements ISession {
this._onDidCustomEvent.fire(event);
}));
this.rawListeners.push(this._raw.onDidExitAdapter(() => this._onDidExitAdapter.fire()));
this.rawListeners.push(this._raw.onDidExitAdapter(error => {
this._onDidExitAdapter.fire(error);
}));
}
dispose(): void {
dispose(this.rawListeners);
this.model.clearThreads(this.getId(), true);
this.model.removeSession(this.getId());
this.fetchThreadsScheduler = undefined;
if (this._raw && !this._raw.disconnected) {
this._raw.disconnect();
}
this._raw = undefined;
}
//---- sources
getSourceForUri(modelUri: uri): Source {
return this.sources.get(modelUri.toString());
}
getSource(raw: DebugProtocol.Source): Source {
let source = new Source(raw, this.getId());
if (this.sources.has(source.uri.toString())) {
source = this.sources.get(source.uri.toString());
source.raw = mixin(source.raw, raw);
if (source.raw && raw) {
// Always take the latest presentation hint from adapter #42139
source.raw.presentationHint = raw.presentationHint;
}
} else {
this.sources.set(source.uri.toString(), source);
}
return source;
}
getLoadedSources(): TPromise<Source[]> {
return this._raw.loadedSources({}).then(response => {
return response.body.sources.map(src => this.getSource(src));
}, () => {
return [];
});
}
get onDidLoadedSource(): Event<LoadedSourceEvent> {
return this._onDidLoadedSource.event;
}
//---- completions
completions(frameId: number, text: string, position: Position, overwriteBefore: number): TPromise<ISuggestion[]> {
if (!this._raw.capabilities.supportsCompletionsRequest) {
return TPromise.as([]);
}
return this._raw.completions({
frameId,
text,
column: position.column,
line: position.lineNumber
}).then(response => {
const result: ISuggestion[] = [];
if (response && response.body && response.body.targets) {
response.body.targets.forEach(item => {
if (item && item.label) {
result.push({
label: item.label,
insertText: item.text || item.label,
type: item.type,
filterText: item.start && item.length && text.substr(item.start, item.length).concat(item.label),
overwriteBefore: item.length || overwriteBefore
});
}
});
}
return result;
}, () => []);
}
//---- threads
getThread(threadId: number): Thread {
return this.threads.get(threadId);
}
getAllThreads(): IThread[] {
const result: IThread[] = [];
this.threads.forEach(t => result.push(t));
return result;
}
clearThreads(removeThreads: boolean, reference: number = undefined): void {
if (reference !== undefined && reference !== null) {
if (this.threads.has(reference)) {
const thread = this.threads.get(reference);
thread.clearCallStack();
thread.stoppedDetails = undefined;
thread.stopped = false;
if (removeThreads) {
this.threads.delete(reference);
}
}
} else {
this.threads.forEach(thread => {
thread.clearCallStack();
thread.stoppedDetails = undefined;
thread.stopped = false;
});
if (removeThreads) {
this.threads.clear();
ExpressionContainer.allValues.clear();
}
}
}
rawUpdate(data: IRawModelUpdate): void {
if (data.thread && !this.threads.has(data.threadId)) {
// A new thread came in, initialize it.
this.threads.set(data.threadId, new Thread(this, data.thread.name, data.thread.id));
} else if (data.thread && data.thread.name) {
// Just the thread name got updated #18244
this.threads.get(data.threadId).name = data.thread.name;
}
if (data.stoppedDetails) {
// Set the availability of the threads' callstacks depending on
// whether the thread is stopped or not
if (data.stoppedDetails.allThreadsStopped) {
this.threads.forEach(thread => {
thread.stoppedDetails = thread.threadId === data.threadId ? data.stoppedDetails : { reason: undefined };
thread.stopped = true;
thread.clearCallStack();
});
} else if (this.threads.has(data.threadId)) {
// One thread is stopped, only update that thread.
const thread = this.threads.get(data.threadId);
thread.stoppedDetails = data.stoppedDetails;
thread.clearCallStack();
thread.stopped = true;
}
}
}
private fetchThreads(stoppedDetails?: IRawStoppedDetails): TPromise<any> {
......@@ -432,15 +454,4 @@ export class Session implements ISession {
}
});
}
dispose(): void {
dispose(this.rawListeners);
this.model.clearThreads(this.getId(), true);
this.model.removeSession(this.getId());
this.fetchThreadsScheduler = undefined;
if (!this._raw.disconnected) {
this._raw.disconnect();
}
this._raw = undefined;
}
}
......@@ -13,7 +13,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Debugger } from 'vs/workbench/parts/debug/node/debugger';
import { IOutputService } from 'vs/workbench/parts/output/common/output';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { formatPII } from 'vs/workbench/parts/debug/common/debugUtils';
import { SocketDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter';
import { IRawSession, IDebugAdapter } from 'vs/workbench/parts/debug/common/debug';
......@@ -26,7 +25,7 @@ export class RawDebugSession implements IRawSession {
public disconnected: boolean;
private debugAdapter: IDebugAdapter;
private cachedInitServerP: TPromise<void>;
private cachedInitDebugAdapterP: TPromise<void>;
private startTime: number;
private terminated: boolean;
private cancellationTokens: CancellationTokenSource[];
......@@ -48,14 +47,13 @@ export class RawDebugSession implements IRawSession {
private readonly _onDidEvent: Emitter<DebugProtocol.Event>;
// DA events
private readonly _onDidExitAdapter: Emitter<void>;
private readonly _onDidExitAdapter: Emitter<Error>;
constructor(
private debugServerPort: number,
private _debugger: Debugger,
public customTelemetryService: ITelemetryService,
private root: IWorkspaceFolder,
@INotificationService private notificationService: INotificationService,
@ITelemetryService private telemetryService: ITelemetryService,
@IOutputService private outputService: IOutputService
) {
......@@ -76,7 +74,7 @@ export class RawDebugSession implements IRawSession {
this._onDidCustomEvent = new Emitter<DebugProtocol.Event>();
this._onDidEvent = new Emitter<DebugProtocol.Event>();
this._onDidExitAdapter = new Emitter<void>();
this._onDidExitAdapter = new Emitter<Error>();
}
// DAP events
......@@ -126,83 +124,81 @@ export class RawDebugSession implements IRawSession {
}
// DA event
public get onDidExitAdapter(): Event<void> {
public get onDidExitAdapter(): Event<Error> {
return this._onDidExitAdapter.event;
}
private initServer(): TPromise<void> {
private startAdapter(): TPromise<void> {
if (this.cachedInitServerP) {
return this.cachedInitServerP;
}
const startSessionP = this.startSession();
this.cachedInitServerP = startSessionP.then(() => {
this.startTime = new Date().getTime();
}, err => {
this.cachedInitServerP = null;
return TPromise.wrapError(err);
});
if (!this.cachedInitDebugAdapterP) {
return this.cachedInitServerP;
}
private startSession(): TPromise<void> {
const startSessionP = this._debugger.createDebugAdapter(this.root, this.outputService, this.debugServerPort).then(debugAdapter => {
return this._debugger.createDebugAdapter(this.root, this.outputService, this.debugServerPort).then(debugAdapter => {
this.debugAdapter = debugAdapter;
this.debugAdapter = debugAdapter;
this.debugAdapter.onError(err => this.onDebugAdapterError(err));
this.debugAdapter.onEvent(event => this.onDapEvent(event));
this.debugAdapter.onRequest(request => this.dispatchRequest(request));
this.debugAdapter.onExit(code => this.onDebugAdapterExit(code));
this.debugAdapter.onError(err => this.onDebugAdapterError(err));
this.debugAdapter.onEvent(event => this.onDapEvent(event));
this.debugAdapter.onRequest(request => this.dispatchRequest(request));
this.debugAdapter.onExit(code => this.onDebugAdapterExit());
return this.debugAdapter.startSession();
});
return this.debugAdapter.startSession();
});
}
this.cachedInitDebugAdapterP = startSessionP.then(() => {
this.startTime = new Date().getTime();
}, err => {
this.cachedInitDebugAdapterP = null;
return TPromise.wrapError(err);
});
}
public custom(request: string, args: any): TPromise<DebugProtocol.Response> {
return this.send(request, args);
return this.cachedInitDebugAdapterP;
}
private send<R extends DebugProtocol.Response>(command: string, args: any, cancelOnDisconnect = true): TPromise<R> {
return this.initServer().then(() => {
return this.startAdapter().then(() => {
const cancellationSource = new CancellationTokenSource();
const promise = this.internalSend<R>(command, args, cancellationSource.token).then(response => response, (errorResponse: DebugProtocol.ErrorResponse) => {
const error = errorResponse && errorResponse.body ? errorResponse.body.error : null;
const errorMessage = errorResponse ? errorResponse.message : '';
const telemetryMessage = error ? formatPII(error.format, true, error.variables) : errorMessage;
if (error && error.sendTelemetry) {
/* __GDPR__
"debugProtocolErrorResponse" : {
"error" : { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage });
if (this.customTelemetryService) {
/* __GDPR__TODO__
The message is sent in the name of the adapter but the adapter doesn't know about it.
However, since adapters are an open-ended set, we can not declared the events statically either.
const promise = this.internalSend<R>(command, args, cancellationSource.token).then(response => {
return response;
},
(errorResponse: DebugProtocol.ErrorResponse) => {
const error = errorResponse && errorResponse.body ? errorResponse.body.error : null;
const errorMessage = errorResponse ? errorResponse.message : '';
const telemetryMessage = error ? formatPII(error.format, true, error.variables) : errorMessage;
if (error && error.sendTelemetry) {
/* __GDPR__
"debugProtocolErrorResponse" : {
"error" : { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
}
*/
this.customTelemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage });
this.telemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage });
if (this.customTelemetryService) {
/* __GDPR__TODO__
The message is sent in the name of the adapter but the adapter doesn't know about it.
However, since adapters are an open-ended set, we can not declared the events statically either.
*/
this.customTelemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage });
}
}
}
const userMessage = error ? formatPII(error.format, false, error.variables) : errorMessage;
if (error && error.url) {
const label = error.urlLabel ? error.urlLabel : nls.localize('moreInfo', "More Info");
return TPromise.wrapError<R>(errors.create(userMessage, {
actions: [new Action('debug.moreInfo', label, null, true, () => {
window.open(error.url);
return TPromise.as(null);
})]
}));
}
const userMessage = error ? formatPII(error.format, false, error.variables) : errorMessage;
if (error && error.url) {
const label = error.urlLabel ? error.urlLabel : nls.localize('moreInfo', "More Info");
return TPromise.wrapError<R>(errors.create(userMessage, {
actions: [new Action('debug.moreInfo', label, null, true, () => {
window.open(error.url);
return TPromise.as(null);
})]
}));
}
return errors.isPromiseCanceledError(errorResponse) ? undefined : TPromise.wrapError<R>(new Error(userMessage));
});
if (errors.isPromiseCanceledError(errorResponse)) {
return TPromise.wrapError<R>(<any>errorResponse);
}
return TPromise.wrapError<R>(new Error(userMessage));
});
if (cancelOnDisconnect) {
this.cancellationTokens.push(cancellationSource);
......@@ -397,7 +393,6 @@ export class RawDebugSession implements IRawSession {
return this.send<DebugProtocol.LoadedSourcesResponse>('loadedSources', args);
}
public threads(): TPromise<DebugProtocol.ThreadsResponse> {
return this.send<DebugProtocol.ThreadsResponse>('threads', null);
}
......@@ -424,6 +419,10 @@ export class RawDebugSession implements IRawSession {
});
}
public custom(request: string, args: any): TPromise<DebugProtocol.Response> {
return this.send(request, args);
}
public getLengthInSeconds(): number {
return (new Date().getTime() - this.startTime) / 1000;
}
......@@ -503,14 +502,14 @@ export class RawDebugSession implements IRawSession {
return TPromise.as(null);
}
private stopServer(): TPromise<any> {
private stopServer(error?: Error): TPromise<any> {
if (/* this.socket !== null */ this.debugAdapter instanceof SocketDebugAdapter) {
this.debugAdapter.stopSession();
this.cachedInitServerP = null;
this.cachedInitDebugAdapterP = null;
}
this._onDidExitAdapter.fire();
this._onDidExitAdapter.fire(error);
this.disconnected = true;
if (!this.debugAdapter || this.debugAdapter instanceof SocketDebugAdapter) {
return TPromise.as(null);
......@@ -518,18 +517,18 @@ export class RawDebugSession implements IRawSession {
return this.debugAdapter.stopSession();
}
private onDebugAdapterError(err: Error): void {
this.notificationService.error(err.message || err.toString());
this.stopServer();
this.stopServer(err);
}
private onDebugAdapterExit(): void {
private onDebugAdapterExit(code: number): void {
this.debugAdapter = null;
this.cachedInitServerP = null;
if (!this.disconnected) {
this.notificationService.error(nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
this.cachedInitDebugAdapterP = null;
if (!this.disconnected && code !== 0) {
this._onDidExitAdapter.fire(new Error(`exit code: ${code}`));
} else {
// normal exit
this._onDidExitAdapter.fire();
}
this._onDidExitAdapter.fire();
}
}
......@@ -157,7 +157,7 @@ export class MockSession implements ISession {
return null;
}
get onDidExitAdapter(): Event<void> {
get onDidExitAdapter(): Event<Error> {
return null;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册