Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update language server to use named pipe client #6351

Merged
merged 7 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
}
},
"defaults": {
"roslyn": "4.8.0-3.23458.4",
"roslyn": "4.8.0-3.23463.5",
"omniSharp": "1.39.7",
"razor": "7.0.0-preview.23456.2",
"razorOmnisharp": "7.0.0-preview.23363.1"
Expand Down Expand Up @@ -4992,4 +4992,4 @@
}
]
}
}
}
74 changes: 72 additions & 2 deletions src/lsptoolshost/roslynLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as fs from 'fs';
import * as path from 'path';
import * as cp from 'child_process';
import * as uuid from 'uuid';
import * as net from 'net';
import { registerCommands } from './commands';
import { registerDebugger } from './debugger';
import { UriConverter } from './uriConverter';
Expand All @@ -21,6 +22,10 @@ import {
RequestType0,
PartialResultParams,
ProtocolRequestType,
SocketMessageWriter,
SocketMessageReader,
MessageTransports,
RAL,
} from 'vscode-languageclient/node';
import { PlatformInformation } from '../shared/platform';
import { readConfigurations } from './configurationMiddleware';
Expand Down Expand Up @@ -49,6 +54,7 @@ import { RoslynLanguageServerEvents } from './languageServerEvents';
import { registerShowToastNotification } from './showToastNotification';
import { registerRazorCommands } from './razorCommands';
import { registerOnAutoInsert } from './onAutoInsert';
import { NamedPipeInformation } from './roslynProtocol';

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

/**
* The encoding to use when writing to and from the stream.
*/
private static readonly encoding: RAL.MessageBufferEncoding = 'utf-8';

/**
* The regular expression used to find the named pipe key in the LSP server's stdout stream.
*/
private static readonly namedPipeKeyRegex = /{"pipeName":"[^"]+"}/;

/**
* The timeout for stopping the language server (in ms).
*/
Expand Down Expand Up @@ -439,7 +455,7 @@ export class RoslynLanguageServer {
context: vscode.ExtensionContext,
telemetryReporter: TelemetryReporter,
additionalExtensionPaths: string[]
): Promise<cp.ChildProcess> {
): Promise<MessageTransports> {
const options = optionProvider.GetLatestOptions();
const serverPath = getServerPath(options, platformInfo);

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

return childProcess;
// Timeout promise used to time out the connection process if it takes too long.
const timeout = new Promise<undefined>((resolve) => {
RAL().timer.setTimeout(resolve, 30000);
});

// The server process will create the named pipe used for communcation. Wait for it to be created,
// and listen for the server to pass back the connection information via stdout.
const namedPipeConnectionPromise = new Promise<NamedPipeInformation>((resolve) => {
_channel.appendLine('waiting for named pipe information from server...');
childProcess.stdout.on('data', (data: { toString: (arg0: any) => any }) => {
const result: string = isString(data) ? data : data.toString(RoslynLanguageServer.encoding);
_channel.append('[stdout] ' + result);

// Use the regular expression to find all JSON lines
const jsonLines = result.match(RoslynLanguageServer.namedPipeKeyRegex);
if (jsonLines) {
const transmittedPipeNameInfo: NamedPipeInformation = JSON.parse(jsonLines[0]);
_channel.appendLine('received named pipe information from server');
resolve(transmittedPipeNameInfo);
}
});
});

// Wait for the server to send back the name of the pipe to connect to.
// If it takes too long it will timeout and throw an error.
const pipeConnectionInfo = await Promise.race([namedPipeConnectionPromise, timeout]);
if (pipeConnectionInfo === undefined) {
throw new Error('Timeout. Named pipe information not received from server.');
}

const socketPromise = new Promise<net.Socket>((resolve) => {
_channel.appendLine('attempting to connect client to server...');
const socket = net.createConnection(pipeConnectionInfo.pipeName, () => {
_channel.appendLine('client has connected to server');
resolve(socket);
});
});

// Wait for the client to connect to the named pipe.
// If it takes too long it will timeout and throw an error.
const socket = await Promise.race([socketPromise, timeout]);
if (socket === undefined) {
throw new Error(
'Timeout. Client cound not connect to server via named pipe: ' + pipeConnectionInfo.pipeName
);
}

return {
reader: new SocketMessageReader(socket, RoslynLanguageServer.encoding),
writer: new SocketMessageWriter(socket, RoslynLanguageServer.encoding),
};
}

private registerDynamicFileInfo() {
Expand Down Expand Up @@ -836,3 +902,7 @@ function getSessionId(): string {

return sessionId;
}

export function isString(value: any): value is string {
return typeof value === 'string' || value instanceof String;
}
4 changes: 4 additions & 0 deletions src/lsptoolshost/roslynProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ export interface BuildOnlyDiagnosticIdsResult {
ids: string[];
}

export interface NamedPipeInformation {
pipeName: string;
}

export namespace WorkspaceDebugConfigurationRequest {
export const method = 'workspace/debugConfiguration';
export const messageDirection: lsp.MessageDirection = lsp.MessageDirection.clientToServer;
Expand Down