/// <summary> /// Receive a message from the language server. /// </summary> /// <returns> /// A <see cref="ServerMessage"/> representing the message, /// </returns> async Task <ServerMessage> ReceiveMessage() { Log.LogDebug("Reading response headers..."); byte[] headerBuffer = new byte[HeaderBufferSize]; int bytesRead = await _input.ReadAsync(headerBuffer, 0, MinimumHeaderLength, _cancellation); Log.LogDebug("Read {ByteCount} bytes from input stream.", bytesRead); if (bytesRead == 0) { return(null); // Stream closed. } const byte CR = (byte)'\r'; const byte LF = (byte)'\n'; while (bytesRead < MinimumHeaderLength || headerBuffer[bytesRead - 4] != CR || headerBuffer[bytesRead - 3] != LF || headerBuffer[bytesRead - 2] != CR || headerBuffer[bytesRead - 1] != LF) { Log.LogDebug("Reading additional data from input stream..."); // Read single bytes until we've got a valid end-of-header sequence. var additionalBytesRead = await _input.ReadAsync(headerBuffer, bytesRead, 1, _cancellation); if (additionalBytesRead == 0) { return(null); // no more _input, mitigates endless loop here. } Log.LogDebug("Read {ByteCount} bytes of additional data from input stream.", additionalBytesRead); bytesRead += additionalBytesRead; } string headers = HeaderEncoding.GetString(headerBuffer, 0, bytesRead); Log.LogDebug("Got raw headers: {Headers}", headers); if (String.IsNullOrWhiteSpace(headers)) { return(null); // Stream closed. } Log.LogDebug("Read response headers {Headers}.", headers); Dictionary <string, string> parsedHeaders = ParseHeaders(headers); string contentLengthHeader; if (!parsedHeaders.TryGetValue("Content-Length", out contentLengthHeader)) { Log.LogDebug("Invalid request headers (missing 'Content-Length' header)."); return(null); } int contentLength = Int32.Parse(contentLengthHeader); Log.LogDebug("Reading response body ({ExpectedByteCount} bytes expected).", contentLength); var requestBuffer = new byte[contentLength]; var received = 0; while (received < contentLength) { Log.LogDebug("Reading segment of incoming request body ({ReceivedByteCount} of {TotalByteCount} bytes so far)...", received, contentLength); var payloadBytesRead = await _input.ReadAsync(requestBuffer, received, requestBuffer.Length - received, _cancellation); if (payloadBytesRead == 0) { Log.LogWarning("Bailing out of reading payload (no_more_input after {ByteCount} bytes)...", received); return(null); } received += payloadBytesRead; Log.LogDebug("Read segment of incoming request body ({ReceivedByteCount} of {TotalByteCount} bytes so far).", received, contentLength); } Log.LogDebug("Received entire payload ({ReceivedByteCount} bytes).", received); string responseBody = PayloadEncoding.GetString(requestBuffer); ServerMessage message = JsonConvert.DeserializeObject <ServerMessage>(responseBody); Log.LogDebug("Read response body {ResponseBody}.", responseBody); return(message); }
/// <summary> /// Dispatch a request. /// </summary> /// <param name="requestMessage"> /// The request message. /// </param> private void DispatchRequest(ServerMessage requestMessage) { if (requestMessage == null) { throw new ArgumentNullException(nameof(requestMessage)); } string requestId = requestMessage.Id.ToString(); Log.LogDebug("Dispatching incoming {RequestMethod} request {RequestId}...", requestMessage.Method, requestId); CancellationTokenSource requestCancellation = CancellationTokenSource.CreateLinkedTokenSource(_cancellation); _requestCancellations.TryAdd(requestId, requestCancellation); Task <object> handlerTask = _dispatcher.TryHandleRequest(requestMessage.Method, requestMessage.Params, requestCancellation.Token); if (handlerTask == null) { Log.LogWarning("Unable to dispatch incoming {RequestMethod} request {RequestId} (no handler registered).", requestMessage.Method, requestId); _outgoing.TryAdd( new JsonRpcMessages.MethodNotFound(requestMessage.Id, requestMessage.Method) ); return; } #pragma warning disable CS4014 // Continuation does the work we need; no need to await it as this would tie up the dispatch loop. handlerTask.ContinueWith(_ => { if (handlerTask.IsCanceled) { Log.LogDebug("{RequestMethod} request {RequestId} canceled.", requestMessage.Method, requestId); } else if (handlerTask.IsFaulted) { Exception handlerError = handlerTask.Exception.Flatten().InnerExceptions[0]; Log.LogError(handlerError, "{RequestMethod} request {RequestId} failed (unexpected error raised by handler).", requestMessage.Method, requestId); _outgoing.TryAdd(new RpcError(requestId, new JsonRpcMessages.ErrorMessage( code: 500, message: "Error processing request: " + handlerError.Message, data: handlerError.ToString() ) )); } else if (handlerTask.IsCompleted) { Log.LogDebug("{RequestMethod} request {RequestId} complete (Result = {@Result}).", requestMessage.Method, requestId, handlerTask.Result); _outgoing.TryAdd(new ClientMessage { Id = requestMessage.Id, Method = requestMessage.Method, Result = handlerTask.Result != null ? JObject.FromObject(handlerTask.Result) : null }); } _requestCancellations.TryRemove(requestId, out CancellationTokenSource cancellation); cancellation.Dispose(); }); #pragma warning restore CS4014 // Continuation does the work we need; no need to await it as this would tie up the dispatch loop. Log.LogDebug("Dispatched incoming {RequestMethod} request {RequestId}.", requestMessage.Method, requestMessage.Id); }
/// <summary> /// The connection's message-receive loop. /// </summary> /// <returns> /// A <see cref="Task"/> representing the loop's activity. /// </returns> async Task ReceiveLoop() { await Task.Yield(); Log.LogInformation("Receive loop started."); try { while (!_cancellation.IsCancellationRequested && !_incoming.IsAddingCompleted) { ServerMessage message = await ReceiveMessage(); if (message == null) { continue; } _cancellation.ThrowIfCancellationRequested(); try { if (message.Id != null) { // Request or response. if (message.Params != null) { // Request. Log.LogDebug("Received {RequestMethod} request {RequestId} from language server: {RequestParameters}", message.Method, message.Id, message.Params?.ToString(Formatting.None) ); // Publish. if (!_incoming.IsAddingCompleted) { _incoming.TryAdd(message); } } else { // Response. string requestId = message.Id.ToString(); TaskCompletionSource <ServerMessage> completion; if (_responseCompletions.TryGetValue(requestId, out completion)) { if (message.Error != null) { Log.LogDebug("Received error response {RequestId} from language server: {@ErrorMessage}", requestId, message.Error ); Log.LogDebug("Faulting request {RequestId}.", requestId); completion.TrySetException( CreateLspException(message) ); } else { Log.LogDebug("Received response {RequestId} from language server: {ResponseResult}", requestId, message.Result?.ToString(Formatting.None) ); Log.LogDebug("Completing request {RequestId}.", requestId); completion.TrySetResult(message); } } else { Log.LogDebug("Received unexpected response {RequestId} from language server: {ResponseResult}", requestId, message.Result?.ToString(Formatting.None) ); } } } else { // Notification. Log.LogDebug("Received {NotificationMethod} notification from language server: {NotificationParameters}", message.Method, message.Params?.ToString(Formatting.None) ); // Publish. if (!_incoming.IsAddingCompleted) { _incoming.TryAdd(message); } } } catch (Exception dispatchError) { Log.LogError(dispatchError, "Unexpected error processing incoming message {@Message}.", message); } } } catch (OperationCanceledException operationCanceled) { // Like tears in rain if (operationCanceled.CancellationToken != _cancellation) { throw; // time to die } } finally { Log.LogInformation("Receive loop terminated."); } }