protected override void AddResponseBody(BinaryWriter writer) { writer.Write(this.ReturnCode); writer.Write(this.Utf8Output); BuildProtocolConstants.WriteLengthPrefixedString(writer, this.Output); BuildProtocolConstants.WriteLengthPrefixedString(writer, this.ErrorOutput); }
/// <summary> /// May throw exceptions if there are pipe problems. /// </summary> /// <param name="stream"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public static async Task <BuildResponse> ReadAsync(Stream stream, CancellationToken cancellationToken) { CompilerServerLogger.Log("Reading response length"); // Read the response length var lengthBuffer = new byte[4]; await BuildProtocolConstants.ReadAllAsync(stream, lengthBuffer, 4, cancellationToken).ConfigureAwait(false); var length = BitConverter.ToUInt32(lengthBuffer, 0); // Read the response CompilerServerLogger.Log("Reading response of length {0}", length); var responseBuffer = new byte[length]; await BuildProtocolConstants.ReadAllAsync(stream, responseBuffer, responseBuffer.Length, cancellationToken).ConfigureAwait(false); using (var reader = new BinaryReader(new MemoryStream(responseBuffer), Encoding.Unicode)) { var responseType = (ResponseType)reader.ReadInt32(); switch (responseType) { case ResponseType.Completed: return(CompletedBuildResponse.Create(reader)); case ResponseType.MismatchedVersion: return(MismatchedVersionBuildResponse.Create(reader)); default: throw new InvalidOperationException("Received invalid response type from server."); } } }
/// <summary> /// May throw exceptions if there are pipe problems. /// </summary> /// <param name="stream"></param> /// <param name="cancellationToken"></param> /// <returns></returns> public static async Task <BuildResponse> ReadAsync(PipeStream stream, CancellationToken cancellationToken) { CompilerServerLogger.Log("Reading response length"); // Read the response length var lengthBuffer = new byte[4]; await BuildProtocolConstants.ReadAllAsync(stream, lengthBuffer, 4, cancellationToken).ConfigureAwait(false); var length = BitConverter.ToUInt32(lengthBuffer, 0); // Read the response CompilerServerLogger.Log("Reading response of length {0}", length); var responseBuffer = new byte[length]; await BuildProtocolConstants.ReadAllAsync(stream, responseBuffer, responseBuffer.Length, cancellationToken).ConfigureAwait(false); using (var reader = new BinaryReader(new MemoryStream(responseBuffer), Encoding.Unicode)) { int returnCode = reader.ReadInt32(); string output = BuildProtocolConstants.ReadLengthPrefixedString(reader); string errorOutput = BuildProtocolConstants.ReadLengthPrefixedString(reader); return(new BuildResponse(returnCode, output, errorOutput)); } }
public static int Main(string[] args) { CompilerServerLogger.Initialize("SRV"); CompilerServerLogger.Log("Process started"); var keepAliveTimeout = GetKeepAliveTimeout(); // Pipename should be passed as the first and only argument to the server process // and it must have the form "-pipename:name". Otherwise, exit with a non-zero // exit code const string pipeArgPrefix = "-pipename:"; if (args.Length != 1 || args[0].Length <= pipeArgPrefix.Length || !args[0].StartsWith(pipeArgPrefix)) { return(CommonCompiler.Failed); } var pipeName = args[0].Substring(pipeArgPrefix.Length); var serverMutexName = BuildProtocolConstants.GetServerMutexName(pipeName); // VBCSCompiler is installed in the same directory as csc.exe and vbc.exe which is also the // location of the response files. var clientDirectory = AppDomain.CurrentDomain.BaseDirectory; var sdkDirectory = RuntimeEnvironment.GetRuntimeDirectory(); var compilerServerHost = new DesktopCompilerServerHost(clientDirectory, sdkDirectory); var clientConnectionHost = new NamedPipeClientConnectionHost(compilerServerHost, pipeName); return(Run(serverMutexName, clientConnectionHost, keepAliveTimeout)); }
public static Argument ReadFromBinaryReader(BinaryReader reader) { uint argId = reader.ReadUInt32(); uint argIndex = reader.ReadUInt32(); string value = BuildProtocolConstants.ReadLengthPrefixedString(reader); return(new Argument(argId, argIndex, value)); }
internal async Task <CompletionData> ProcessAsync( Task <IClientConnection> clientConnectionTask, bool allowCompilationRequests = true, CancellationToken cancellationToken = default) { try { return(await ProcessCore().ConfigureAwait(false)); } catch (Exception ex) { CompilerServerLogger.LogException(ex, $"Error processing request for client"); return(CompletionData.RequestError); } async Task <CompletionData> ProcessCore() { using var clientConnection = await clientConnectionTask.ConfigureAwait(false); var request = await clientConnection.ReadBuildRequestAsync(cancellationToken).ConfigureAwait(false); if (request.ProtocolVersion != BuildProtocolConstants.ProtocolVersion) { var response = new MismatchedVersionBuildResponse(); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); return(CompletionData.RequestCompleted); } if (!string.Equals(request.CompilerHash, BuildProtocolConstants.GetCommitHash(), StringComparison.OrdinalIgnoreCase)) { var response = new IncorrectHashBuildResponse(); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); return(CompletionData.RequestCompleted); } if (request.Arguments.Count == 1 && request.Arguments[0].ArgumentId == BuildProtocolConstants.ArgumentId.Shutdown) { var id = Process.GetCurrentProcess().Id; var response = new ShutdownBuildResponse(id); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); return(new CompletionData(CompletionReason.RequestCompleted, shutdownRequested: true)); } if (!allowCompilationRequests) { var response = new RejectedBuildResponse("Compilation not allowed at this time"); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); return(CompletionData.RequestCompleted); } return(await ProcessCompilationRequestAsync(clientConnection, request, cancellationToken).ConfigureAwait(false)); } }
public static CompletedBuildResponse Create(BinaryReader reader) { var returnCode = reader.ReadInt32(); var utf8Output = reader.ReadBoolean(); var output = BuildProtocolConstants.ReadLengthPrefixedString(reader); var errorOutput = BuildProtocolConstants.ReadLengthPrefixedString(reader); return(new CompletedBuildResponse(returnCode, utf8Output, output, errorOutput)); }
/// <summary> /// Read a Request from the given stream. /// /// The total request size must be less than 1MB. /// </summary> /// <returns>null if the Request was too large, the Request otherwise.</returns> public static async Task <BuildRequest> ReadAsync(Stream inStream, CancellationToken cancellationToken) { // Read the length of the request var lengthBuffer = new byte[4]; CompilerServerLogger.Log("Reading length of request"); await BuildProtocolConstants.ReadAllAsync(inStream, lengthBuffer, 4, cancellationToken).ConfigureAwait(false); var length = BitConverter.ToInt32(lengthBuffer, 0); // Back out if the request is > 1MB if (length > 0x100000) { CompilerServerLogger.Log("Request is over 1MB in length, cancelling read."); return(null); } cancellationToken.ThrowIfCancellationRequested(); // Read the full request var responseBuffer = new byte[length]; await BuildProtocolConstants.ReadAllAsync(inStream, responseBuffer, length, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); CompilerServerLogger.Log("Parsing request"); // Parse the request into the Request data structure. using (var reader = new BinaryReader(new MemoryStream(responseBuffer), Encoding.Unicode)) { var protocolVersion = reader.ReadUInt32(); var language = (BuildProtocolConstants.RequestLanguage)reader.ReadUInt32(); uint argumentCount = reader.ReadUInt32(); var argumentsBuilder = ImmutableArray.CreateBuilder <Argument>((int)argumentCount); for (int i = 0; i < argumentCount; i++) { cancellationToken.ThrowIfCancellationRequested(); argumentsBuilder.Add(BuildRequest.Argument.ReadFromBinaryReader(reader)); } return(new BuildRequest(protocolVersion, language, argumentsBuilder.ToImmutableArray())); } }
/// <summary> /// Main entry point for the process. Initialize the server dispatcher /// and wait for connections. /// </summary> public static int Main(string[] args) { CompilerServerLogger.Initialize("SRV"); CompilerServerLogger.Log("Process started"); TimeSpan?keepAliveTimeout = null; // VBCSCompiler is installed in the same directory as csc.exe and vbc.exe which is also the // location of the response files. var compilerExeDirectory = AppDomain.CurrentDomain.BaseDirectory; try { int keepAliveValue; string keepAliveStr = ConfigurationManager.AppSettings["keepalive"]; if (int.TryParse(keepAliveStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out keepAliveValue) && keepAliveValue >= 0) { if (keepAliveValue == 0) { // This is a one time server entry. keepAliveTimeout = null; } else { keepAliveTimeout = TimeSpan.FromSeconds(keepAliveValue); } } else { keepAliveTimeout = s_defaultServerKeepAlive; } } catch (ConfigurationErrorsException e) { keepAliveTimeout = s_defaultServerKeepAlive; CompilerServerLogger.LogException(e, "Could not read AppSettings"); } CompilerServerLogger.Log("Keep alive timeout is: {0} milliseconds.", keepAliveTimeout?.TotalMilliseconds ?? 0); FatalError.Handler = FailFast.OnFatalException; var dispatcher = new ServerDispatcher(new CompilerRequestHandler(compilerExeDirectory), new EmptyDiagnosticListener()); dispatcher.ListenAndDispatchConnections( BuildProtocolConstants.GetPipeName(compilerExeDirectory), keepAliveTimeout, watchAnalyzerFiles: true); return(0); }
public BuildRequest(uint protocolVersion, BuildProtocolConstants.RequestLanguage language, ImmutableArray<Argument> arguments) { ProtocolVersion = protocolVersion; Language = language; if (arguments.Length > ushort.MaxValue) { throw new ArgumentOutOfRangeException(nameof(arguments), "Too many arguments: maximum of " + ushort.MaxValue + " arguments allowed."); } Arguments = arguments; }
public async Task <ConnectionData> HandleConnection(bool allowCompilationRequests = true, CancellationToken cancellationToken = default(CancellationToken)) { try { BuildRequest request; try { Log("Begin reading request."); request = await BuildRequest.ReadAsync(_stream, cancellationToken).ConfigureAwait(false); ValidateBuildRequest(request); Log("End reading request."); } catch (Exception e) { LogException(e, "Error reading build request."); return(new ConnectionData(CompletionReason.CompilationNotStarted)); } if (request.ProtocolVersion != BuildProtocolConstants.ProtocolVersion) { return(await HandleMismatchedVersionRequest(cancellationToken).ConfigureAwait(false)); } else if (!string.Equals(request.CompilerHash, BuildProtocolConstants.GetCommitHash(), StringComparison.OrdinalIgnoreCase)) { return(await HandleIncorrectHashRequest(cancellationToken).ConfigureAwait(false)); } else if (IsShutdownRequest(request)) { return(await HandleShutdownRequest(cancellationToken).ConfigureAwait(false)); } else if (!allowCompilationRequests) { return(await HandleRejectedRequest(cancellationToken).ConfigureAwait(false)); } else { return(await HandleCompilationRequest(request, cancellationToken).ConfigureAwait(false)); } } finally { Close(); } }
internal static int RunServer(string pipeName, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrEmpty(pipeName)) { return(CommonCompiler.Failed); } var keepAliveTimeout = GetKeepAliveTimeout(); var serverMutexName = BuildProtocolConstants.GetServerMutexName(pipeName); // VBCSCompiler is installed in the same directory as csc.exe and vbc.exe which is also the // location of the response files. var clientDirectory = AppDomain.CurrentDomain.BaseDirectory; var sdkDirectory = RuntimeEnvironment.GetRuntimeDirectory(); var compilerServerHost = new DesktopCompilerServerHost(clientDirectory, sdkDirectory); var clientConnectionHost = new NamedPipeClientConnectionHost(compilerServerHost, pipeName); return(Run(serverMutexName, clientConnectionHost, keepAliveTimeout, cancellationToken)); }
public async Task WriteAsync(PipeStream outStream, CancellationToken cancellationToken) { using (var writer = new BinaryWriter(new MemoryStream(), Encoding.Unicode)) { // Format the response CompilerServerLogger.Log("Formatting Response"); writer.Write(this.ReturnCode); BuildProtocolConstants.WriteLengthPrefixedString(writer, this.Output); BuildProtocolConstants.WriteLengthPrefixedString(writer, this.ErrorOutput); writer.Flush(); cancellationToken.ThrowIfCancellationRequested(); // Send the response to the client // Grab the MemoryStream and its internal buffer to prevent // making another copy. var stream = (MemoryStream)writer.BaseStream; // Write the length of the response uint length = (uint)stream.Length; CompilerServerLogger.Log("Writing response length"); // There is no way to know the number of bytes written to // the pipe stream. We just have to assume all of them are written. await outStream.WriteAsync(BitConverter.GetBytes(length), 0, 4, cancellationToken).ConfigureAwait(false); // Write the response CompilerServerLogger.Log("Writing response of size {0}", length); // There is no way to know the number of bytes written to // the pipe stream. We just have to assume all of them are written. await outStream.WriteAsync(stream.GetBuffer(), 0, (int)length, cancellationToken).ConfigureAwait(false); } }
public Argument(BuildProtocolConstants.ArgumentId argumentId, uint argumentIndex, string value) { this.ArgumentId = argumentId; this.ArgumentIndex = argumentIndex; this.Value = value; }
public void WriteToBinaryWriter(BinaryWriter writer) { writer.Write(this.ArgumentId); writer.Write(this.ArgumentIndex); BuildProtocolConstants.WriteLengthPrefixedString(writer, this.Value); }
/// <summary> /// Shutting down the server is an inherently racy operation. The server can be started or stopped by /// external parties at any time. /// /// This function will return success if at any time in the function the server is determined to no longer /// be running. /// </summary> internal static async Task <int> RunShutdownAsync(string pipeName, bool waitForProcess = true, TimeSpan?timeout = null, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrEmpty(pipeName)) { var clientDirectory = AppDomain.CurrentDomain.BaseDirectory; pipeName = DesktopBuildClient.GetPipeNameFromFileInfo(clientDirectory); } var mutexName = BuildProtocolConstants.GetServerMutexName(pipeName); if (!DesktopBuildClient.WasServerMutexOpen(mutexName)) { // The server holds the mutex whenever it is running, if it's not open then the // server simply isn't running. return(CommonCompiler.Succeeded); } try { using (var client = new NamedPipeClientStream(pipeName)) { var realTimeout = timeout != null ? (int)timeout.Value.TotalMilliseconds : Timeout.Infinite; client.Connect(realTimeout); var request = BuildRequest.CreateShutdown(); await request.WriteAsync(client, cancellationToken).ConfigureAwait(false); var response = await BuildResponse.ReadAsync(client, cancellationToken).ConfigureAwait(false); var shutdownResponse = (ShutdownBuildResponse)response; if (waitForProcess) { try { var process = Process.GetProcessById(shutdownResponse.ServerProcessId); process.WaitForExit(); } catch (Exception) { // 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 succeed. } } } return(CommonCompiler.Succeeded); } catch (Exception) { if (!DesktopBuildClient.WasServerMutexOpen(mutexName)) { // If the server was in the process of shutting down when we connected then it's reasonable // for an exception to happen. If the mutex has shutdown at this point then the server // is shut down. return(CommonCompiler.Succeeded); } return(CommonCompiler.Failed); } }
/// <summary> /// Handles a client connection. The returned task here will never fail. Instead all exceptions will be wrapped /// in a <see cref="CompletionReason.RequestError"/> /// </summary> internal async Task <CompletionData> ProcessAsync( Task <IClientConnection> clientConnectionTask, bool allowCompilationRequests = true, CancellationToken cancellationToken = default) { try { return(await ProcessCore().ConfigureAwait(false)); } catch (Exception ex) { Logger.LogException(ex, $"Error processing request for client"); return(CompletionData.RequestError); } async Task <CompletionData> ProcessCore() { using var clientConnection = await clientConnectionTask.ConfigureAwait(false); var request = await clientConnection.ReadBuildRequestAsync(cancellationToken).ConfigureAwait(false); Logger.Log($"Received request {request.RequestId} of type {request.GetType()}"); if (!string.Equals(request.CompilerHash, BuildProtocolConstants.GetCommitHash(), StringComparison.OrdinalIgnoreCase)) { return(await WriteBuildResponseAsync( clientConnection, request.RequestId, new IncorrectHashBuildResponse(), CompletionData.RequestError, cancellationToken).ConfigureAwait(false)); } if (request.Arguments.Count == 1 && request.Arguments[0].ArgumentId == BuildProtocolConstants.ArgumentId.Shutdown) { return(await WriteBuildResponseAsync( clientConnection, request.RequestId, new ShutdownBuildResponse(Process.GetCurrentProcess().Id), new CompletionData(CompletionReason.RequestCompleted, shutdownRequested : true), cancellationToken).ConfigureAwait(false)); } if (!allowCompilationRequests) { return(await WriteBuildResponseAsync( clientConnection, request.RequestId, new RejectedBuildResponse("Compilation not allowed at this time"), CompletionData.RequestCompleted, cancellationToken).ConfigureAwait(false)); } if (!Environment.Is64BitProcess && !MemoryHelper.IsMemoryAvailable(Logger)) { return(await WriteBuildResponseAsync( clientConnection, request.RequestId, new RejectedBuildResponse("Not enough resources to accept connection"), CompletionData.RequestError, cancellationToken).ConfigureAwait(false)); } return(await ProcessCompilationRequestAsync(clientConnection, request, cancellationToken).ConfigureAwait(false)); } }