Skip to content

Commit b6ff910

Browse files
authored
Merge pull request microsoft#103210 from TylerLeonhardt/add-debug-adapter-named-pipe-server
Add DebugAdapterNamedPipeServer
2 parents 32c0f6b + 36969b3 commit b6ff910

8 files changed

Lines changed: 166 additions & 15 deletions

File tree

src/vs/vscode.d.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10899,6 +10899,21 @@ declare module 'vscode' {
1089910899
constructor(port: number, host?: string);
1090010900
}
1090110901

10902+
/**
10903+
* Represents a debug adapter running as a Named Pipe (on Windows)/UNIX Domain Socket (on non-Windows) based server.
10904+
*/
10905+
export class DebugAdapterNamedPipeServer {
10906+
/**
10907+
* The path to the NamedPipe/UNIX Domain Socket.
10908+
*/
10909+
readonly path: string;
10910+
10911+
/**
10912+
* Create a description for a debug adapter running as a socket based server.
10913+
*/
10914+
constructor(path: string);
10915+
}
10916+
1090210917
/**
1090310918
* A debug adapter that implements the Debug Adapter Protocol can be registered with VS Code if it implements the DebugAdapter interface.
1090410919
*/
@@ -10937,7 +10952,7 @@ declare module 'vscode' {
1093710952
constructor(implementation: DebugAdapter);
1093810953
}
1093910954

10940-
export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterInlineImplementation;
10955+
export type DebugAdapterDescriptor = DebugAdapterExecutable | DebugAdapterServer | DebugAdapterNamedPipeServer | DebugAdapterInlineImplementation;
1094110956

1094210957
export interface DebugAdapterDescriptorFactory {
1094310958
/**

src/vs/workbench/api/common/extHost.api.impl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
10191019
ConfigurationTarget: extHostTypes.ConfigurationTarget,
10201020
DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable,
10211021
DebugAdapterServer: extHostTypes.DebugAdapterServer,
1022+
DebugAdapterNamedPipeServer: extHostTypes.DebugAdapterNamedPipeServer,
10221023
DebugAdapterInlineImplementation: extHostTypes.DebugAdapterInlineImplementation,
10231024
DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior,
10241025
Diagnostic: extHostTypes.Diagnostic,

src/vs/workbench/api/common/extHostDebugService.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import {
1111
MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, DebugSessionUUID,
1212
IBreakpointsDeltaDto, ISourceMultiBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto
1313
} from 'vs/workbench/api/common/extHost.protocol';
14-
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode, DebugAdapterInlineImplementation } from 'vs/workbench/api/common/extHostTypes';
14+
import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable, DataBreakpoint, DebugConsoleMode, DebugAdapterInlineImplementation, DebugAdapterNamedPipeServer } from 'vs/workbench/api/common/extHostTypes';
1515
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
1616
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
1717
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
1818
import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
19-
import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor, IDebugAdapterImpl } from 'vs/workbench/contrib/debug/common/debug';
19+
import { IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor, IDebugAdapterImpl, IDebugAdapterNamedPipeServer } from 'vs/workbench/contrib/debug/common/debug';
2020
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
2121
import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver';
2222
import { ExtHostConfigProvider, IExtHostConfiguration } from '../common/extHostConfiguration';
@@ -737,6 +737,11 @@ export abstract class ExtHostDebugServiceBase implements IExtHostDebugService, E
737737
port: x.port,
738738
host: x.host
739739
};
740+
} else if (x instanceof DebugAdapterNamedPipeServer) {
741+
return <IDebugAdapterNamedPipeServer>{
742+
type: 'pipeServer',
743+
path: x.path
744+
};
740745
} else if (x instanceof DebugAdapterInlineImplementation) {
741746
return <IDebugAdapterImpl>{
742747
type: 'implementation',

src/vs/workbench/api/common/extHostTypes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,6 +2294,12 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer {
22942294
}
22952295
}
22962296

2297+
@es5ClassCompat
2298+
export class DebugAdapterNamedPipeServer implements vscode.DebugAdapterNamedPipeServer {
2299+
constructor(public readonly path: string) {
2300+
}
2301+
}
2302+
22972303
@es5ClassCompat
22982304
export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInlineImplementation {
22992305
readonly implementation: vscode.DebugAdapter;

src/vs/workbench/api/node/extHostDebugService.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
77
import type * as vscode from 'vscode';
88
import * as env from 'vs/base/common/platform';
99
import { DebugAdapterExecutable } from 'vs/workbench/api/common/extHostTypes';
10-
import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
10+
import { ExecutableDebugAdapter, SocketDebugAdapter, NamedPipeDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
1111
import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/common/abstractDebugAdapter';
1212
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
1313
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
@@ -49,6 +49,8 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
4949
switch (adapter.type) {
5050
case 'server':
5151
return new SocketDebugAdapter(adapter);
52+
case 'pipeServer':
53+
return new NamedPipeDebugAdapter(adapter);
5254
case 'executable':
5355
return new ExecutableDebugAdapter(adapter, session.type);
5456
}

src/vs/workbench/contrib/debug/common/debug.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,11 @@ export interface IDebugAdapterServer {
573573
readonly host?: string;
574574
}
575575

576+
export interface IDebugAdapterNamedPipeServer {
577+
readonly type: 'pipeServer';
578+
readonly path: string;
579+
}
580+
576581
export interface IDebugAdapterInlineImpl extends IDisposable {
577582
readonly onDidSendMessage: Event<DebugProtocol.Message>;
578583
handleMessage(message: DebugProtocol.Message): void;
@@ -583,7 +588,7 @@ export interface IDebugAdapterImpl {
583588
readonly implementation: IDebugAdapterInlineImpl;
584589
}
585590

586-
export type IAdapterDescriptor = IDebugAdapterExecutable | IDebugAdapterServer | IDebugAdapterImpl;
591+
export type IAdapterDescriptor = IDebugAdapterExecutable | IDebugAdapterServer | IDebugAdapterNamedPipeServer | IDebugAdapterImpl;
587592

588593
export interface IPlatformSpecificAdapterContribution {
589594
program?: string;

src/vs/workbench/contrib/debug/node/debugAdapter.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import * as objects from 'vs/base/common/objects';
1414
import * as platform from 'vs/base/common/platform';
1515
import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
1616
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
17-
import { IDebugAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug';
17+
import { IDebugAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IDebugAdapterServer, IDebugAdapterNamedPipeServer } from 'vs/workbench/contrib/debug/common/debug';
1818
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
1919
import { AbstractDebugAdapter } from '../common/abstractDebugAdapter';
2020

@@ -91,32 +91,30 @@ export abstract class StreamDebugAdapter extends AbstractDebugAdapter {
9191
}
9292
}
9393

94-
/**
95-
* An implementation that connects to a debug adapter via a socket.
96-
*/
97-
export class SocketDebugAdapter extends StreamDebugAdapter {
94+
export abstract class NetworkDebugAdapter extends StreamDebugAdapter {
9895

99-
private socket?: net.Socket;
96+
protected socket?: net.Socket;
10097

101-
constructor(private adapterServer: IDebugAdapterServer) {
102-
super();
103-
}
98+
protected abstract createConnection(connectionListener: () => void): net.Socket;
10499

105100
startSession(): Promise<void> {
106101
return new Promise<void>((resolve, reject) => {
107102
let connected = false;
108-
this.socket = net.createConnection(this.adapterServer.port, this.adapterServer.host || '127.0.0.1', () => {
103+
104+
this.socket = this.createConnection(() => {
109105
this.connect(this.socket!, this.socket!);
110106
resolve();
111107
connected = true;
112108
});
109+
113110
this.socket.on('close', () => {
114111
if (connected) {
115112
this._onError.fire(new Error('connection closed'));
116113
} else {
117114
reject(new Error('connection closed'));
118115
}
119116
});
117+
120118
this.socket.on('error', error => {
121119
if (connected) {
122120
this._onError.fire(error);
@@ -136,6 +134,34 @@ export class SocketDebugAdapter extends StreamDebugAdapter {
136134
}
137135
}
138136

137+
/**
138+
* An implementation that connects to a debug adapter via a socket.
139+
*/
140+
export class SocketDebugAdapter extends NetworkDebugAdapter {
141+
142+
constructor(private adapterServer: IDebugAdapterServer) {
143+
super();
144+
}
145+
146+
protected createConnection(connectionListener: () => void): net.Socket {
147+
return net.createConnection(this.adapterServer.port, this.adapterServer.host || '127.0.0.1', connectionListener);
148+
}
149+
}
150+
151+
/**
152+
* An implementation that connects to a debug adapter via a NamedPipe (on Windows)/UNIX Domain Socket (on non-Windows).
153+
*/
154+
export class NamedPipeDebugAdapter extends NetworkDebugAdapter {
155+
156+
constructor(private adapterServer: IDebugAdapterNamedPipeServer) {
157+
super();
158+
}
159+
160+
protected createConnection(connectionListener: () => void): net.Socket {
161+
return net.createConnection(this.adapterServer.path, connectionListener);
162+
}
163+
}
164+
139165
/**
140166
* An implementation that launches the debug adapter as a separate process and communicates via stdin/stdout.
141167
*/
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 * as assert from 'assert';
7+
import * as crypto from 'crypto';
8+
import * as net from 'net';
9+
import * as platform from 'vs/base/common/platform';
10+
import { tmpdir } from 'os';
11+
import { join } from 'vs/base/common/path';
12+
import { SocketDebugAdapter, NamedPipeDebugAdapter, StreamDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter';
13+
14+
function rndPort(): number {
15+
const min = 8000;
16+
const max = 9000;
17+
return Math.floor(Math.random() * (max - min) + min);
18+
}
19+
20+
function sendInitializeRequest(debugAdapter: StreamDebugAdapter): Promise<DebugProtocol.Response> {
21+
return new Promise((resolve, reject) => {
22+
debugAdapter.sendRequest('initialize', { adapterID: 'test' }, (result) => {
23+
resolve(result);
24+
});
25+
});
26+
}
27+
28+
function serverConnection(socket: net.Socket) {
29+
socket.on('data', (data: Buffer) => {
30+
const str = data.toString().split('\r\n')[2];
31+
const request = JSON.parse(str);
32+
const response: any = {
33+
seq: request.seq,
34+
request_seq: request.seq,
35+
type: 'response',
36+
command: request.command
37+
};
38+
if (request.arguments.adapterID === 'test') {
39+
response.success = true;
40+
} else {
41+
response.success = false;
42+
response.message = 'failed';
43+
}
44+
45+
const responsePayload = JSON.stringify(response);
46+
socket.write(`Content-Length: ${responsePayload.length}\r\n\r\n${responsePayload}`);
47+
});
48+
}
49+
50+
suite('Debug - StreamDebugAdapter', () => {
51+
const port = rndPort();
52+
const pipeName = crypto.randomBytes(10).toString('utf8');
53+
const pipePath = platform.isWindows ? join('\\\\.\\pipe\\', pipeName) : join(tmpdir(), pipeName);
54+
55+
const testCases: { testName: string, debugAdapter: StreamDebugAdapter, connectionDetail: string | number }[] = [
56+
{
57+
testName: 'NamedPipeDebugAdapter',
58+
debugAdapter: new NamedPipeDebugAdapter({
59+
type: 'pipeServer',
60+
path: pipePath
61+
}),
62+
connectionDetail: pipePath
63+
},
64+
{
65+
testName: 'SocketDebugAdapter',
66+
debugAdapter: new SocketDebugAdapter({
67+
type: 'server',
68+
port
69+
}),
70+
connectionDetail: port
71+
}
72+
];
73+
74+
for (const testCase of testCases) {
75+
test(`StreamDebugAdapter (${testCase.testName}) can initialize a connection`, async () => {
76+
const server = net.createServer(serverConnection).listen(testCase.connectionDetail);
77+
const debugAdapter = testCase.debugAdapter;
78+
try {
79+
await debugAdapter.startSession();
80+
const response: DebugProtocol.Response = await sendInitializeRequest(debugAdapter);
81+
assert.strictEqual(response.command, 'initialize');
82+
assert.strictEqual(response.request_seq, 1);
83+
assert.strictEqual(response.success, true, response.message);
84+
} finally {
85+
await debugAdapter.stopSession();
86+
server.close();
87+
debugAdapter.dispose();
88+
}
89+
});
90+
}
91+
});

0 commit comments

Comments
 (0)