Path: blob/main/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Emitter, Event } from '../../../../base/common/event.js';6import { IDebugAdapter } from './debug.js';7import { timeout } from '../../../../base/common/async.js';8import { localize } from '../../../../nls.js';910/**11* Abstract implementation of the low level API for a debug adapter.12* Missing is how this API communicates with the debug adapter.13*/14export abstract class AbstractDebugAdapter implements IDebugAdapter {15private sequence: number;16private pendingRequests = new Map<number, (e: DebugProtocol.Response) => void>();17private requestCallback: ((request: DebugProtocol.Request) => void) | undefined;18private eventCallback: ((request: DebugProtocol.Event) => void) | undefined;19private messageCallback: ((message: DebugProtocol.ProtocolMessage) => void) | undefined;20private queue: DebugProtocol.ProtocolMessage[] = [];21protected readonly _onError = new Emitter<Error>();22protected readonly _onExit = new Emitter<number | null>();2324constructor() {25this.sequence = 1;26}2728abstract startSession(): Promise<void>;2930abstract stopSession(): Promise<void>;3132abstract sendMessage(message: DebugProtocol.ProtocolMessage): void;3334get onError(): Event<Error> {35return this._onError.event;36}3738get onExit(): Event<number | null> {39return this._onExit.event;40}4142onMessage(callback: (message: DebugProtocol.ProtocolMessage) => void): void {43if (this.messageCallback) {44this._onError.fire(new Error(`attempt to set more than one 'Message' callback`));45}46this.messageCallback = callback;47}4849onEvent(callback: (event: DebugProtocol.Event) => void): void {50if (this.eventCallback) {51this._onError.fire(new Error(`attempt to set more than one 'Event' callback`));52}53this.eventCallback = callback;54}5556onRequest(callback: (request: DebugProtocol.Request) => void): void {57if (this.requestCallback) {58this._onError.fire(new Error(`attempt to set more than one 'Request' callback`));59}60this.requestCallback = callback;61}6263sendResponse(response: DebugProtocol.Response): void {64if (response.seq > 0) {65this._onError.fire(new Error(`attempt to send more than one response for command ${response.command}`));66} else {67this.internalSend('response', response);68}69}7071sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void, timeout?: number): number {72const request: any = {73command: command74};75if (args && Object.keys(args).length > 0) {76request.arguments = args;77}78this.internalSend('request', request);79if (typeof timeout === 'number') {80const timer = setTimeout(() => {81clearTimeout(timer);82const clb = this.pendingRequests.get(request.seq);83if (clb) {84this.pendingRequests.delete(request.seq);85const err: DebugProtocol.Response = {86type: 'response',87seq: 0,88request_seq: request.seq,89success: false,90command,91message: localize('timeout', "Timeout after {0} ms for '{1}'", timeout, command)92};93clb(err);94}95}, timeout);96}97if (clb) {98// store callback for this request99this.pendingRequests.set(request.seq, clb);100}101102return request.seq;103}104105acceptMessage(message: DebugProtocol.ProtocolMessage): void {106if (this.messageCallback) {107this.messageCallback(message);108} else {109this.queue.push(message);110if (this.queue.length === 1) {111// first item = need to start processing loop112this.processQueue();113}114}115}116117/**118* Returns whether we should insert a timeout between processing messageA119* and messageB. Artificially queueing protocol messages guarantees that any120* microtasks for previous message finish before next message is processed.121* This is essential ordering when using promises anywhere along the call path.122*123* For example, take the following, where `chooseAndSendGreeting` returns124* a person name and then emits a greeting event:125*126* ```127* let person: string;128* adapter.onGreeting(() => console.log('hello', person));129* person = await adapter.chooseAndSendGreeting();130* ```131*132* Because the event is dispatched synchronously, it may fire before person133* is assigned if they're processed in the same task. Inserting a task134* boundary avoids this issue.135*/136protected needsTaskBoundaryBetween(messageA: DebugProtocol.ProtocolMessage, messageB: DebugProtocol.ProtocolMessage) {137return messageA.type !== 'event' || messageB.type !== 'event';138}139140/**141* Reads and dispatches items from the queue until it is empty.142*/143private async processQueue() {144let message: DebugProtocol.ProtocolMessage | undefined;145while (this.queue.length) {146if (!message || this.needsTaskBoundaryBetween(this.queue[0], message)) {147await timeout(0);148}149150message = this.queue.shift();151if (!message) {152return; // may have been disposed of153}154155switch (message.type) {156case 'event':157this.eventCallback?.(<DebugProtocol.Event>message);158break;159case 'request':160this.requestCallback?.(<DebugProtocol.Request>message);161break;162case 'response': {163const response = <DebugProtocol.Response>message;164const clb = this.pendingRequests.get(response.request_seq);165if (clb) {166this.pendingRequests.delete(response.request_seq);167clb(response);168}169break;170}171}172}173}174175private internalSend(typ: 'request' | 'response' | 'event', message: DebugProtocol.ProtocolMessage): void {176message.type = typ;177message.seq = this.sequence++;178this.sendMessage(message);179}180181protected async cancelPendingRequests(): Promise<void> {182if (this.pendingRequests.size === 0) {183return Promise.resolve();184}185186const pending = new Map<number, (e: DebugProtocol.Response) => void>();187this.pendingRequests.forEach((value, key) => pending.set(key, value));188await timeout(500);189pending.forEach((callback, request_seq) => {190const err: DebugProtocol.Response = {191type: 'response',192seq: 0,193request_seq,194success: false,195command: 'canceled',196message: 'canceled'197};198callback(err);199this.pendingRequests.delete(request_seq);200});201}202203getPendingRequestIds(): number[] {204return Array.from(this.pendingRequests.keys());205}206207dispose(): void {208this.queue = [];209}210}211212213