Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Emitter, Event } from '../../../../base/common/event.js';
7
import { IDebugAdapter } from './debug.js';
8
import { timeout } from '../../../../base/common/async.js';
9
import { localize } from '../../../../nls.js';
10
11
/**
12
* Abstract implementation of the low level API for a debug adapter.
13
* Missing is how this API communicates with the debug adapter.
14
*/
15
export abstract class AbstractDebugAdapter implements IDebugAdapter {
16
private sequence: number;
17
private pendingRequests = new Map<number, (e: DebugProtocol.Response) => void>();
18
private requestCallback: ((request: DebugProtocol.Request) => void) | undefined;
19
private eventCallback: ((request: DebugProtocol.Event) => void) | undefined;
20
private messageCallback: ((message: DebugProtocol.ProtocolMessage) => void) | undefined;
21
private queue: DebugProtocol.ProtocolMessage[] = [];
22
protected readonly _onError = new Emitter<Error>();
23
protected readonly _onExit = new Emitter<number | null>();
24
25
constructor() {
26
this.sequence = 1;
27
}
28
29
abstract startSession(): Promise<void>;
30
31
abstract stopSession(): Promise<void>;
32
33
abstract sendMessage(message: DebugProtocol.ProtocolMessage): void;
34
35
get onError(): Event<Error> {
36
return this._onError.event;
37
}
38
39
get onExit(): Event<number | null> {
40
return this._onExit.event;
41
}
42
43
onMessage(callback: (message: DebugProtocol.ProtocolMessage) => void): void {
44
if (this.messageCallback) {
45
this._onError.fire(new Error(`attempt to set more than one 'Message' callback`));
46
}
47
this.messageCallback = callback;
48
}
49
50
onEvent(callback: (event: DebugProtocol.Event) => void): void {
51
if (this.eventCallback) {
52
this._onError.fire(new Error(`attempt to set more than one 'Event' callback`));
53
}
54
this.eventCallback = callback;
55
}
56
57
onRequest(callback: (request: DebugProtocol.Request) => void): void {
58
if (this.requestCallback) {
59
this._onError.fire(new Error(`attempt to set more than one 'Request' callback`));
60
}
61
this.requestCallback = callback;
62
}
63
64
sendResponse(response: DebugProtocol.Response): void {
65
if (response.seq > 0) {
66
this._onError.fire(new Error(`attempt to send more than one response for command ${response.command}`));
67
} else {
68
this.internalSend('response', response);
69
}
70
}
71
72
sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void, timeout?: number): number {
73
const request: any = {
74
command: command
75
};
76
if (args && Object.keys(args).length > 0) {
77
request.arguments = args;
78
}
79
this.internalSend('request', request);
80
if (typeof timeout === 'number') {
81
const timer = setTimeout(() => {
82
clearTimeout(timer);
83
const clb = this.pendingRequests.get(request.seq);
84
if (clb) {
85
this.pendingRequests.delete(request.seq);
86
const err: DebugProtocol.Response = {
87
type: 'response',
88
seq: 0,
89
request_seq: request.seq,
90
success: false,
91
command,
92
message: localize('timeout', "Timeout after {0} ms for '{1}'", timeout, command)
93
};
94
clb(err);
95
}
96
}, timeout);
97
}
98
if (clb) {
99
// store callback for this request
100
this.pendingRequests.set(request.seq, clb);
101
}
102
103
return request.seq;
104
}
105
106
acceptMessage(message: DebugProtocol.ProtocolMessage): void {
107
if (this.messageCallback) {
108
this.messageCallback(message);
109
} else {
110
this.queue.push(message);
111
if (this.queue.length === 1) {
112
// first item = need to start processing loop
113
this.processQueue();
114
}
115
}
116
}
117
118
/**
119
* Returns whether we should insert a timeout between processing messageA
120
* and messageB. Artificially queueing protocol messages guarantees that any
121
* microtasks for previous message finish before next message is processed.
122
* This is essential ordering when using promises anywhere along the call path.
123
*
124
* For example, take the following, where `chooseAndSendGreeting` returns
125
* a person name and then emits a greeting event:
126
*
127
* ```
128
* let person: string;
129
* adapter.onGreeting(() => console.log('hello', person));
130
* person = await adapter.chooseAndSendGreeting();
131
* ```
132
*
133
* Because the event is dispatched synchronously, it may fire before person
134
* is assigned if they're processed in the same task. Inserting a task
135
* boundary avoids this issue.
136
*/
137
protected needsTaskBoundaryBetween(messageA: DebugProtocol.ProtocolMessage, messageB: DebugProtocol.ProtocolMessage) {
138
return messageA.type !== 'event' || messageB.type !== 'event';
139
}
140
141
/**
142
* Reads and dispatches items from the queue until it is empty.
143
*/
144
private async processQueue() {
145
let message: DebugProtocol.ProtocolMessage | undefined;
146
while (this.queue.length) {
147
if (!message || this.needsTaskBoundaryBetween(this.queue[0], message)) {
148
await timeout(0);
149
}
150
151
message = this.queue.shift();
152
if (!message) {
153
return; // may have been disposed of
154
}
155
156
switch (message.type) {
157
case 'event':
158
this.eventCallback?.(<DebugProtocol.Event>message);
159
break;
160
case 'request':
161
this.requestCallback?.(<DebugProtocol.Request>message);
162
break;
163
case 'response': {
164
const response = <DebugProtocol.Response>message;
165
const clb = this.pendingRequests.get(response.request_seq);
166
if (clb) {
167
this.pendingRequests.delete(response.request_seq);
168
clb(response);
169
}
170
break;
171
}
172
}
173
}
174
}
175
176
private internalSend(typ: 'request' | 'response' | 'event', message: DebugProtocol.ProtocolMessage): void {
177
message.type = typ;
178
message.seq = this.sequence++;
179
this.sendMessage(message);
180
}
181
182
protected async cancelPendingRequests(): Promise<void> {
183
if (this.pendingRequests.size === 0) {
184
return Promise.resolve();
185
}
186
187
const pending = new Map<number, (e: DebugProtocol.Response) => void>();
188
this.pendingRequests.forEach((value, key) => pending.set(key, value));
189
await timeout(500);
190
pending.forEach((callback, request_seq) => {
191
const err: DebugProtocol.Response = {
192
type: 'response',
193
seq: 0,
194
request_seq,
195
success: false,
196
command: 'canceled',
197
message: 'canceled'
198
};
199
callback(err);
200
this.pendingRequests.delete(request_seq);
201
});
202
}
203
204
getPendingRequestIds(): number[] {
205
return Array.from(this.pendingRequests.keys());
206
}
207
208
dispose(): void {
209
this.queue = [];
210
}
211
}
212
213