public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken) { if (!TryParseArguments(request, out var parsed)) { return(new RejectedServerResponse()); } var exitCode = 0; var output = string.Empty; var app = new Application(cancellationToken); var commandArgs = parsed.args.ToArray(); if (ServerLogger.IsLoggingEnabled) { using (var writer = new StringWriter()) { app.Out = writer; app.Error = writer; exitCode = app.Execute(commandArgs); output = writer.ToString(); ServerLogger.Log(output); } } else { using (var writer = new StreamWriter(Stream.Null)) { app.Out = writer; app.Error = writer; exitCode = app.Execute(commandArgs); } } return(new CompletedServerResponse(exitCode, utf8output: false, output: string.Empty)); }
public async Task ServerRequest_WriteRead_RoundtripsProperly() { // Arrange var request = new ServerRequest( ServerProtocol.ProtocolVersion, ImmutableArray.Create( new RequestArgument(RequestArgument.ArgumentId.CurrentDirectory, argumentIndex: 0, value: "directory"), new RequestArgument(RequestArgument.ArgumentId.CommandLineArgument, argumentIndex: 1, value: "file"))); var memoryStream = new MemoryStream(); // Act await request.WriteAsync(memoryStream, CancellationToken.None); // Assert Assert.True(memoryStream.Position > 0); memoryStream.Position = 0; var read = await ServerRequest.ReadAsync(memoryStream, CancellationToken.None); Assert.Equal(ServerProtocol.ProtocolVersion, read.ProtocolVersion); Assert.Equal(2, read.Arguments.Count); Assert.Equal(RequestArgument.ArgumentId.CurrentDirectory, read.Arguments[0].Id); Assert.Equal(0, read.Arguments[0].ArgumentIndex); Assert.Equal("directory", read.Arguments[0].Value); Assert.Equal(RequestArgument.ArgumentId.CommandLineArgument, read.Arguments[1].Id); Assert.Equal(1, read.Arguments[1].ArgumentIndex); Assert.Equal("file", read.Arguments[1].Value); }
public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken) { if (!TryParseArguments(request, out var parsed)) { return(new RejectedServerResponse()); } var exitCode = 0; var commandArgs = parsed.args.ToArray(); var outputWriter = new StringWriter(); var errorWriter = new StringWriter(); var checker = new DefaultExtensionDependencyChecker(Loader, outputWriter, errorWriter); var app = new Application(cancellationToken, Loader, checker, AssemblyReferenceProvider, outputWriter, errorWriter); exitCode = app.Execute(commandArgs); var output = outputWriter.ToString(); var error = errorWriter.ToString(); outputWriter.Dispose(); errorWriter.Dispose(); // This will no-op if server logging is not enabled. ServerLogger.Log(output); ServerLogger.Log(error); return(new CompletedServerResponse(exitCode, utf8output: false, output, error)); }
public async Task ShutdownRequest_WriteRead_RoundtripsProperly() { // Arrange var memoryStream = new MemoryStream(); var request = ServerRequest.CreateShutdown(); // Act await request.WriteAsync(memoryStream, CancellationToken.None); // Assert memoryStream.Position = 0; var read = await ServerRequest.ReadAsync(memoryStream, CancellationToken.None); var argument1 = request.Arguments[0]; Assert.Equal(RequestArgument.ArgumentId.Shutdown, argument1.Id); Assert.Equal(0, argument1.ArgumentIndex); Assert.Equal("", argument1.Value); var argument2 = request.Arguments[1]; Assert.Equal(RequestArgument.ArgumentId.CommandLineArgument, argument2.Id); Assert.Equal(1, argument2.ArgumentIndex); Assert.Equal("shutdown", argument2.Value); }
public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken) { if (!TryParseArguments(request, out var parsed)) { return(new RejectedServerResponse()); } var exitCode = 0; var output = string.Empty; var commandArgs = parsed.args.ToArray(); var writer = ServerLogger.IsLoggingEnabled ? new StringWriter() : TextWriter.Null; var checker = new DefaultExtensionDependencyChecker(Loader, writer); var app = new Application(cancellationToken, Loader, checker) { Out = writer, Error = writer, }; exitCode = app.Execute(commandArgs); if (ServerLogger.IsLoggingEnabled) { output = writer.ToString(); ServerLogger.Log(output); } return(new CompletedServerResponse(exitCode, utf8output: false, output: string.Empty)); }
/// <summary> /// Try to process the request using the server. Returns a null-containing Task if a response /// from the server cannot be retrieved. /// </summary> private static async Task <ServerResponse> TryProcessRequest( Client client, ServerRequest request, CancellationToken cancellationToken) { ServerResponse response; using (client) { // Write the request try { ServerLogger.Log("Begin writing request"); await request.WriteAsync(client.Stream, cancellationToken).ConfigureAwait(false); ServerLogger.Log("End writing request"); } catch (Exception e) { ServerLogger.LogException(e, "Error writing build request."); return(new RejectedServerResponse()); } // Wait for the compilation and a monitor to detect if the server disconnects var serverCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); ServerLogger.Log("Begin reading response"); var responseTask = ServerResponse.ReadAsync(client.Stream, serverCts.Token); var monitorTask = client.WaitForDisconnectAsync(serverCts.Token); await Task.WhenAny(new[] { responseTask, monitorTask }).ConfigureAwait(false); ServerLogger.Log("End reading response"); if (responseTask.IsCompleted) { // await the task to log any exceptions try { response = await responseTask.ConfigureAwait(false); } catch (Exception e) { ServerLogger.LogException(e, "Error reading response"); response = new RejectedServerResponse(); } } else { ServerLogger.Log("Server disconnect"); response = new RejectedServerResponse(); } // Cancel whatever task is still around serverCts.Cancel(); Debug.Assert(response != null); return(response); } }
public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken) { if (ExecuteFunc != null) { return(ExecuteFunc(request, cancellationToken)); } return(EmptyServerResponse); }
internal static async Task <ServerResponse> Send(string pipeName, ServerRequest request) { using (var client = await Client.ConnectAsync(pipeName, timeout: null, cancellationToken: default).ConfigureAwait(false)) { await request.WriteAsync(client.Stream).ConfigureAwait(false); return(await ServerResponse.ReadAsync(client.Stream).ConfigureAwait(false)); } }
protected async override Task <int> ExecuteCoreAsync() { if (!IsServerRunning()) { // server isn't running right now Out.Write("Server is not running."); return(0); } try { using (var client = await Client.ConnectAsync(Pipe.Value(), timeout: TimeSpan.FromSeconds(5), cancellationToken: Cancelled)) { if (client == null) { throw new InvalidOperationException("Couldn't connect to the server."); } var request = ServerRequest.CreateShutdown(); await request.WriteAsync(client.Stream, Cancelled).ConfigureAwait(false); var response = ((ShutdownServerResponse)await ServerResponse.ReadAsync(client.Stream, Cancelled)); if (Wait.HasValue()) { try { var process = Process.GetProcessById(response.ServerProcessId); process.WaitForExit(); } catch (Exception ex) { // There is an inherent race here with the server process. If it has already shutdown // by the time we try to access it then the operation has succeeded. Error.Write(ex); } Out.Write("Server pid:{0} shut down completed.", response.ServerProcessId); } } } catch (Exception ex) when(IsServerRunning()) { // Ignore an exception that occurred while the server was shutting down. Error.Write(ex); } return(0); }
private Task <ServerResponse> ExecuteRequestAsync(ServerRequest buildRequest, CancellationToken cancellationToken) { Func <ServerResponse> func = () => { ServerLogger.Log("Begin processing request"); var response = _compilerHost.Execute(buildRequest, cancellationToken); ServerLogger.Log("End processing request"); return(response); }; var task = new Task <ServerResponse>(func, cancellationToken, TaskCreationOptions.LongRunning); task.Start(); return(task); }
public void CreateShutdown_CreatesCorrectShutdownRequest() { // Arrange & Act var request = ServerRequest.CreateShutdown(); // Assert Assert.Equal(2, request.Arguments.Count); var argument1 = request.Arguments[0]; Assert.Equal(RequestArgument.ArgumentId.Shutdown, argument1.Id); Assert.Equal(0, argument1.ArgumentIndex); Assert.Equal("", argument1.Value); var argument2 = request.Arguments[1]; Assert.Equal(RequestArgument.ArgumentId.CommandLineArgument, argument2.Id); Assert.Equal(1, argument2.ArgumentIndex); Assert.Equal("shutdown", argument2.Value); }
public async Task AcceptConnection_ShutdownRequest_ReturnsShutdownResponse() { // Arrange var stream = new TestableStream(); await ServerRequest.CreateShutdown().WriteAsync(stream.ReadStream, CancellationToken.None); stream.ReadStream.Position = 0; var connection = CreateConnection(stream); var connectionHost = CreateConnectionHost(); var compilerHost = CreateCompilerHost(); var dispatcher = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None); // Act var connectionResult = await dispatcher.AcceptConnection( Task.FromResult <Connection>(connection), accept : true, cancellationToken : CancellationToken.None); // Assert Assert.Equal(ConnectionResult.Reason.ClientShutdownRequest, connectionResult.CloseReason); stream.WriteStream.Position = 0; var response = await ServerResponse.ReadAsync(stream.WriteStream).ConfigureAwait(false); Assert.Equal(ServerResponse.ResponseType.Shutdown, response.Type); }
private static async Task <ServerResponse> RunOnServerCore( IList <string> arguments, ServerPaths buildPaths, string pipeName, string keepAlive, int?timeoutOverride, Func <string, string, bool, bool> tryCreateServerFunc, CancellationToken cancellationToken, bool debug) { if (pipeName == null) { return(new RejectedServerResponse()); } if (buildPaths.TempDirectory == null) { return(new RejectedServerResponse()); } var clientDir = buildPaths.ClientDirectory; var timeoutNewProcess = timeoutOverride ?? TimeOutMsNewProcess; var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess; var clientMutexName = MutexName.GetClientMutexName(pipeName); Task <Client> pipeTask = null; using (var clientMutex = new Mutex(initiallyOwned: true, name: clientMutexName, createdNew: out var holdsMutex)) { try { if (!holdsMutex) { try { holdsMutex = clientMutex.WaitOne(timeoutNewProcess); if (!holdsMutex) { return(new RejectedServerResponse()); } } catch (AbandonedMutexException) { holdsMutex = true; } } // Check for an already running server var serverMutexName = MutexName.GetServerMutexName(pipeName); var wasServerRunning = WasServerMutexOpen(serverMutexName); var timeout = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess; if (wasServerRunning || tryCreateServerFunc(clientDir, pipeName, debug)) { pipeTask = Client.ConnectAsync(pipeName, TimeSpan.FromMilliseconds(timeout), cancellationToken); } } finally { if (holdsMutex) { clientMutex.ReleaseMutex(); } } } if (pipeTask != null) { var client = await pipeTask.ConfigureAwait(false); if (client != null) { var request = ServerRequest.Create( buildPaths.WorkingDirectory, buildPaths.TempDirectory, arguments, keepAlive); return(await TryProcessRequest(client, request, cancellationToken).ConfigureAwait(false)); } } return(new RejectedServerResponse()); }
private static async Task <ServerResponse> RunOnServerCore( IList <string> arguments, ServerPaths serverPaths, string pipeName, string keepAlive, int?timeoutOverride, TryCreateServerCoreDelegate <string, string, int?, bool, bool> tryCreateServerFunc, CancellationToken cancellationToken, bool debug) { if (pipeName == null) { return(new RejectedServerResponse()); } if (serverPaths.TempDirectory == null) { return(new RejectedServerResponse()); } var clientDir = serverPaths.ClientDirectory; var timeoutNewProcess = timeoutOverride ?? TimeOutMsNewProcess; var timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess; var clientMutexName = MutexName.GetClientMutexName(pipeName); Task <Client> pipeTask = null; Mutex clientMutex = null; var holdsMutex = false; try { try { clientMutex = new Mutex(initiallyOwned: true, name: clientMutexName, createdNew: out holdsMutex); } catch (Exception ex) { // The Mutex constructor can throw in certain cases. One specific example is docker containers // where the /tmp directory is restricted. In those cases there is no reliable way to execute // the server and we need to fall back to the command line. // Example: https://github.com/dotnet/roslyn/issues/24124 ServerLogger.LogException(ex, "Client mutex creation failed."); return(new RejectedServerResponse()); } if (!holdsMutex) { try { holdsMutex = clientMutex.WaitOne(timeoutNewProcess); if (!holdsMutex) { return(new RejectedServerResponse()); } } catch (AbandonedMutexException) { holdsMutex = true; } } // Check for an already running server var serverMutexName = MutexName.GetServerMutexName(pipeName); var wasServerRunning = WasServerMutexOpen(serverMutexName); var timeout = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess; if (wasServerRunning || tryCreateServerFunc(clientDir, pipeName, out var _, debug)) { pipeTask = Client.ConnectAsync(pipeName, TimeSpan.FromMilliseconds(timeout), cancellationToken); } } finally { if (holdsMutex) { clientMutex?.ReleaseMutex(); } clientMutex?.Dispose(); } if (pipeTask != null) { var client = await pipeTask.ConfigureAwait(false); if (client != null) { var request = ServerRequest.Create( serverPaths.WorkingDirectory, serverPaths.TempDirectory, arguments, keepAlive); return(await TryProcessRequest(client, request, cancellationToken).ConfigureAwait(false)); } } return(new RejectedServerResponse()); }
private bool TryParseArguments(ServerRequest request, out (string workingDirectory, string tempDirectory, string[] args) parsed)
internal static async Task <int> SendShutdown(string pipeName) { var response = await Send(pipeName, ServerRequest.CreateShutdown()); return(((ShutdownServerResponse)response).ServerProcessId); }
public abstract ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken);
internal async Task <ConnectionResult> AcceptConnection(Task <Connection> task, bool accept, CancellationToken cancellationToken) { Connection connection; try { connection = await task; } catch (Exception ex) { // Unable to establish a connection with the client. The client is responsible for // handling this case. Nothing else for us to do here. ServerLogger.LogException(ex, "Error creating client named pipe"); return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted)); } try { using (connection) { ServerRequest request; try { ServerLogger.Log("Begin reading request."); request = await ServerRequest.ReadAsync(connection.Stream, cancellationToken).ConfigureAwait(false); ServerLogger.Log("End reading request."); } catch (Exception e) { ServerLogger.LogException(e, "Error reading build request."); return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted)); } if (request.IsShutdownRequest()) { // Reply with the PID of this process so that the client can wait for it to exit. var response = new ShutdownServerResponse(Process.GetCurrentProcess().Id); await response.WriteAsync(connection.Stream, cancellationToken); // We can safely disconnect the client, then when this connection gets cleaned up by the event loop // the server will go to a shutdown state. return(new ConnectionResult(ConnectionResult.Reason.ClientShutdownRequest)); } else if (!accept) { // We're already in shutdown mode, respond gracefully so the client can run in-process. var response = new RejectedServerResponse(); await response.WriteAsync(connection.Stream, cancellationToken).ConfigureAwait(false); return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted)); } else { // If we get here then this is a real request that we will accept and process. // // Kick off both the compilation and a task to monitor the pipe for closing. var buildCancelled = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var watcher = connection.WaitForDisconnectAsync(buildCancelled.Token); var worker = ExecuteRequestAsync(request, buildCancelled.Token); // await will end when either the work is complete or the connection is closed. await Task.WhenAny(worker, watcher); // Do an 'await' on the completed task, preference being compilation, to force // any exceptions to be realized in this method for logging. ConnectionResult.Reason reason; if (worker.IsCompleted) { var response = await worker; try { ServerLogger.Log("Begin writing response."); await response.WriteAsync(connection.Stream, cancellationToken); ServerLogger.Log("End writing response."); reason = ConnectionResult.Reason.CompilationCompleted; _eventBus.CompilationCompleted(); } catch { reason = ConnectionResult.Reason.ClientDisconnect; } } else { await watcher; reason = ConnectionResult.Reason.ClientDisconnect; } // Begin the tear down of the Task which didn't complete. buildCancelled.Cancel(); return(new ConnectionResult(reason, request.KeepAlive)); } } } catch (Exception ex) { ServerLogger.LogException(ex, "Error handling connection"); return(new ConnectionResult(ConnectionResult.Reason.ClientException)); } }