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
Changes from 2 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
64 changes: 59 additions & 5 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,11 @@ import {
RequestType0,
PartialResultParams,
ProtocolRequestType,
MessageType,
SocketMessageWriter,
SocketMessageReader,
MessageTransports,
RAL
} from 'vscode-languageclient/node';
import { PlatformInformation } from '../shared/platform';
import { readConfigurations } from './configurationMiddleware';
Expand Down Expand Up @@ -53,14 +59,25 @@ import { registerOnAutoInsert } from './onAutoInsert';
let _channel: vscode.OutputChannel;
let _traceChannel: vscode.OutputChannel;

// Flag indicating if C# Devkit was installed the last time we activated.
// Used to determine if we need to restart the server on extension changes.
let _wasActivatedWithCSharpDevkit: boolean | undefined;
interface TransmittedInformation {
Key: string;
Value: string;
}

export class RoslynLanguageServer {
// These are notifications we will get from the LSP server and will forward to the Razor extension.
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 = /{"Key":"NamedPipeInformation","Value":"[^"]+"}/;

/**
* The timeout for stopping the language server (in ms).
Expand Down Expand Up @@ -439,7 +456,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 +555,40 @@ export class RoslynLanguageServer {
childProcess = cp.spawn(serverPath, args, cpOptions);
}

return childProcess;
// 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<TransmittedInformation>((resolve) => {
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: TransmittedInformation = JSON.parse(jsonLines[0]);
beccamc marked this conversation as resolved.
Show resolved Hide resolved
_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.
_channel.appendLine('waiting for named pipe information from server...');
const pipeConnectionInfo = await namedPipeConnectionPromise;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably add a timeout here and show an error toast if it times out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's a good idea. Should probably do it for both receiving the name and making the named pipe connection

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to throw an error, which will log. The "couldn't create connection" toast will show.
image


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

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

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

return sessionId;
}

export function isString(value: any): value is string {
return typeof value === 'string' || value instanceof String;
}
Loading