@@ -8,6 +8,7 @@ import * as fs from 'fs';
8
8
import * as path from 'path' ;
9
9
import * as cp from 'child_process' ;
10
10
import * as uuid from 'uuid' ;
11
+ import * as net from 'net' ;
11
12
import { registerCommands } from './commands' ;
12
13
import { registerDebugger } from './debugger' ;
13
14
import { UriConverter } from './uriConverter' ;
@@ -21,6 +22,10 @@ import {
21
22
RequestType0 ,
22
23
PartialResultParams ,
23
24
ProtocolRequestType ,
25
+ SocketMessageWriter ,
26
+ SocketMessageReader ,
27
+ MessageTransports ,
28
+ RAL ,
24
29
} from 'vscode-languageclient/node' ;
25
30
import { PlatformInformation } from '../shared/platform' ;
26
31
import { readConfigurations } from './configurationMiddleware' ;
@@ -49,6 +54,7 @@ import { RoslynLanguageServerEvents } from './languageServerEvents';
49
54
import { registerShowToastNotification } from './showToastNotification' ;
50
55
import { registerRazorCommands } from './razorCommands' ;
51
56
import { registerOnAutoInsert } from './onAutoInsert' ;
57
+ import { NamedPipeInformation } from './roslynProtocol' ;
52
58
53
59
let _channel : vscode . OutputChannel ;
54
60
let _traceChannel : vscode . OutputChannel ;
@@ -62,6 +68,16 @@ export class RoslynLanguageServer {
62
68
private static readonly provideRazorDynamicFileInfoMethodName : string = 'razor/provideDynamicFileInfo' ;
63
69
private static readonly removeRazorDynamicFileInfoMethodName : string = 'razor/removeDynamicFileInfo' ;
64
70
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 = / { " p i p e N a m e " : " [ ^ " ] + " } / ;
80
+
65
81
/**
66
82
* The timeout for stopping the language server (in ms).
67
83
*/
@@ -439,7 +455,7 @@ export class RoslynLanguageServer {
439
455
context : vscode . ExtensionContext ,
440
456
telemetryReporter : TelemetryReporter ,
441
457
additionalExtensionPaths : string [ ]
442
- ) : Promise < cp . ChildProcess > {
458
+ ) : Promise < MessageTransports > {
443
459
const options = optionProvider . GetLatestOptions ( ) ;
444
460
const serverPath = getServerPath ( options , platformInfo ) ;
445
461
@@ -538,7 +554,57 @@ export class RoslynLanguageServer {
538
554
childProcess = cp . spawn ( serverPath , args , cpOptions ) ;
539
555
}
540
556
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
+ } ;
542
608
}
543
609
544
610
private registerDynamicFileInfo ( ) {
@@ -836,3 +902,7 @@ function getSessionId(): string {
836
902
837
903
return sessionId ;
838
904
}
905
+
906
+ export function isString ( value : any ) : value is string {
907
+ return typeof value === 'string' || value instanceof String ;
908
+ }
0 commit comments