未验证 提交 0df8fee2 编写于 作者: D David Wengier 提交者: GitHub

Handle errors in RequestExecutionQueue (#47107)

上级 24d86037
......@@ -341,6 +341,13 @@ private protected static LanguageServerProtocol GetLanguageServer(Solution solut
return workspace.ExportProvider.GetExportedValue<LanguageServerProtocol>();
}
private protected static RequestExecutionQueue CreateRequestQueue(Solution solution)
{
var workspace = (TestWorkspace)solution.Workspace;
var solutionProvider = workspace.ExportProvider.GetExportedValue<ILspSolutionProvider>();
return new RequestExecutionQueue(solutionProvider);
}
private static string GetDocumentFilePathFromName(string documentName)
=> "C:\\" + documentName;
}
......
......@@ -11,7 +11,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens;
using Roslyn.Utilities;
using LSP = Microsoft.VisualStudio.LanguageServer.Protocol;
......@@ -20,12 +19,10 @@ namespace Microsoft.CodeAnalysis.LanguageServer
internal abstract class AbstractRequestHandlerProvider
{
private readonly ImmutableDictionary<string, Lazy<IRequestHandler, IRequestHandlerMetadata>> _requestHandlers;
private readonly RequestExecutionQueue _queue;
public AbstractRequestHandlerProvider(IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, ILspSolutionProvider solutionProvider, string? languageName = null)
public AbstractRequestHandlerProvider(IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, string? languageName = null)
{
_requestHandlers = CreateMethodToHandlerMap(requestHandlers.Where(rh => rh.Metadata.LanguageName == languageName));
_queue = new RequestExecutionQueue(solutionProvider);
}
private static ImmutableDictionary<string, Lazy<IRequestHandler, IRequestHandlerMetadata>> CreateMethodToHandlerMap(IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
......@@ -39,7 +36,7 @@ public AbstractRequestHandlerProvider(IEnumerable<Lazy<IRequestHandler, IRequest
return requestHandlerDictionary.ToImmutable();
}
public Task<ResponseType> ExecuteRequestAsync<RequestType, ResponseType>(string methodName, RequestType request, LSP.ClientCapabilities clientCapabilities,
public Task<ResponseType> ExecuteRequestAsync<RequestType, ResponseType>(RequestExecutionQueue queue, string methodName, RequestType request, LSP.ClientCapabilities clientCapabilities,
string? clientName, CancellationToken cancellationToken) where RequestType : class
{
Contract.ThrowIfNull(request);
......@@ -53,7 +50,7 @@ public AbstractRequestHandlerProvider(IEnumerable<Lazy<IRequestHandler, IRequest
var handler = (IRequestHandler<RequestType, ResponseType>?)handlerEntry.Value;
Contract.ThrowIfNull(handler, string.Format("Request handler not found for method {0}", methodName));
return _queue.ExecuteAsync(mutatesSolutionState, handler, request, clientCapabilities, clientName, cancellationToken);
return queue.ExecuteAsync(mutatesSolutionState, handler, request, clientCapabilities, clientName, cancellationToken);
}
}
}
......@@ -5,13 +5,12 @@
#nullable enable
using System;
using System.Composition;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.VisualStudio.LanguageServer.Protocol;
using Microsoft.VisualStudio.Threading;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
......@@ -38,22 +37,52 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler
/// and any consumers observing the results of the task returned from <see cref="ExecuteAsync{TRequestType, TResponseType}(bool, IRequestHandler{TRequestType, TResponseType}, TRequestType, ClientCapabilities, string?, CancellationToken)"/>
/// will see the results of the handling of the request, whenever it occurred.
/// </para>
/// <para>
/// Exceptions in the handling of non-mutating requests are sent back to callers. Exceptions in the processing of
/// the queue will close the LSP connection so that the client can reconnect. Exceptions in the handling of mutating
/// requests will also close the LSP connection, as at that point the mutated solution is in an unknown state.
/// </para>
/// <para>
/// After shutdown is called, or an error causes the closing of the connection, the queue will not accept any
/// more messages, and a new queue will need to be created.
/// </para>
/// </remarks>
internal partial class RequestExecutionQueue : IDisposable
internal partial class RequestExecutionQueue
{
private readonly AsyncQueue<QueueItem> _queue = new AsyncQueue<QueueItem>();
private readonly ILspSolutionProvider _solutionProvider;
private readonly AsyncQueue<QueueItem> _queue;
private readonly CancellationTokenSource _cancelSource;
/// <summary>
/// Raised when the execution queue has failed, or the solution state its tracking is in an unknown state
/// and so the only course of action is to shutdown the server so that the client re-connects and we can
/// start over again.
/// </summary>
/// <remarks>
/// Once this event has been fired all currently active and pending work items in the queue will be cancelled.
/// </remarks>
public event EventHandler<RequestShutdownEventArgs>? RequestServerShutdown;
public RequestExecutionQueue(ILspSolutionProvider solutionProvider)
{
_solutionProvider = solutionProvider;
_queue = new AsyncQueue<QueueItem>();
_cancelSource = new CancellationTokenSource();
// Start the queue processing
_ = ProcessQueueAsync();
}
/// <summary>
/// Shuts down the queue, stops accepting new messages, and cancels any in-progress or queued tasks. Calling
/// this multiple times won't cause any issues.
/// </summary>
public void Shutdown()
{
_cancelSource.Cancel();
DrainQueue();
}
/// <summary>
/// Queues a request to be handled by the specified handler, with mutating requests blocking subsequent requests
/// from starting until the mutation is complete.
......@@ -79,6 +108,8 @@ public RequestExecutionQueue(ILspSolutionProvider solutionProvider)
// Create a task completion source that will represent the processing of this request to the caller
var completion = new TaskCompletionSource<TResponseType>();
// Note: If the queue is not accepting any more items then TryEnqueue below will fail.
var textDocument = handler.GetTextDocumentIdentifier(request);
var item = new QueueItem(mutatesSolutionState, clientCapabilities, clientName, textDocument,
callbackAsync: async (context, cancellationToken) =>
......@@ -100,9 +131,9 @@ public RequestExecutionQueue(ILspSolutionProvider solutionProvider)
// Tell the queue that this was successful so that mutations (if any) can be applied
return true;
}
catch (OperationCanceledException)
catch (OperationCanceledException ex)
{
completion.SetCanceled();
completion.TrySetCanceled(ex.CancellationToken);
}
catch (Exception exception)
{
......@@ -114,7 +145,15 @@ public RequestExecutionQueue(ILspSolutionProvider solutionProvider)
// Tell the queue to ignore any mutations from this request
return false;
}, requestCancellationToken);
_queue.Enqueue(item);
var didEnqueue = _queue.TryEnqueue(item);
// If the queue has been shut down the enqueue will fail, so we just fault the task immediately.
// The queue itself is threadsafe (_queue.TryEnqueue and _queue.Complete use the same lock).
if (!didEnqueue)
{
completion.SetException(new InvalidOperationException("Server was requested to shut down."));
}
return completion.Task;
}
......@@ -123,40 +162,98 @@ private async Task ProcessQueueAsync()
{
// Keep track of solution state modifications made by LSP requests
Solution? lastMutatedSolution = null;
var queueToken = _cancelSource.Token;
while (!queueToken.IsCancellationRequested)
try
{
var work = await _queue.DequeueAsync().ConfigureAwait(false);
while (!_cancelSource.IsCancellationRequested)
{
var work = await _queue.DequeueAsync().ConfigureAwait(false);
// Create a linked cancellation token to cancel any requests in progress when this shuts down
var cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(queueToken, work.CancellationToken).Token;
// Create a linked cancellation token to cancel any requests in progress when this shuts down
var cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelSource.Token, work.CancellationToken).Token;
// The "current" solution can be updated by non-LSP actions, so we need it, but we also need
// to merge in the changes from any mutations that have been applied to open documents
var (document, solution) = _solutionProvider.GetDocumentAndSolution(work.TextDocument, work.ClientName);
solution = MergeChanges(solution, lastMutatedSolution);
// The "current" solution can be updated by non-LSP actions, so we need it, but we also need
// to merge in the changes from any mutations that have been applied to open documents
var (document, solution) = _solutionProvider.GetDocumentAndSolution(work.TextDocument, work.ClientName);
solution = MergeChanges(solution, lastMutatedSolution);
Solution? mutatedSolution = null;
var context = new RequestContext(solution, work.ClientCapabilities, work.ClientName, document, s => mutatedSolution = s);
if (work.MutatesSolutionState)
{
Solution? mutatedSolution = null;
var context = new RequestContext(solution, work.ClientCapabilities, work.ClientName, document, s => mutatedSolution = s);
if (work.MutatesSolutionState)
{
// Mutating requests block other requests from starting to ensure an up to date snapshot is used.
var ranToCompletion = await work.CallbackAsync(context, cancellationToken).ConfigureAwait(false);
// Mutating requests block other requests from starting to ensure an up to date snapshot is used.
var ranToCompletion = false;
try
{
ranToCompletion = await work.CallbackAsync(context, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Since the cancellationToken passed to the callback is a linked source it could be cancelled
// without the queue token being cancelled, but ranToCompletion will be false so no need to
// do anything special here.
RoslynDebug.Assert(ranToCompletion == false);
}
// If the handling of the request failed, the exception will bubble back up to the caller, but we
// still need to react to it here by throwing away solution updates
if (ranToCompletion)
// If the handling of the request failed, the exception will bubble back up to the caller, but we
// still need to react to it here by throwing away solution updates
if (ranToCompletion)
{
lastMutatedSolution = mutatedSolution ?? lastMutatedSolution;
}
else
{
OnRequestServerShutdown($"An error occured processing a mutating request and the solution is in an invalid state. Check LSP client logs for any error information.");
break;
}
}
else
{
lastMutatedSolution = mutatedSolution ?? lastMutatedSolution;
var context = new RequestContext(solution, work.ClientCapabilities, work.ClientName, document, null);
// Non mutating are fire-and-forget because they are by definition readonly. Any errors
// will be sent back to the client but we can still capture errors in queue processing
// via NFW, though these errors don't put us into a bad state as far as the rest of the queue goes.
_ = work.CallbackAsync(context, cancellationToken).ReportNonFatalErrorAsync();
}
}
else
{
// Non mutating request get given the current solution state, but are otherwise fire-and-forget
_ = work.CallbackAsync(context, cancellationToken);
}
}
catch (OperationCanceledException e) when (e.CancellationToken == _cancelSource.Token)
{
// If the queue is asked to shut down between the start of the while loop, and the Dequeue call
// we could end up here, but we don't want to report an error. The Shutdown call will take care of things.
}
catch (Exception e) when (FatalError.ReportWithoutCrash(e))
{
OnRequestServerShutdown($"Error occured processing queue: {e.Message}.");
}
}
private void OnRequestServerShutdown(string message)
{
RequestServerShutdown?.Invoke(this, new RequestShutdownEventArgs(message));
Shutdown();
}
/// <summary>
/// Cancels all requests in the queue and stops the queue from accepting any more requests. After this method
/// is called this queue is essentially useless.
/// </summary>
private void DrainQueue()
{
// Tell the queue not to accept any more items
_queue.Complete();
// Spin through the queue and pass in our cancelled token, so that the waiting tasks are cancelled.
// NOTE: This only really works because the first thing that CallbackAsync does is check for cancellation
// but generics make it annoying to store the TaskCompletionSource<TResult> on the QueueItem so this
// is the best we can do for now. Ideally we would manipulate the TaskCompletionSource directly here
// and just call SetCanceled
while (_queue.TryDequeue(out var item))
{
_ = item.CallbackAsync(default, new CancellationToken(true));
}
}
......@@ -166,11 +263,5 @@ private static Solution MergeChanges(Solution solution, Solution? mutatedSolutio
// https://github.com/dotnet/roslyn/issues/45427
return mutatedSolution ?? solution;
}
public void Dispose()
{
_cancelSource.Cancel();
_cancelSource.Dispose();
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler
{
internal class RequestShutdownEventArgs : EventArgs
{
public string Message { get; }
public RequestShutdownEventArgs(string message)
{
this.Message = message;
}
}
}
......@@ -22,8 +22,8 @@ internal sealed class LanguageServerProtocol : AbstractRequestHandlerProvider
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public LanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, ILspSolutionProvider solutionProvider)
: base(requestHandlers, solutionProvider)
public LanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
: base(requestHandlers)
{
}
}
......
......@@ -110,7 +110,8 @@ void M()
VSCodeAction unresolvedCodeAction,
LSP.ClientCapabilities clientCapabilities = null)
{
var result = await GetLanguageServer(solution).ExecuteRequestAsync<LSP.VSCodeAction, LSP.VSCodeAction>(
var queue = CreateRequestQueue(solution);
var result = await GetLanguageServer(solution).ExecuteRequestAsync<LSP.VSCodeAction, LSP.VSCodeAction>(queue,
LSP.MSLSPMethods.TextDocumentCodeActionResolveName, unresolvedCodeAction,
clientCapabilities, null, CancellationToken.None);
return result;
......
......@@ -192,7 +192,8 @@ void M()
LSP.Location caret,
LSP.ClientCapabilities clientCapabilities = null)
{
var result = await GetLanguageServer(solution).ExecuteRequestAsync<LSP.CodeActionParams, LSP.VSCodeAction[]>(
var queue = CreateRequestQueue(solution);
var result = await GetLanguageServer(solution).ExecuteRequestAsync<LSP.CodeActionParams, LSP.VSCodeAction[]>(queue,
LSP.Methods.TextDocumentCodeActionName, CreateCodeActionParams(caret),
clientCapabilities, null, CancellationToken.None);
return result;
......
......@@ -41,8 +41,11 @@ void M()
}
private static async Task<object> RunResolveCompletionItemAsync(Solution solution, LSP.CompletionItem completionItem, LSP.ClientCapabilities clientCapabilities = null)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.CompletionItem, LSP.CompletionItem>(LSP.Methods.TextDocumentCompletionResolveName,
completionItem, clientCapabilities, null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.CompletionItem, LSP.CompletionItem>(queue, LSP.Methods.TextDocumentCompletionResolveName,
completionItem, clientCapabilities, null, CancellationToken.None);
}
private static LSP.VSCompletionItem CreateResolvedCompletionItem(string text, LSP.CompletionItemKind kind, string[] tags, LSP.CompletionParams requestParameters,
ClassifiedTextElement description, string detail, string documentation, string[] commitCharacters = null)
......
......@@ -128,7 +128,8 @@ void M()
private static async Task<LSP.CompletionList> RunGetCompletionsAsync(Solution solution, LSP.CompletionParams completionParams)
{
var clientCapabilities = new LSP.VSClientCapabilities { SupportsVisualStudioExtensions = true };
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.CompletionParams, LSP.CompletionList>(LSP.Methods.TextDocumentCompletionName,
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.CompletionParams, LSP.CompletionList>(queue, LSP.Methods.TextDocumentCompletionName,
completionParams, clientCapabilities, null, CancellationToken.None);
}
}
......
......@@ -103,7 +103,10 @@ void M()
}
private static async Task<LSP.Location[]> RunGotoDefinitionAsync(Solution solution, LSP.Location caret)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.Location[]>(LSP.Methods.TextDocumentDefinitionName,
CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.Location[]>(queue, LSP.Methods.TextDocumentDefinitionName,
CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None);
}
}
}
......@@ -75,7 +75,10 @@ class B
}
private static async Task<LSP.Location[]> RunGotoTypeDefinitionAsync(Solution solution, LSP.Location caret)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.Location[]>(LSP.Methods.TextDocumentTypeDefinitionName,
CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.Location[]>(queue, LSP.Methods.TextDocumentTypeDefinitionName,
CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None);
}
}
}
......@@ -69,7 +69,8 @@ private static async Task<LSP.FoldingRange[]> RunGetFoldingRangeAsync(Solution s
TextDocument = CreateTextDocumentIdentifier(new Uri(document.FilePath))
};
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.FoldingRangeParams, LSP.FoldingRange[]>(LSP.Methods.TextDocumentFoldingRangeName,
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.FoldingRangeParams, LSP.FoldingRange[]>(queue, LSP.Methods.TextDocumentFoldingRangeName,
request, new LSP.ClientCapabilities(), null, CancellationToken.None);
}
......
......@@ -45,9 +45,12 @@ void M()
}
private static async Task<LSP.TextEdit[]> RunFormatDocumentOnTypeAsync(Solution solution, string characterTyped, LSP.Location locationTyped)
=> await GetLanguageServer(solution)
.ExecuteRequestAsync<LSP.DocumentOnTypeFormattingParams, LSP.TextEdit[]>(LSP.Methods.TextDocumentOnTypeFormattingName,
CreateDocumentOnTypeFormattingParams(characterTyped, locationTyped), new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution)
.ExecuteRequestAsync<LSP.DocumentOnTypeFormattingParams, LSP.TextEdit[]>(queue, LSP.Methods.TextDocumentOnTypeFormattingName,
CreateDocumentOnTypeFormattingParams(characterTyped, locationTyped), new LSP.ClientCapabilities(), null, CancellationToken.None);
}
private static LSP.DocumentOnTypeFormattingParams CreateDocumentOnTypeFormattingParams(string characterTyped, LSP.Location locationTyped)
=> new LSP.DocumentOnTypeFormattingParams()
......
......@@ -42,8 +42,11 @@ void M()
}
private static async Task<LSP.TextEdit[]> RunFormatDocumentRangeAsync(Solution solution, LSP.Location location)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.DocumentRangeFormattingParams, LSP.TextEdit[]>(LSP.Methods.TextDocumentRangeFormattingName,
CreateDocumentRangeFormattingParams(location), new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.DocumentRangeFormattingParams, LSP.TextEdit[]>(queue, LSP.Methods.TextDocumentRangeFormattingName,
CreateDocumentRangeFormattingParams(location), new LSP.ClientCapabilities(), null, CancellationToken.None);
}
private static LSP.DocumentRangeFormattingParams CreateDocumentRangeFormattingParams(LSP.Location location)
=> new LSP.DocumentRangeFormattingParams()
......
......@@ -43,8 +43,11 @@ void M()
}
private static async Task<LSP.TextEdit[]> RunFormatDocumentAsync(Solution solution, Uri uri)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.DocumentFormattingParams, LSP.TextEdit[]>(LSP.Methods.TextDocumentFormattingName,
CreateDocumentFormattingParams(uri), new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.DocumentFormattingParams, LSP.TextEdit[]>(queue, LSP.Methods.TextDocumentFormattingName,
CreateDocumentFormattingParams(uri), new LSP.ClientCapabilities(), null, CancellationToken.None);
}
private static LSP.DocumentFormattingParams CreateDocumentFormattingParams(Uri uri)
=> new LSP.DocumentFormattingParams()
......
......@@ -61,7 +61,8 @@ void M()
private static async Task<LSP.DocumentHighlight[]> RunGetDocumentHighlightAsync(Solution solution, LSP.Location caret)
{
var results = await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.DocumentHighlight[]>(LSP.Methods.TextDocumentDocumentHighlightName,
var queue = CreateRequestQueue(solution);
var results = await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.DocumentHighlight[]>(queue, LSP.Methods.TextDocumentDocumentHighlightName,
CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None);
Array.Sort(results, (h1, h2) =>
{
......
......@@ -190,8 +190,11 @@ static void Main(string[] args)
}
private static async Task<LSP.VSHover> RunGetHoverAsync(Solution solution, LSP.Location caret, ProjectId projectContext = null)
=> (LSP.VSHover)await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.Hover>(LSP.Methods.TextDocumentHoverName,
CreateTextDocumentPositionParams(caret, projectContext), new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return (LSP.VSHover)await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.Hover>(queue, LSP.Methods.TextDocumentHoverName,
CreateTextDocumentPositionParams(caret, projectContext), new LSP.ClientCapabilities(), null, CancellationToken.None);
}
private void VerifyContent(LSP.VSHover result, string expectedContent)
{
......
......@@ -23,8 +23,11 @@ public async Task TestInitializeAsync()
}
private static async Task<LSP.InitializeResult> RunInitializeAsync(Solution solution, LSP.InitializeParams request)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.InitializeParams, LSP.InitializeResult>(LSP.Methods.InitializeName,
request, new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.InitializeParams, LSP.InitializeResult>(queue, LSP.Methods.InitializeName,
request, new LSP.ClientCapabilities(), null, CancellationToken.None);
}
private static void AssertServerCapabilities(LSP.ServerCapabilities actual)
{
......
......@@ -204,9 +204,11 @@ private async Task VerifyNoResult(string characterTyped, string markup)
}
private static async Task<LSP.DocumentOnAutoInsertResponseItem[]> RunOnAutoInsertAsync(Solution solution, string characterTyped, LSP.Location locationTyped)
=> await GetLanguageServer(solution)
.ExecuteRequestAsync<LSP.DocumentOnAutoInsertParams, LSP.DocumentOnAutoInsertResponseItem[]>(MSLSPMethods.OnAutoInsertName,
CreateDocumentOnAutoInsertParams(characterTyped, locationTyped), new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.DocumentOnAutoInsertParams, LSP.DocumentOnAutoInsertResponseItem[]>(queue, MSLSPMethods.OnAutoInsertName,
CreateDocumentOnAutoInsertParams(characterTyped, locationTyped), new LSP.ClientCapabilities(), null, CancellationToken.None);
}
private static LSP.DocumentOnAutoInsertParams CreateDocumentOnAutoInsertParams(string characterTyped, LSP.Location locationTyped)
=> new LSP.DocumentOnAutoInsertParams()
......
......@@ -141,7 +141,7 @@ public async Task ThrowingTaskDoesntBringDownQueue()
var waitables = StartTestRun(requests);
// first task should fail
await Assert.ThrowsAsync<InvalidOperationException>(async () => await waitables[0]);
await Assert.ThrowsAsync<InvalidOperationException>(() => waitables[0]);
// remaining tasks should have executed normally
var responses = await Task.WhenAll(waitables.Skip(1));
......@@ -151,8 +151,12 @@ public async Task ThrowingTaskDoesntBringDownQueue()
}
[Fact]
public async Task ThrowingMutableTaskDoesntBringDownQueue()
public async Task FailingMutableTaskShutsDownQueue()
{
// NOTE: A failing task shuts down the queue not due to an exception escaping out of the handler
// but because the solution state would be invalid. This doesn't test the queues exception
// resiliancy.
var requests = new[] {
new TestRequest(FailingMutatingRequestHandler.MethodName),
new TestRequest(NonMutatingRequestHandler.MethodName),
......@@ -163,34 +167,12 @@ public async Task ThrowingMutableTaskDoesntBringDownQueue()
var waitables = StartTestRun(requests);
// first task should fail
await Assert.ThrowsAsync<InvalidOperationException>(async () => await waitables[0]);
// remaining tasks should have executed normally
var responses = await Task.WhenAll(waitables.Skip(1));
Assert.Empty(responses.Where(r => r.StartTime == default));
Assert.All(responses, r => Assert.True(r.EndTime > r.StartTime));
}
[Fact]
public async Task ThrowingMutableTaskDoesntMutateTheSolution()
{
var requests = new[] {
new TestRequest(NonMutatingRequestHandler.MethodName),
new TestRequest(FailingMutatingRequestHandler.MethodName),
new TestRequest(NonMutatingRequestHandler.MethodName),
};
var waitables = StartTestRun(requests);
// second task should have failed
await Assert.ThrowsAsync<InvalidOperationException>(async () => await waitables[1]);
await Assert.ThrowsAsync<InvalidOperationException>(() => waitables[0]);
var responses = await Task.WhenAll(waitables.Where(t => !t.IsFaulted));
// remaining tasks should be canceled
await Assert.ThrowsAsync<TaskCanceledException>(() => Task.WhenAll(waitables.Skip(1)));
// First and last tasks use the same solution because the middle request failed
// Note the last task is the _second_ item in responses because it only contains the successful responses
Assert.Equal(responses[0].Solution.WorkspaceVersion, responses[1].Solution.WorkspaceVersion);
Assert.All(waitables.Skip(1), w => Assert.True(w.IsCanceled));
}
private async Task<TestResponse[]> TestAsync(TestRequest[] requests)
......@@ -211,6 +193,7 @@ private List<Task<TestResponse>> StartTestRun(TestRequest[] requests)
using var workspace = CreateTestWorkspace("class C { }", out _);
var solution = workspace.CurrentSolution;
var queue = CreateRequestQueue(solution);
var languageServer = GetLanguageServer(solution);
var clientCapabilities = new LSP.ClientCapabilities();
......@@ -219,7 +202,7 @@ private List<Task<TestResponse>> StartTestRun(TestRequest[] requests)
foreach (var request in requests)
{
request.RequestOrder = order++;
waitables.Add(languageServer.ExecuteRequestAsync<TestRequest, TestResponse>(request.MethodName, request, clientCapabilities, null, CancellationToken.None));
waitables.Add(languageServer.ExecuteRequestAsync<TestRequest, TestResponse>(queue, request.MethodName, request, clientCapabilities, null, CancellationToken.None));
}
return waitables;
......
......@@ -97,8 +97,11 @@ public async Task SwitchingContextsChangesDefaultContext()
}
private static async Task<LSP.ActiveProjectContexts?> RunGetProjectContext(Solution solution, Uri uri)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.GetTextDocumentWithContextParams, LSP.ActiveProjectContexts?>(LSP.MSLSPMethods.ProjectContextsName,
CreateGetProjectContextParams(uri), new LSP.ClientCapabilities(), clientName: null, cancellationToken: CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.GetTextDocumentWithContextParams, LSP.ActiveProjectContexts?>(queue, LSP.MSLSPMethods.ProjectContextsName,
CreateGetProjectContextParams(uri), new LSP.ClientCapabilities(), clientName: null, cancellationToken: CancellationToken.None);
}
private static LSP.GetTextDocumentWithContextParams CreateGetProjectContextParams(Uri uri)
=> new LSP.GetTextDocumentWithContextParams()
......
......@@ -129,7 +129,8 @@ private static async Task<LSP.VSReferenceItem[]> RunFindAllReferencesAsync(Solut
SupportsVisualStudioExtensions = true
};
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.ReferenceParams, LSP.VSReferenceItem[]>(LSP.Methods.TextDocumentReferencesName,
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.ReferenceParams, LSP.VSReferenceItem[]>(queue, LSP.Methods.TextDocumentReferencesName,
CreateReferenceParams(caret), vsClientCapabilities, null, CancellationToken.None);
}
......
......@@ -124,7 +124,10 @@ class {|implementation:C|} : A { }";
}
private static async Task<LSP.Location[]> RunFindImplementationAsync(Solution solution, LSP.Location caret)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.Location[]>(LSP.Methods.TextDocumentImplementationName,
CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.Location[]>(queue, LSP.Methods.TextDocumentImplementationName,
CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None);
}
}
}
......@@ -125,7 +125,10 @@ private static LSP.RenameParams CreateRenameParams(LSP.Location location, string
};
private static async Task<WorkspaceEdit> RunRenameAsync(Solution solution, LSP.Location renameLocation, string renamevalue)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.RenameParams, LSP.WorkspaceEdit>(LSP.Methods.TextDocumentRenameName,
CreateRenameParams(renameLocation, renamevalue), new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.RenameParams, LSP.WorkspaceEdit>(queue, LSP.Methods.TextDocumentRenameName,
CreateRenameParams(renameLocation, renamevalue), new LSP.ClientCapabilities(), null, CancellationToken.None);
}
}
}
......@@ -19,9 +19,12 @@ public abstract class AbstractSemanticTokensTests : AbstractLanguageServerProtoc
{
protected static async Task<LSP.SemanticTokens> RunGetSemanticTokensAsync(
Solution solution, LSP.Location caret)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.SemanticTokensParams, LSP.SemanticTokens>(
LSP.SemanticTokensMethods.TextDocumentSemanticTokensName,
CreateSemanticTokensParams(caret), new LSP.VSClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.SemanticTokensParams, LSP.SemanticTokens>(queue,
LSP.SemanticTokensMethods.TextDocumentSemanticTokensName,
CreateSemanticTokensParams(caret), new LSP.VSClientCapabilities(), null, CancellationToken.None);
}
private static LSP.SemanticTokensParams CreateSemanticTokensParams(LSP.Location caret)
=> new LSP.SemanticTokensParams
......@@ -31,9 +34,12 @@ private static LSP.SemanticTokensParams CreateSemanticTokensParams(LSP.Location
protected static async Task<LSP.SemanticTokens> RunGetSemanticTokensRangeAsync(
Solution solution, LSP.Location caret, LSP.Range range)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.SemanticTokensRangeParams, LSP.SemanticTokens>(
LSP.SemanticTokensMethods.TextDocumentSemanticTokensRangeName,
CreateSemanticTokensRangeParams(caret, range), new LSP.VSClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.SemanticTokensRangeParams, LSP.SemanticTokens>(queue,
LSP.SemanticTokensMethods.TextDocumentSemanticTokensRangeName,
CreateSemanticTokensRangeParams(caret, range), new LSP.VSClientCapabilities(), null, CancellationToken.None);
}
private static LSP.SemanticTokensRangeParams CreateSemanticTokensRangeParams(LSP.Location caret, LSP.Range range)
=> new LSP.SemanticTokensRangeParams
......@@ -44,9 +50,12 @@ private static LSP.SemanticTokensRangeParams CreateSemanticTokensRangeParams(LSP
protected static async Task<SumType<LSP.SemanticTokens, LSP.SemanticTokensEdits>> RunGetSemanticTokensEditsAsync(
Solution solution, LSP.Location caret, string previousResultId)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.SemanticTokensEditsParams, SumType<LSP.SemanticTokens, LSP.SemanticTokensEdits>>(
LSP.SemanticTokensMethods.TextDocumentSemanticTokensEditsName,
CreateSemanticTokensParams(caret, previousResultId), new LSP.VSClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.SemanticTokensEditsParams, SumType<LSP.SemanticTokens, LSP.SemanticTokensEdits>>(queue,
LSP.SemanticTokensMethods.TextDocumentSemanticTokensEditsName,
CreateSemanticTokensParams(caret, previousResultId), new LSP.VSClientCapabilities(), null, CancellationToken.None);
}
private static LSP.SemanticTokensEditsParams CreateSemanticTokensParams(LSP.Location caret, string previousResultId)
=> new LSP.SemanticTokensEditsParams
......
......@@ -45,8 +45,11 @@ int M2(string a)
}
private static async Task<LSP.SignatureHelp> RunGetSignatureHelpAsync(Solution solution, LSP.Location caret)
=> await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.SignatureHelp>(LSP.Methods.TextDocumentSignatureHelpName,
CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None);
{
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.TextDocumentPositionParams, LSP.SignatureHelp>(queue, LSP.Methods.TextDocumentSignatureHelpName,
CreateTextDocumentPositionParams(caret), new LSP.ClientCapabilities(), null, CancellationToken.None);
}
private static LSP.SignatureInformation CreateSignatureInformation(string methodLabal, string methodDocumentation, string parameterLabel, string parameterDocumentation)
=> new LSP.SignatureInformation()
......
......@@ -102,7 +102,8 @@ private static async Task<object[]> RunGetDocumentSymbolsAsync(Solution solution
}
};
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.DocumentSymbolParams, object[]>(LSP.Methods.TextDocumentDocumentSymbolName,
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.DocumentSymbolParams, object[]>(queue, LSP.Methods.TextDocumentDocumentSymbolName,
request, clientCapabilities, null, CancellationToken.None);
}
......
......@@ -156,7 +156,8 @@ private static async Task<LSP.SymbolInformation[]> RunGetWorkspaceSymbolsAsync(S
Query = query
};
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.WorkspaceSymbolParams, LSP.SymbolInformation[]>(LSP.Methods.WorkspaceSymbolName,
var queue = CreateRequestQueue(solution);
return await GetLanguageServer(solution).ExecuteRequestAsync<LSP.WorkspaceSymbolParams, LSP.SymbolInformation[]>(queue, LSP.Methods.WorkspaceSymbolName,
request, new LSP.ClientCapabilities(), null, CancellationToken.None);
}
}
......
......@@ -26,6 +26,7 @@ internal abstract class AbstractLanguageServerClient : ILanguageClient
private readonly IAsynchronousOperationListenerProvider _listenerProvider;
private readonly AbstractRequestHandlerProvider _requestHandlerProvider;
private readonly Workspace _workspace;
private readonly ILspSolutionProvider _solutionProvider;
private InProcLanguageServer? _languageServer;
/// <summary>
......@@ -63,6 +64,7 @@ internal abstract class AbstractLanguageServerClient : ILanguageClient
VisualStudioWorkspace workspace,
IDiagnosticService diagnosticService,
IAsynchronousOperationListenerProvider listenerProvider,
ILspSolutionProvider solutionProvider,
string? diagnosticsClientName)
{
_requestHandlerProvider = requestHandlerProvider;
......@@ -70,6 +72,7 @@ internal abstract class AbstractLanguageServerClient : ILanguageClient
_diagnosticService = diagnosticService;
_listenerProvider = listenerProvider;
_diagnosticsClientName = diagnosticsClientName;
_solutionProvider = solutionProvider;
}
public Task<Connection> ActivateAsync(CancellationToken token)
......@@ -78,7 +81,7 @@ public Task<Connection> ActivateAsync(CancellationToken token)
var (clientStream, serverStream) = FullDuplexStream.CreatePair();
_languageServer = new InProcLanguageServer(serverStream, serverStream, _requestHandlerProvider, _workspace,
_diagnosticService, _listenerProvider, clientName: _diagnosticsClientName);
_diagnosticService, _listenerProvider, _solutionProvider, clientName: _diagnosticsClientName);
return Task.FromResult(new Connection(clientStream, clientStream));
}
......
......@@ -16,6 +16,7 @@
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.LanguageServer.Handler;
using Microsoft.CodeAnalysis.LanguageServer.Handler.SemanticTokens;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
......@@ -41,6 +42,7 @@ internal class InProcLanguageServer
private readonly JsonRpc _jsonRpc;
private readonly AbstractRequestHandlerProvider _requestHandlerProvider;
private readonly CodeAnalysis.Workspace _workspace;
private readonly RequestExecutionQueue _queue;
private VSClientCapabilities _clientCapabilities;
private bool _shuttingDown;
......@@ -51,6 +53,7 @@ internal class InProcLanguageServer
CodeAnalysis.Workspace workspace,
IDiagnosticService diagnosticService,
IAsynchronousOperationListenerProvider listenerProvider,
ILspSolutionProvider solutionProvider,
string? clientName)
{
_requestHandlerProvider = requestHandlerProvider;
......@@ -70,6 +73,9 @@ internal class InProcLanguageServer
_diagnosticService.DiagnosticsUpdated += DiagnosticService_DiagnosticsUpdated;
_clientCapabilities = new VSClientCapabilities();
_queue = new RequestExecutionQueue(solutionProvider);
_queue.RequestServerShutdown += RequestExecutionQueue_Errored;
}
public bool Running => !_shuttingDown && !_jsonRpc.IsDisposed;
......@@ -84,7 +90,7 @@ public async Task<InitializeResult> InitializeAsync(InitializeParams initializeP
{
_clientCapabilities = (VSClientCapabilities)initializeParams.Capabilities;
var serverCapabilities = await _requestHandlerProvider.ExecuteRequestAsync<InitializeParams, InitializeResult>(Methods.InitializeName,
var serverCapabilities = await _requestHandlerProvider.ExecuteRequestAsync<InitializeParams, InitializeResult>(_queue, Methods.InitializeName,
initializeParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false);
// Always support hover - if any LSP client for a content type advertises support,
......@@ -119,6 +125,8 @@ public Task ShutdownAsync(CancellationToken _)
_shuttingDown = true;
_diagnosticService.DiagnosticsUpdated -= DiagnosticService_DiagnosticsUpdated;
ShutdownRequestQueue();
return Task.CompletedTask;
}
......@@ -142,119 +150,119 @@ public Task ExitAsync(CancellationToken _)
[JsonRpcMethod(Methods.TextDocumentDefinitionName, UseSingleObjectParameterDeserialization = true)]
public Task<LSP.Location[]> GetTextDocumentDefinitionAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, LSP.Location[]>(Methods.TextDocumentDefinitionName,
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, LSP.Location[]>(_queue, Methods.TextDocumentDefinitionName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentRenameName, UseSingleObjectParameterDeserialization = true)]
public Task<WorkspaceEdit> GetTextDocumentRenameAsync(RenameParams renameParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<RenameParams, WorkspaceEdit>(Methods.TextDocumentRenameName,
=> _requestHandlerProvider.ExecuteRequestAsync<RenameParams, WorkspaceEdit>(_queue, Methods.TextDocumentRenameName,
renameParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentReferencesName, UseSingleObjectParameterDeserialization = true)]
public Task<VSReferenceItem[]> GetTextDocumentReferencesAsync(ReferenceParams referencesParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<ReferenceParams, VSReferenceItem[]>(Methods.TextDocumentReferencesName,
=> _requestHandlerProvider.ExecuteRequestAsync<ReferenceParams, VSReferenceItem[]>(_queue, Methods.TextDocumentReferencesName,
referencesParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentCodeActionName, UseSingleObjectParameterDeserialization = true)]
public Task<VSCodeAction[]> GetTextDocumentCodeActionsAsync(CodeActionParams codeActionParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<CodeActionParams, VSCodeAction[]>(Methods.TextDocumentCodeActionName,
=> _requestHandlerProvider.ExecuteRequestAsync<CodeActionParams, VSCodeAction[]>(_queue, Methods.TextDocumentCodeActionName,
codeActionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(MSLSPMethods.TextDocumentCodeActionResolveName, UseSingleObjectParameterDeserialization = true)]
public Task<VSCodeAction> ResolveCodeActionAsync(VSCodeAction vsCodeAction, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<VSCodeAction, VSCodeAction>(MSLSPMethods.TextDocumentCodeActionResolveName,
=> _requestHandlerProvider.ExecuteRequestAsync<VSCodeAction, VSCodeAction>(_queue, MSLSPMethods.TextDocumentCodeActionResolveName,
vsCodeAction, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentCompletionName, UseSingleObjectParameterDeserialization = true)]
public async Task<SumType<CompletionList, CompletionItem[]>> GetTextDocumentCompletionAsync(CompletionParams completionParams, CancellationToken cancellationToken)
// Convert to sumtype before reporting to work around https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1107698
=> await _requestHandlerProvider.ExecuteRequestAsync<CompletionParams, CompletionList>(Methods.TextDocumentCompletionName,
=> await _requestHandlerProvider.ExecuteRequestAsync<CompletionParams, CompletionList>(_queue, Methods.TextDocumentCompletionName,
completionParams, _clientCapabilities, _clientName, cancellationToken).ConfigureAwait(false);
[JsonRpcMethod(Methods.TextDocumentCompletionResolveName, UseSingleObjectParameterDeserialization = true)]
public Task<CompletionItem> ResolveCompletionItemAsync(CompletionItem completionItem, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<CompletionItem, CompletionItem>(Methods.TextDocumentCompletionResolveName,
=> _requestHandlerProvider.ExecuteRequestAsync<CompletionItem, CompletionItem>(_queue, Methods.TextDocumentCompletionResolveName,
completionItem, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentFoldingRangeName, UseSingleObjectParameterDeserialization = true)]
public Task<FoldingRange[]> GetTextDocumentFoldingRangeAsync(FoldingRangeParams textDocumentFoldingRangeParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<FoldingRangeParams, FoldingRange[]>(Methods.TextDocumentFoldingRangeName,
=> _requestHandlerProvider.ExecuteRequestAsync<FoldingRangeParams, FoldingRange[]>(_queue, Methods.TextDocumentFoldingRangeName,
textDocumentFoldingRangeParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentDocumentHighlightName, UseSingleObjectParameterDeserialization = true)]
public Task<DocumentHighlight[]> GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, DocumentHighlight[]>(Methods.TextDocumentDocumentHighlightName,
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, DocumentHighlight[]>(_queue, Methods.TextDocumentDocumentHighlightName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentHoverName, UseSingleObjectParameterDeserialization = true)]
public Task<Hover?> GetTextDocumentDocumentHoverAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, Hover?>(Methods.TextDocumentHoverName,
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, Hover?>(_queue, Methods.TextDocumentHoverName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentDocumentSymbolName, UseSingleObjectParameterDeserialization = true)]
public Task<object[]> GetTextDocumentDocumentSymbolsAsync(DocumentSymbolParams documentSymbolParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentSymbolParams, object[]>(Methods.TextDocumentDocumentSymbolName,
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentSymbolParams, object[]>(_queue, Methods.TextDocumentDocumentSymbolName,
documentSymbolParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentFormattingName, UseSingleObjectParameterDeserialization = true)]
public Task<TextEdit[]> GetTextDocumentFormattingAsync(DocumentFormattingParams documentFormattingParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentFormattingParams, TextEdit[]>(Methods.TextDocumentFormattingName,
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentFormattingParams, TextEdit[]>(_queue, Methods.TextDocumentFormattingName,
documentFormattingParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentOnTypeFormattingName, UseSingleObjectParameterDeserialization = true)]
public Task<TextEdit[]> GetTextDocumentFormattingOnTypeAsync(DocumentOnTypeFormattingParams documentOnTypeFormattingParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentOnTypeFormattingParams, TextEdit[]>(Methods.TextDocumentOnTypeFormattingName,
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentOnTypeFormattingParams, TextEdit[]>(_queue, Methods.TextDocumentOnTypeFormattingName,
documentOnTypeFormattingParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentImplementationName, UseSingleObjectParameterDeserialization = true)]
public Task<LSP.Location[]> GetTextDocumentImplementationsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, LSP.Location[]>(Methods.TextDocumentImplementationName,
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, LSP.Location[]>(_queue, Methods.TextDocumentImplementationName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentRangeFormattingName, UseSingleObjectParameterDeserialization = true)]
public Task<TextEdit[]> GetTextDocumentRangeFormattingAsync(DocumentRangeFormattingParams documentRangeFormattingParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentRangeFormattingParams, TextEdit[]>(Methods.TextDocumentRangeFormattingName,
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentRangeFormattingParams, TextEdit[]>(_queue, Methods.TextDocumentRangeFormattingName,
documentRangeFormattingParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.TextDocumentSignatureHelpName, UseSingleObjectParameterDeserialization = true)]
public Task<SignatureHelp> GetTextDocumentSignatureHelpAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, SignatureHelp>(Methods.TextDocumentSignatureHelpName,
=> _requestHandlerProvider.ExecuteRequestAsync<TextDocumentPositionParams, SignatureHelp>(_queue, Methods.TextDocumentSignatureHelpName,
textDocumentPositionParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.WorkspaceExecuteCommandName, UseSingleObjectParameterDeserialization = true)]
public Task<object> ExecuteWorkspaceCommandAsync(ExecuteCommandParams executeCommandParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<ExecuteCommandParams, object>(Methods.WorkspaceExecuteCommandName,
=> _requestHandlerProvider.ExecuteRequestAsync<ExecuteCommandParams, object>(_queue, Methods.WorkspaceExecuteCommandName,
executeCommandParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(Methods.WorkspaceSymbolName, UseSingleObjectParameterDeserialization = true)]
public Task<SymbolInformation[]> GetWorkspaceSymbolsAsync(WorkspaceSymbolParams workspaceSymbolParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<WorkspaceSymbolParams, SymbolInformation[]>(Methods.WorkspaceSymbolName,
=> _requestHandlerProvider.ExecuteRequestAsync<WorkspaceSymbolParams, SymbolInformation[]>(_queue, Methods.WorkspaceSymbolName,
workspaceSymbolParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(MSLSPMethods.ProjectContextsName, UseSingleObjectParameterDeserialization = true)]
public Task<ActiveProjectContexts?> GetProjectContextsAsync(GetTextDocumentWithContextParams textDocumentWithContextParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<GetTextDocumentWithContextParams, ActiveProjectContexts?>(MSLSPMethods.ProjectContextsName,
=> _requestHandlerProvider.ExecuteRequestAsync<GetTextDocumentWithContextParams, ActiveProjectContexts?>(_queue, MSLSPMethods.ProjectContextsName,
textDocumentWithContextParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(SemanticTokensMethods.TextDocumentSemanticTokensName, UseSingleObjectParameterDeserialization = true)]
public Task<SemanticTokens> GetTextDocumentSemanticTokensAsync(SemanticTokensParams semanticTokensParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<SemanticTokensParams, SemanticTokens>(SemanticTokensMethods.TextDocumentSemanticTokensName,
=> _requestHandlerProvider.ExecuteRequestAsync<SemanticTokensParams, SemanticTokens>(_queue, SemanticTokensMethods.TextDocumentSemanticTokensName,
semanticTokensParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(SemanticTokensMethods.TextDocumentSemanticTokensEditsName, UseSingleObjectParameterDeserialization = true)]
public Task<SumType<SemanticTokens, SemanticTokensEdits>> GetTextDocumentSemanticTokensEditsAsync(SemanticTokensEditsParams semanticTokensEditsParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<SemanticTokensEditsParams, SumType<SemanticTokens, SemanticTokensEdits>>(SemanticTokensMethods.TextDocumentSemanticTokensEditsName,
=> _requestHandlerProvider.ExecuteRequestAsync<SemanticTokensEditsParams, SumType<SemanticTokens, SemanticTokensEdits>>(_queue, SemanticTokensMethods.TextDocumentSemanticTokensEditsName,
semanticTokensEditsParams, _clientCapabilities, _clientName, cancellationToken);
// Note: Since a range request is always received in conjunction with a whole document request, we don't need to cache range results.
[JsonRpcMethod(SemanticTokensMethods.TextDocumentSemanticTokensRangeName, UseSingleObjectParameterDeserialization = true)]
public Task<SemanticTokens> GetTextDocumentSemanticTokensRangeAsync(SemanticTokensRangeParams semanticTokensRangeParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<SemanticTokensRangeParams, SemanticTokens>(SemanticTokensMethods.TextDocumentSemanticTokensRangeName,
=> _requestHandlerProvider.ExecuteRequestAsync<SemanticTokensRangeParams, SemanticTokens>(_queue, SemanticTokensMethods.TextDocumentSemanticTokensRangeName,
semanticTokensRangeParams, _clientCapabilities, _clientName, cancellationToken);
[JsonRpcMethod(MSLSPMethods.OnAutoInsertName, UseSingleObjectParameterDeserialization = true)]
public Task<DocumentOnAutoInsertResponseItem[]> GetDocumentOnAutoInsertAsync(DocumentOnAutoInsertParams autoInsertParams, CancellationToken cancellationToken)
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentOnAutoInsertParams, DocumentOnAutoInsertResponseItem[]>(MSLSPMethods.OnAutoInsertName,
=> _requestHandlerProvider.ExecuteRequestAsync<DocumentOnAutoInsertParams, DocumentOnAutoInsertResponseItem[]>(_queue, MSLSPMethods.OnAutoInsertName,
autoInsertParams, _clientCapabilities, _clientName, cancellationToken);
private void DiagnosticService_DiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs e)
......@@ -281,6 +289,35 @@ private void DiagnosticService_DiagnosticsUpdated(object sender, DiagnosticsUpda
}
}
private void ShutdownRequestQueue()
{
_queue.RequestServerShutdown -= RequestExecutionQueue_Errored;
// if the queue requested shutdown via its event, it will have already shut itself down, but this
// won't cause any problems calling it again
_queue.Shutdown();
}
private void RequestExecutionQueue_Errored(object sender, RequestShutdownEventArgs e)
{
// log message and shut down
var message = new LogMessageParams()
{
MessageType = MessageType.Error,
Message = e.Message
};
var asyncToken = _listener.BeginAsyncOperation(nameof(RequestExecutionQueue_Errored));
Task.Run(async () =>
{
await _jsonRpc.NotifyWithParameterObjectAsync(Methods.WindowLogMessageName, message).ConfigureAwait(false);
// The "default" here is the cancellation token, which these methods don't use, hence the discard name
await ShutdownAsync(_: default).ConfigureAwait(false);
await ExitAsync(_: default).ConfigureAwait(false);
}).CompletesAsyncOperation(asyncToken);
}
/// <summary>
/// Stores the last published LSP diagnostics with the Roslyn document that they came from.
/// This is useful in the following scenario. Imagine we have documentA which has contributions to mapped files m1 and m2.
......
......@@ -29,8 +29,9 @@ internal class LiveShareLanguageServerClient : AbstractLanguageServerClient
public LiveShareLanguageServerClient(LanguageServerProtocol languageServerProtocol,
VisualStudioWorkspace workspace,
IDiagnosticService diagnosticService,
IAsynchronousOperationListenerProvider listenerProvider)
: base(languageServerProtocol, workspace, diagnosticService, listenerProvider, diagnosticsClientName: null)
IAsynchronousOperationListenerProvider listenerProvider,
ILspSolutionProvider solutionProvider)
: base(languageServerProtocol, workspace, diagnosticService, listenerProvider, solutionProvider, diagnosticsClientName: null)
{
}
......
......@@ -42,8 +42,9 @@ internal class RazorLanguageClient : AbstractLanguageServerClient
public RazorLanguageClient(LanguageServerProtocol languageServerProtocol,
VisualStudioWorkspace workspace,
IDiagnosticService diagnosticService,
IAsynchronousOperationListenerProvider listenerProvider)
: base(languageServerProtocol, workspace, diagnosticService, listenerProvider, ClientName)
IAsynchronousOperationListenerProvider listenerProvider,
ILspSolutionProvider solutionProvider)
: base(languageServerProtocol, workspace, diagnosticService, listenerProvider, solutionProvider, ClientName)
{
}
}
......
......@@ -379,8 +379,9 @@ static InProcLanguageServer CreateLanguageServer(Stream inputStream, Stream outp
{
var protocol = workspace.ExportProvider.GetExportedValue<LanguageServerProtocol>();
var listenerProvider = workspace.ExportProvider.GetExportedValue<IAsynchronousOperationListenerProvider>();
var solutionProvider = workspace.ExportProvider.GetExportedValue<ILspSolutionProvider>();
var languageServer = new InProcLanguageServer(inputStream, outputStream, protocol, workspace, mockDiagnosticService, listenerProvider, clientName: null);
var languageServer = new InProcLanguageServer(inputStream, outputStream, protocol, workspace, mockDiagnosticService, listenerProvider, solutionProvider, clientName: null);
return languageServer;
}
}
......
......@@ -9,6 +9,7 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
......@@ -24,8 +25,8 @@ internal class XamlLanguageServerClient : AbstractLanguageServerClient
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, true)]
public XamlLanguageServerClient(XamlLanguageServerProtocol languageServerProtocol, VisualStudioWorkspace workspace, IDiagnosticService diagnosticService, IAsynchronousOperationListenerProvider listenerProvider)
: base(languageServerProtocol, workspace, diagnosticService, listenerProvider, diagnosticsClientName: null)
public XamlLanguageServerClient(XamlLanguageServerProtocol languageServerProtocol, VisualStudioWorkspace workspace, IDiagnosticService diagnosticService, IAsynchronousOperationListenerProvider listenerProvider, ILspSolutionProvider solutionProvider)
: base(languageServerProtocol, workspace, diagnosticService, listenerProvider, solutionProvider, diagnosticsClientName: null)
{
}
......
......@@ -23,8 +23,8 @@ internal sealed class XamlLanguageServerProtocol : AbstractRequestHandlerProvide
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public XamlLanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers, ILspSolutionProvider solutionProvider)
: base(requestHandlers, solutionProvider, languageName: StringConstants.XamlLanguageName)
public XamlLanguageServerProtocol([ImportMany] IEnumerable<Lazy<IRequestHandler, IRequestHandlerMetadata>> requestHandlers)
: base(requestHandlers, languageName: StringConstants.XamlLanguageName)
{
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册