Skip to content

Commit

Permalink
Add support for language server exit & shutdown. (#44900)
Browse files Browse the repository at this point in the history
- The language server platform is properly implementing solution close understanding for local language servers and I found that when testing their bits Roslyn would explode gloriously. Turns out shutdown and exit support just wasn't fully enabled yet.
- When the language server platform reboots language servers it re-invokes Activate and because of that Roslyn explodes because the language client has already been activated and there was previously a contract throws check on already being initialized.
- Implemented the `ShutdownAsync` target on the `InProcLanguageServer` to detach from the diagnostic service.
- Implemented the `ExitAsync` target on the `InProcLanguageServer` to properly dispose our `JsonRpc` object.
  • Loading branch information
NTaylorMullen authored Jun 11, 2020
1 parent d3298d8 commit 5c5d58d
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public AbstractLanguageServerClient(LanguageServerProtocol languageServerProtoco

public Task<Connection> ActivateAsync(CancellationToken token)
{
Contract.ThrowIfFalse(_languageServer == null, "This language server has already been initialized");
Contract.ThrowIfTrue(_languageServer?.Running == true, "The language server has not yet shutdown.");

var (clientStream, serverStream) = FullDuplexStream.CreatePair();
_languageServer = new InProcLanguageServer(serverStream, serverStream, _languageServerProtocol, _workspace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ internal class InProcLanguageServer
private readonly CodeAnalysis.Workspace _workspace;

private VSClientCapabilities _clientCapabilities;
private bool _shuttingDown;

public InProcLanguageServer(Stream inputStream,
Stream outputStream,
Expand All @@ -66,6 +67,8 @@ public InProcLanguageServer(Stream inputStream,
_clientCapabilities = new VSClientCapabilities();
}

public bool Running => !_shuttingDown && !_jsonRpc.IsDisposed;

/// <summary>
/// Handle the LSP initialize request by storing the client capabilities
/// and responding with the server capabilities.
Expand Down Expand Up @@ -104,11 +107,32 @@ public async Task InitializedAsync()
}

[JsonRpcMethod(Methods.ShutdownName)]
public object? Shutdown(CancellationToken _) => null;
public Task ShutdownAsync(CancellationToken _)
{
Contract.ThrowIfTrue(_shuttingDown, "Shutdown has already been called.");

_shuttingDown = true;
_diagnosticService.DiagnosticsUpdated -= DiagnosticService_DiagnosticsUpdated;

return Task.CompletedTask;
}

[JsonRpcMethod(Methods.ExitName)]
public void Exit()
public Task ExitAsync(CancellationToken _)
{
Contract.ThrowIfFalse(_shuttingDown, "Shutdown has not been called yet.");

try
{
_jsonRpc.Dispose();
}
catch (Exception e) when (FatalError.ReportWithoutCrash(e))
{
// Swallow exceptions thrown by disposing our JsonRpc object. Disconnected events can potentially throw their own exceptions so
// we purposefully ignore all of those exceptions in an effort to shutdown gracefully.
}

return Task.CompletedTask;
}

[JsonRpcMethod(Methods.TextDocumentDefinitionName, UseSingleObjectParameterDeserialization = true)]
Expand Down

0 comments on commit 5c5d58d

Please sign in to comment.