Skip to content

Commit 1d20255

Browse files
authored
Merge pull request #6351 from beccamc/u/beccam/serverpipe
Update language server to use named pipe client
2 parents 95ec22f + 65e27bc commit 1d20255

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
}
3838
},
3939
"defaults": {
40-
"roslyn": "4.8.0-3.23458.4",
40+
"roslyn": "4.8.0-3.23463.5",
4141
"omniSharp": "1.39.7",
4242
"razor": "7.0.0-preview.23456.2",
4343
"razorOmnisharp": "7.0.0-preview.23363.1"
@@ -4993,4 +4993,4 @@
49934993
}
49944994
]
49954995
}
4996-
}
4996+
}

src/lsptoolshost/roslynLanguageServer.ts

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as fs from 'fs';
88
import * as path from 'path';
99
import * as cp from 'child_process';
1010
import * as uuid from 'uuid';
11+
import * as net from 'net';
1112
import { registerCommands } from './commands';
1213
import { registerDebugger } from './debugger';
1314
import { UriConverter } from './uriConverter';
@@ -21,6 +22,10 @@ import {
2122
RequestType0,
2223
PartialResultParams,
2324
ProtocolRequestType,
25+
SocketMessageWriter,
26+
SocketMessageReader,
27+
MessageTransports,
28+
RAL,
2429
} from 'vscode-languageclient/node';
2530
import { PlatformInformation } from '../shared/platform';
2631
import { readConfigurations } from './configurationMiddleware';
@@ -49,6 +54,7 @@ import { RoslynLanguageServerEvents } from './languageServerEvents';
4954
import { registerShowToastNotification } from './showToastNotification';
5055
import { registerRazorCommands } from './razorCommands';
5156
import { registerOnAutoInsert } from './onAutoInsert';
57+
import { NamedPipeInformation } from './roslynProtocol';
5258

5359
let _channel: vscode.OutputChannel;
5460
let _traceChannel: vscode.OutputChannel;
@@ -62,6 +68,16 @@ export class RoslynLanguageServer {
6268
private static readonly provideRazorDynamicFileInfoMethodName: string = 'razor/provideDynamicFileInfo';
6369
private static readonly removeRazorDynamicFileInfoMethodName: string = 'razor/removeDynamicFileInfo';
6470

71+
/**
72+
* The encoding to use when writing to and from the stream.
73+
*/
74+
private static readonly encoding: RAL.MessageBufferEncoding = 'utf-8';
75+
76+
/**
77+
* The regular expression used to find the named pipe key in the LSP server's stdout stream.
78+
*/
79+
private static readonly namedPipeKeyRegex = /{"pipeName":"[^"]+"}/;
80+
6581
/**
6682
* The timeout for stopping the language server (in ms).
6783
*/
@@ -439,7 +455,7 @@ export class RoslynLanguageServer {
439455
context: vscode.ExtensionContext,
440456
telemetryReporter: TelemetryReporter,
441457
additionalExtensionPaths: string[]
442-
): Promise<cp.ChildProcess> {
458+
): Promise<MessageTransports> {
443459
const options = optionProvider.GetLatestOptions();
444460
const serverPath = getServerPath(options, platformInfo);
445461

@@ -538,7 +554,57 @@ export class RoslynLanguageServer {
538554
childProcess = cp.spawn(serverPath, args, cpOptions);
539555
}
540556

541-
return childProcess;
557+
// Timeout promise used to time out the connection process if it takes too long.
558+
const timeout = new Promise<undefined>((resolve) => {
559+
RAL().timer.setTimeout(resolve, 30000);
560+
});
561+
562+
// The server process will create the named pipe used for communcation. Wait for it to be created,
563+
// and listen for the server to pass back the connection information via stdout.
564+
const namedPipeConnectionPromise = new Promise<NamedPipeInformation>((resolve) => {
565+
_channel.appendLine('waiting for named pipe information from server...');
566+
childProcess.stdout.on('data', (data: { toString: (arg0: any) => any }) => {
567+
const result: string = isString(data) ? data : data.toString(RoslynLanguageServer.encoding);
568+
_channel.append('[stdout] ' + result);
569+
570+
// Use the regular expression to find all JSON lines
571+
const jsonLines = result.match(RoslynLanguageServer.namedPipeKeyRegex);
572+
if (jsonLines) {
573+
const transmittedPipeNameInfo: NamedPipeInformation = JSON.parse(jsonLines[0]);
574+
_channel.appendLine('received named pipe information from server');
575+
resolve(transmittedPipeNameInfo);
576+
}
577+
});
578+
});
579+
580+
// Wait for the server to send back the name of the pipe to connect to.
581+
// If it takes too long it will timeout and throw an error.
582+
const pipeConnectionInfo = await Promise.race([namedPipeConnectionPromise, timeout]);
583+
if (pipeConnectionInfo === undefined) {
584+
throw new Error('Timeout. Named pipe information not received from server.');
585+
}
586+
587+
const socketPromise = new Promise<net.Socket>((resolve) => {
588+
_channel.appendLine('attempting to connect client to server...');
589+
const socket = net.createConnection(pipeConnectionInfo.pipeName, () => {
590+
_channel.appendLine('client has connected to server');
591+
resolve(socket);
592+
});
593+
});
594+
595+
// Wait for the client to connect to the named pipe.
596+
// If it takes too long it will timeout and throw an error.
597+
const socket = await Promise.race([socketPromise, timeout]);
598+
if (socket === undefined) {
599+
throw new Error(
600+
'Timeout. Client cound not connect to server via named pipe: ' + pipeConnectionInfo.pipeName
601+
);
602+
}
603+
604+
return {
605+
reader: new SocketMessageReader(socket, RoslynLanguageServer.encoding),
606+
writer: new SocketMessageWriter(socket, RoslynLanguageServer.encoding),
607+
};
542608
}
543609

544610
private registerDynamicFileInfo() {
@@ -836,3 +902,7 @@ function getSessionId(): string {
836902

837903
return sessionId;
838904
}
905+
906+
export function isString(value: any): value is string {
907+
return typeof value === 'string' || value instanceof String;
908+
}

src/lsptoolshost/roslynProtocol.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ export interface BuildOnlyDiagnosticIdsResult {
141141
ids: string[];
142142
}
143143

144+
export interface NamedPipeInformation {
145+
pipeName: string;
146+
}
147+
144148
export namespace WorkspaceDebugConfigurationRequest {
145149
export const method = 'workspace/debugConfiguration';
146150
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.clientToServer;

0 commit comments

Comments
 (0)