提交 adc58ddb 编写于 作者: D Dmitry Gozman

Process debug adapter messages in separate tasks

上级 4e293af1
......@@ -685,6 +685,11 @@ export class DebugSession implements IDebugSession {
}) : Promise.resolve(undefined);
}
initializeForTest(raw: RawDebugSession): void {
this.raw = raw;
this.registerListeners();
}
//---- private
private registerListeners(): void {
......
......@@ -20,6 +20,7 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter {
private messageCallback: ((message: DebugProtocol.ProtocolMessage) => void) | undefined;
protected readonly _onError: Emitter<Error>;
protected readonly _onExit: Emitter<number | null>;
private queue: DebugProtocol.ProtocolMessage[] = [];
constructor() {
this.sequence = 1;
......@@ -110,29 +111,43 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter {
this.messageCallback(message);
}
else {
switch (message.type) {
case 'event':
if (this.eventCallback) {
this.eventCallback(<DebugProtocol.Event>message);
}
break;
case 'request':
if (this.requestCallback) {
this.requestCallback(<DebugProtocol.Request>message);
}
break;
case 'response':
const response = <DebugProtocol.Response>message;
const clb = this.pendingRequests.get(response.request_seq);
if (clb) {
this.pendingRequests.delete(response.request_seq);
clb(response);
}
break;
// Artificially queueing protocol messages guarantees that any microtasks for
// previous message finish before next message is processed. This is essential
// to guarantee ordering when using promises anywhere along the call path.
this.queue.push(message);
if (this.queue.length === 1) {
setTimeout(() => this.processQueue(), 0);
}
}
}
private processQueue(): void {
const message = this.queue!.shift()!;
switch (message.type) {
case 'event':
if (this.eventCallback) {
this.eventCallback(<DebugProtocol.Event>message);
}
break;
case 'request':
if (this.requestCallback) {
this.requestCallback(<DebugProtocol.Request>message);
}
break;
case 'response':
const response = <DebugProtocol.Response>message;
const clb = this.pendingRequests.get(response.request_seq);
if (clb) {
this.pendingRequests.delete(response.request_seq);
clb(response);
}
break;
}
if (this.queue!.length) {
setTimeout(() => this.processQueue(), 0);
}
}
private internalSend(typ: 'request' | 'response' | 'event', message: DebugProtocol.ProtocolMessage): void {
message.type = typ;
message.seq = this.sequence++;
......
......@@ -8,12 +8,14 @@ import { URI as uri } from 'vs/base/common/uri';
import severity from 'vs/base/common/severity';
import { DebugModel, Expression, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import * as sinon from 'sinon';
import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { MockRawSession, MockDebugAdapter } from 'vs/workbench/contrib/debug/test/common/mockDebug';
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession';
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel } from 'vs/workbench/contrib/debug/common/replModel';
import { SimpleReplElement, RawObjectReplElement, ReplEvaluationInput, ReplModel, ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel';
import { IBreakpointUpdateData, IDebugSessionOptions } from 'vs/workbench/contrib/debug/common/debug';
import { NullOpenerService } from 'vs/platform/opener/common/opener';
import { RawDebugSession } from 'vs/workbench/contrib/debug/browser/rawDebugSession';
import { timeout } from 'vs/base/common/async';
function createMockSession(model: DebugModel, name = 'mockSession', options?: IDebugSessionOptions): DebugSession {
return new DebugSession({ resolved: { name, type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, options, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, NullOpenerService);
......@@ -540,4 +542,26 @@ suite('Debug - Model', () => {
assert.equal(grandChild.getReplElements().length, 2);
assert.equal(child3.getReplElements().length, 1);
});
test('repl ordering', async () => {
const session = createMockSession(model);
model.addSession(session);
const adapter = new MockDebugAdapter();
const raw = new RawDebugSession(adapter, undefined!, undefined!, undefined!, undefined!, undefined!);
session.initializeForTest(raw);
await session.addReplExpression(undefined, 'before.1');
assert.equal(session.getReplElements().length, 3);
assert.equal((<ReplEvaluationInput>session.getReplElements()[0]).value, 'before.1');
assert.equal((<SimpleReplElement>session.getReplElements()[1]).value, 'before.1');
assert.equal((<ReplEvaluationResult>session.getReplElements()[2]).value, '=before.1');
await session.addReplExpression(undefined, 'after.2');
await timeout(0);
assert.equal(session.getReplElements().length, 6);
assert.equal((<ReplEvaluationInput>session.getReplElements()[3]).value, 'after.2');
assert.equal((<ReplEvaluationResult>session.getReplElements()[4]).value, '=after.2');
assert.equal((<SimpleReplElement>session.getReplElements()[5]).value, 'after.2');
});
});
......@@ -11,6 +11,7 @@ import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IS
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
import { CompletionItem } from 'vs/editor/common/modes';
import Severity from 'vs/base/common/severity';
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
export class MockDebugService implements IDebugService {
......@@ -463,3 +464,67 @@ export class MockRawSession {
public readonly onDidStop: Event<DebugProtocol.StoppedEvent> = null!;
}
export class MockDebugAdapter extends AbstractDebugAdapter {
private seq = 0;
startSession(): Promise<void> {
return Promise.resolve();
}
stopSession(): Promise<void> {
return Promise.resolve();
}
sendMessage(message: DebugProtocol.ProtocolMessage): void {
setTimeout(() => {
if (message.type === 'request') {
const request = message as DebugProtocol.Request;
switch (request.command) {
case 'evaluate':
this.evaluate(request, request.arguments);
return;
}
this.sendResponseBody(request, {});
return;
}
}, 0);
}
sendResponseBody(request: DebugProtocol.Request, body: any) {
const response: DebugProtocol.Response = {
seq: ++this.seq,
type: 'response',
request_seq: request.seq,
command: request.command,
success: true,
body
};
this.acceptMessage(response);
}
sendEventBody(event: string, body: any) {
const response: DebugProtocol.Event = {
seq: ++this.seq,
type: 'event',
event,
body
};
this.acceptMessage(response);
}
evaluate(request: DebugProtocol.Request, args: DebugProtocol.EvaluateArguments) {
if (args.expression.indexOf('before.') === 0) {
this.sendEventBody('output', { output: args.expression });
}
this.sendResponseBody(request, {
result: '=' + args.expression,
variablesReference: 0
});
if (args.expression.indexOf('after.') === 0) {
this.sendEventBody('output', { output: args.expression });
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册