From 78cdde3beb7ad451f381637f35bb437f12ada9a8 Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Fri, 25 Dec 2015 06:56:46 -0800 Subject: [PATCH] Server supports shutdown request --- .../Core/CommandLine/BuildProtocol.cs | 4 +- .../Server/ServerShared/Connection.cs | 93 ++++++++++++------- .../Server/ServerShared/ServerDispatcher.cs | 25 ++++- .../CompilerServerApiTest.cs | 1 - .../VBCSCompilerTests/CompilerServerTests.cs | 29 +++++- 5 files changed, 110 insertions(+), 42 deletions(-) diff --git a/src/Compilers/Core/CommandLine/BuildProtocol.cs b/src/Compilers/Core/CommandLine/BuildProtocol.cs index 1460550fbd2..40fcc977aaa 100644 --- a/src/Compilers/Core/CommandLine/BuildProtocol.cs +++ b/src/Compilers/Core/CommandLine/BuildProtocol.cs @@ -151,7 +151,7 @@ public static async Task ReadAsync(Stream inStream, CancellationTo /// /// Write a Request to the stream. /// - public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken) + public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken = default(CancellationToken)) { using (var memoryStream = new MemoryStream()) using (var writer = new BinaryWriter(memoryStream, Encoding.Unicode)) @@ -305,7 +305,7 @@ public enum ResponseType /// /// /// - public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken) + public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken)) { Log("Reading response length"); // Read the response length diff --git a/src/Compilers/Server/ServerShared/Connection.cs b/src/Compilers/Server/ServerShared/Connection.cs index 9185e836650..ff4f84c3b2d 100644 --- a/src/Compilers/Server/ServerShared/Connection.cs +++ b/src/Compilers/Server/ServerShared/Connection.cs @@ -2,6 +2,7 @@ using Roslyn.Utilities; using System; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Threading; @@ -45,6 +46,11 @@ internal enum CompletionReason /// There was an unhandled exception processing the result. /// ClientException, + + /// + /// There was a request from the client to shutdown the server. + /// + ClientShutdownRequest, } /// @@ -100,42 +106,14 @@ public async Task HandleConnection(CancellationToken cancellatio return new ConnectionData(CompletionReason.CompilationNotStarted); } - var keepAlive = CheckForNewKeepAlive(request); - - // Kick off both the compilation and a task to monitor the pipe for closing. - var buildCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - var compilationTask = ServeBuildRequest(request, buildCts.Token); - var monitorTask = CreateMonitorDisconnectTask(buildCts.Token); - await Task.WhenAny(compilationTask, monitorTask).ConfigureAwait(false); - - // Do an 'await' on the completed task, preference being compilation, to force - // any exceptions to be realized in this method for logging. - CompletionReason reason; - if (compilationTask.IsCompleted) + if (IsShutdownRequest(request)) { - var response = await compilationTask.ConfigureAwait(false); - - try - { - Log("Begin writing response."); - await response.WriteAsync(_stream, cancellationToken).ConfigureAwait(false); - reason = CompletionReason.CompilationCompleted; - Log("End writing response."); - } - catch - { - reason = CompletionReason.ClientDisconnect; - } + return await HandleShutdownRequest(cancellationToken).ConfigureAwait(false); } else { - await monitorTask.ConfigureAwait(false); - reason = CompletionReason.ClientDisconnect; + return await HandleCompilationRequest(request, cancellationToken).ConfigureAwait(false); } - - // Begin the tear down of the Task which didn't complete. - buildCts.Cancel(); - return new ConnectionData(reason, keepAlive); } finally { @@ -143,6 +121,54 @@ public async Task HandleConnection(CancellationToken cancellatio } } + private async Task HandleCompilationRequest(BuildRequest request, CancellationToken cancellationToken) + { + var keepAlive = CheckForNewKeepAlive(request); + + // Kick off both the compilation and a task to monitor the pipe for closing. + var buildCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + var compilationTask = ServeBuildRequest(request, buildCts.Token); + var monitorTask = CreateMonitorDisconnectTask(buildCts.Token); + await Task.WhenAny(compilationTask, monitorTask).ConfigureAwait(false); + + // Do an 'await' on the completed task, preference being compilation, to force + // any exceptions to be realized in this method for logging. + CompletionReason reason; + if (compilationTask.IsCompleted) + { + var response = await compilationTask.ConfigureAwait(false); + + try + { + Log("Begin writing response."); + await response.WriteAsync(_stream, cancellationToken).ConfigureAwait(false); + reason = CompletionReason.CompilationCompleted; + Log("End writing response."); + } + catch + { + reason = CompletionReason.ClientDisconnect; + } + } + else + { + await monitorTask.ConfigureAwait(false); + reason = CompletionReason.ClientDisconnect; + } + + // Begin the tear down of the Task which didn't complete. + buildCts.Cancel(); + return new ConnectionData(reason, keepAlive); + } + + private async Task HandleShutdownRequest(CancellationToken cancellationToken) + { + var id = Process.GetCurrentProcess().Id; + var response = new ShutdownBuildResponse(id); + await response.WriteAsync(_stream, cancellationToken).ConfigureAwait(false); + return new ConnectionData(CompletionReason.ClientShutdownRequest); + } + /// /// Check the request arguments for a new keep alive time. If one is present, /// set the server timer to the new time. @@ -169,6 +195,11 @@ public async Task HandleConnection(CancellationToken cancellatio return timeout; } + private bool IsShutdownRequest(BuildRequest request) + { + return request.Arguments.Length == 1 && request.Arguments[0].ArgumentId == BuildProtocolConstants.ArgumentId.Shutdown; + } + protected virtual Task ServeBuildRequest(BuildRequest request, CancellationToken cancellationToken) { return Task.Run(() => diff --git a/src/Compilers/Server/ServerShared/ServerDispatcher.cs b/src/Compilers/Server/ServerShared/ServerDispatcher.cs index 164a480b5c7..b2d0d403d65 100644 --- a/src/Compilers/Server/ServerShared/ServerDispatcher.cs +++ b/src/Compilers/Server/ServerShared/ServerDispatcher.cs @@ -169,10 +169,10 @@ private void WaitForAnyCompletion(IEnumerable> e, Task[] ot /// /// Checks the completed connection objects. /// - /// True if everything completed normally and false if there were any client disconnections. + /// False if the server needs to begin shutting down private bool CheckConnectionTask(List> connectionList, ref TimeSpan? keepAlive, ref bool isKeepAliveDefault) { - var allFine = true; + var shutdown = false; var processedCount = 0; var i = 0; while (i < connectionList.Count) @@ -189,9 +189,24 @@ private bool CheckConnectionTask(List> connectionList, ref var connectionData = current.Result; ChangeKeepAlive(connectionData.KeepAlive, ref keepAlive, ref isKeepAliveDefault); - if (connectionData.CompletionReason == CompletionReason.ClientDisconnect || connectionData.CompletionReason == CompletionReason.ClientException) + + switch (connectionData.CompletionReason) { - allFine = false; + case CompletionReason.CompilationCompleted: + case CompletionReason.CompilationNotStarted: + // These are all normal shutdown states. Nothing to do here. + break; + case CompletionReason.ClientDisconnect: + // Have to assume the worst here which is user pressing Ctrl+C at the command line and + // hence wanting all compilation to end. + shutdown = true; + break; + case CompletionReason.ClientException: + case CompletionReason.ClientShutdownRequest: + shutdown = true; + break; + default: + throw new InvalidOperationException($"Unexpected enum value {connectionData.CompletionReason}"); } } @@ -200,7 +215,7 @@ private bool CheckConnectionTask(List> connectionList, ref _diagnosticListener.ConnectionCompleted(processedCount); } - return allFine; + return !shutdown; } private void ChangeKeepAlive(TimeSpan? value, ref TimeSpan? keepAlive, ref bool isKeepAliveDefault) diff --git a/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs b/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs index 4e2681f872b..85dc47947f7 100644 --- a/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs +++ b/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs @@ -249,7 +249,6 @@ public async Task KeepAliveAfterSimultaneousConnection() dispatcher.ListenAndDispatchConnections(keepAlive); }); - await readySource.Task.ConfigureAwait(true); foreach (var source in list) { diff --git a/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs b/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs index 4ddcff008c9..ecb0620ea85 100644 --- a/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs +++ b/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.IO.Pipes; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; @@ -1359,7 +1360,7 @@ public async Task ServerRespectsAppConfig() var exited = proc.HasExited; if (!exited) { - proc.Kill(); + Kill(proc); Assert.True(false, "Compiler server did not exit in time"); } } @@ -1408,7 +1409,28 @@ public void BadKeepAlive4() Assert.Equal("", result.Errors); } - [Fact, WorkItem(1024619, "DevDiv")] + [Fact] + public async Task ShutdownRequestDirect() + { + using (var serverData = ServerUtil.CreateServer()) + using (var client = new NamedPipeClientStream(serverData.PipeName)) + { + await client.ConnectAsync(); + + var memoryStream = new MemoryStream(); + await BuildRequest.CreateShutdown().WriteAsync(memoryStream); + memoryStream.Position = 0; + await memoryStream.CopyToAsync(client); + + var response = await BuildResponse.ReadAsync(client); + Assert.Equal(BuildResponse.ResponseType.Shutdown, response.Type); + Assert.Equal(Process.GetCurrentProcess().Id, ((ShutdownBuildResponse)response).ServerProcessId); + await Verify(serverData, connections: 1, completed: 1); + } + } + + [Fact] + [WorkItem(1024619, "DevDiv")] public async Task Bug1024619_01() { using (var serverData = ServerUtil.CreateServer()) @@ -1440,7 +1462,8 @@ public async Task Bug1024619_01() } } - [Fact, WorkItem(1024619, "DevDiv")] + [Fact] + [WorkItem(1024619, "DevDiv")] public async Task Bug1024619_02() { using (var serverData = ServerUtil.CreateServer()) -- GitLab