/// <summary> /// Invoke the VB compiler with the given arguments and current directory, and send output and error /// to the given TextWriters. /// </summary> private int BasicCompile( string responseFileDirectory, string currentDirectory, string libDirectory, string tempPath, string[] commandLineArguments, TextWriter output, CancellationToken cancellationToken, out bool utf8output) { CompilerServerLogger.Log("CurrentDirectory = '{0}'", currentDirectory); CompilerServerLogger.Log("LIB = '{0}'", libDirectory); for (int i = 0; i < commandLineArguments.Length; ++i) { CompilerServerLogger.Log("Argument[{0}] = '{1}'", i, commandLineArguments[i]); } return(VisualBasicCompilerServer.RunCompiler( responseFileDirectory, commandLineArguments, currentDirectory, libDirectory, tempPath, output, cancellationToken, out utf8output)); }
public static bool IsMemoryAvailable() { if (!PlatformInformation.IsWindows) { // assume we have enough memory on non-Windows machines return(true); } MemoryHelper status = new MemoryHelper(); GlobalMemoryStatusEx(status); ulong max = status.MaxVirtual; ulong free = status.AvailableVirtual; int shift = 20; string unit = "MB"; if (free >> shift == 0) { shift = 10; unit = "KB"; } CompilerServerLogger.Log("Free memory: {1}{0} of {2}{0}.", unit, free >> shift, max >> shift); return(free >= 800 << 20); // Value (500MB) is arbitrary; feel free to improve. }
/// <summary> /// The IsConnected property on named pipes does not detect when the client has disconnected /// if we don't attempt any new I/O after the client disconnects. We start an async I/O here /// which serves to check the pipe for disconnection. /// /// This will return true if the pipe was disconnected. /// </summary> private async Task <bool> CreateMonitorDisconnectTaskCore(CancellationToken cancellationToken) { var buffer = SpecializedCollections.EmptyBytes; while (!cancellationToken.IsCancellationRequested && _pipeStream.IsConnected) { // Wait a second before trying again await Task.Delay(1000, cancellationToken).ConfigureAwait(false); try { CompilerServerLogger.Log("Pipe {0}: Before poking pipe.", _loggingIdentifier); await _pipeStream.ReadAsync(buffer, 0, 0, cancellationToken).ConfigureAwait(false); CompilerServerLogger.Log("Pipe {0}: After poking pipe.", _loggingIdentifier); } catch (Exception e) { // It is okay for this call to fail. Errors will be reflected in the // IsConnected property which will be read on the next iteration of the // loop var msg = string.Format("Pipe {0}: Error poking pipe.", _loggingIdentifier); CompilerServerLogger.LogException(e, msg); } } return(!_pipeStream.IsConnected); }
/// <summary> /// This task does not complete until we are completely done reading. /// </summary> internal static async Task ReadAllAsync( PipeStream stream, byte[] buffer, int count, CancellationToken cancellationToken) { int totalBytesRead = 0; do { CompilerServerLogger.Log("Attempting to read {0} bytes from the stream", count - totalBytesRead); int bytesRead = await stream.ReadAsync(buffer, totalBytesRead, count - totalBytesRead, cancellationToken).ConfigureAwait(false); if (bytesRead == 0) { CompilerServerLogger.Log("Unexpected -- read 0 bytes from the stream."); throw new EndOfStreamException("Reached end of stream before end of read."); } CompilerServerLogger.Log("Read {0} bytes", bytesRead); totalBytesRead += bytesRead; } while (totalBytesRead < count); CompilerServerLogger.Log("Finished read"); }
private static async Task ListenCoreAsync( string pipeName, AsyncQueue <ListenResult> queue, Func <string> getClientLoggingIdentifier, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { NamedPipeServerStream?pipeStream = null; try { // Create the pipe and begin waiting for a connection. This // doesn't block, but could fail in certain circumstances, such // as Windows refusing to create the pipe for some reason // (out of handles?), or the pipe was disconnected before we // starting listening CompilerServerLogger.Log($"Constructing pipe and waiting for connections '{pipeName}'"); pipeStream = NamedPipeUtil.CreateServer(pipeName); // The WaitForConnectionAsync API does not fully respect the provided CancellationToken // on all platforms: // // https://github.com/dotnet/runtime/issues/40289 // // To mitigate this we need to setup a cancellation Task and dispose the NamedPipeServerStream // if it ever completes. Once all of the NamedPipeServerStream for the given pipe name are // disposed they will all exit the WaitForConnectionAsync method var connectTask = pipeStream.WaitForConnectionAsync(cancellationToken); if (!PlatformInformation.IsWindows) { var cancelTask = Task.Delay(TimeSpan.FromMilliseconds(-1), cancellationToken); var completedTask = await Task.WhenAny(new[] { connectTask, cancelTask }).ConfigureAwait(false); if (completedTask == cancelTask) { throw new OperationCanceledException(); } } await connectTask.ConfigureAwait(false); CompilerServerLogger.Log("Pipe connection established."); var connection = new NamedPipeClientConnection(pipeStream, getClientLoggingIdentifier()); queue.Enqueue(new ListenResult(connection: connection)); } catch (OperationCanceledException) { // Expected when the host is shutting down. CompilerServerLogger.Log($"Pipe connection cancelled"); pipeStream?.Dispose(); } catch (Exception ex) { CompilerServerLogger.LogException(ex, $"Pipe connection error"); queue.Enqueue(new ListenResult(exception: ex)); pipeStream?.Dispose(); } } }
/// <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> /// Cancel whatever is being done. This abandons any compilation that is in process. /// We do this only if the client connect is cancelled. For example, this occurs if the /// client presses Ctrl+C on a command line compile or MSBuild decides to cancel a build. /// We want to cancel the compilation process quickly. /// </summary> private void Cancel() { CompilerServerLogger.Log("Cancellation requested."); if (this.isFinished) { Log("Connection already finished."); } else { // CONSIDER: If we want to be more aggressive, we could wait a small timeout for the // cancel to finish, and if the thread hasn't terminated, then call thread.Abort(). // This would give up CPU resources more quickly in case, say, a compilation is taking a very // long time and isn't checking the cancellation token often enough (say it enters an // infinite loop because of a bug). // // I don't think this is strictly needed, though, because once we mark the connection as // finished (by called FinishConnection), the compilation process is free to go away, // so the process will be terminated anyway once the process timeout expires (around 30 seconds), // no matter if the thread dies or not. Since ThreadAbortExceptions make me a little nervous, // I've chosen not to go this route for now. // // If we decide to extend the process timeout significantly, and the compiler isn't // responsive to cancellation requests, then using thread.Abort() might be necessary, because // people will look askance at the compiler burning lots of CPU after the compilation is cancelled. Log("Setting cancellation token to cancelled state."); cancellationTokenSource.Cancel(); FinishConnection(CompletionReason.Cancelled); } }
/// <summary> /// Create an instance of the pipe. This might be the first instance, or a subsequent instance. /// There always needs to be an instance of the pipe created to listen for a new client connection. /// </summary> /// <returns>The pipe instance or throws an exception.</returns> private NamedPipeServerStream ConstructPipe(string pipeName) { CompilerServerLogger.Log("Constructing pipe '{0}'.", pipeName); SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; PipeSecurity security = new PipeSecurity(); // Restrict access to just this account. PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite | PipeAccessRights.CreateNewInstance, AccessControlType.Allow); security.AddAccessRule(rule); security.SetOwner(identifier); NamedPipeServerStream pipeStream = new NamedPipeServerStream( pipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, // Maximum connections. PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.WriteThrough, PipeBufferSize, // Default input buffer PipeBufferSize, // Default output buffer security, HandleInheritability.None); CompilerServerLogger.Log("Successfully constructed pipe '{0}'.", pipeName); return(pipeStream); }
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)); }
/// <summary> /// The IsConnected property on named pipes does not detect when the client has disconnected /// if we don't attempt any new I/O after the client disconnects. We start an async I/O here /// which serves to check the pipe for disconnection. /// /// This will return true if the pipe was disconnected. /// </summary> protected override async Task CreateMonitorDisconnectTask(CancellationToken cancellationToken) { var buffer = Array.Empty <byte>(); while (!cancellationToken.IsCancellationRequested && _pipeStream.IsConnected) { // Wait a second before trying again await Task.Delay(1000, cancellationToken).ConfigureAwait(false); try { CompilerServerLogger.Log($"Pipe {LoggingIdentifier}: Before poking pipe."); await _pipeStream.ReadAsync(buffer, 0, 0, cancellationToken).ConfigureAwait(false); CompilerServerLogger.Log($"Pipe {LoggingIdentifier}: After poking pipe."); } catch (Exception e) { // It is okay for this call to fail. Errors will be reflected in the // IsConnected property which will be read on the next iteration of the // loop var msg = string.Format($"Pipe {LoggingIdentifier}: Error poking pipe."); CompilerServerLogger.LogException(e, msg); } } }
internal Metadata GetMetadata(string fullPath, MetadataReferenceProperties properties) { // Check if we have an entry in the dictionary. FileKey?fileKey = GetUniqueFileKey(fullPath); Metadata metadata; if (fileKey.HasValue && metadataCache.TryGetValue(fileKey.Value, out metadata) && metadata != null) { CompilerServerLogger.Log("Using already loaded metadata for assembly reference '{0}'", fileKey); return(metadata); } if (properties.Kind == MetadataImageKind.Module) { var result = CreateModuleMetadata(fullPath, prefetchEntireImage: true); //?? never add modules to cache? return(result); } else { var primaryModule = CreateModuleMetadata(fullPath, prefetchEntireImage: false); // Get all the modules, and load them. Create an assembly metadata. var allModules = GetAllModules(primaryModule, Path.GetDirectoryName(fullPath)); Metadata result = AssemblyMetadata.Create(allModules); result = metadataCache.GetOrAdd(fileKey.Value, result); return(result); } }
/// <summary> /// Creates a Task that waits for a client connection to occur and returns the connected /// <see cref="NamedPipeServerStream"/> object. Throws on any connection error. /// </summary> /// <param name="cancellationToken">Used to cancel the connection sequence.</param> private async Task <NamedPipeServerStream> CreateListenTaskCore(CancellationToken cancellationToken) { // Create the pipe and begin waiting for a connection. This // doesn't block, but could fail in certain circumstances, such // as Windows refusing to create the pipe for some reason // (out of handles?), or the pipe was disconnected before we // starting listening. CompilerServerLogger.Log("Constructing pipe '{0}'.", _pipeName); var pipeStream = NamedPipeUtil.CreateServer(_pipeName); CompilerServerLogger.Log("Successfully constructed pipe '{0}'.", _pipeName); CompilerServerLogger.Log("Waiting for new connection"); await pipeStream.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false); CompilerServerLogger.Log("Pipe connection detected."); if (Environment.Is64BitProcess || MemoryHelper.IsMemoryAvailable()) { CompilerServerLogger.Log("Memory available - accepting connection"); return(pipeStream); } pipeStream.Close(); throw new Exception("Insufficient resources to process new connection."); }
/// <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)); } }
/// <summary> /// Invoke the C# compiler with the given arguments and current directory, and send output and error /// to the given TextWriters. /// </summary> private int CSharpCompile( string currentDirectory, string libDirectory, string responseFileDirectory, string[] commandLineArguments, TextWriter output, CancellationToken cancellationToken, out bool utf8output) { CompilerServerLogger.Log("CurrentDirectory = '{0}'", currentDirectory); CompilerServerLogger.Log("LIB = '{0}'", libDirectory); for (int i = 0; i < commandLineArguments.Length; ++i) { CompilerServerLogger.Log("Argument[{0}] = '{1}'", i, commandLineArguments[i]); } return(CSharpCompilerServer.RunCompiler( responseFileDirectory, commandLineArguments, currentDirectory, RuntimeEnvironment.GetRuntimeDirectory(), libDirectory, AnalyzerLoader, output, cancellationToken, out utf8output)); }
internal int RunServer( string pipeName, string tempPath, IClientConnectionHost clientConnectionHost = null, IDiagnosticListener listener = null, TimeSpan?keepAlive = null, CancellationToken cancellationToken = default) { keepAlive ??= GetKeepAliveTimeout(); listener ??= new EmptyDiagnosticListener(); clientConnectionHost ??= CreateClientConnectionHost(pipeName); // Grab the server mutex to prevent multiple servers from starting with the same // pipename and consuming excess resources. If someone else holds the mutex // exit immediately with a non-zero exit code var mutexName = BuildServerConnection.GetServerMutexName(pipeName); bool createdNew; using (var serverMutex = BuildServerConnection.OpenOrCreateMutex(name: mutexName, createdNew: out createdNew)) { if (!createdNew) { return(CommonCompiler.Failed); } CompilerServerLogger.Log("Keep alive timeout is: {0} milliseconds.", keepAlive?.TotalMilliseconds ?? 0); FatalError.Handler = FailFast.OnFatalException; var dispatcher = new ServerDispatcher(clientConnectionHost, listener); dispatcher.ListenAndDispatchConnections(keepAlive, cancellationToken); return(CommonCompiler.Succeeded); } }
/// <summary> /// An incoming request as occurred. This is called on a new thread to handle /// the request. /// </summary> public BuildResponse HandleRequest(BuildRequest req, CancellationToken cancellationToken) { switch (req.Id) { case BuildProtocolConstants.RequestId_CSharpCompile: CompilerServerLogger.Log("Request to compile C#"); return(CSharpCompile(req, cancellationToken)); case BuildProtocolConstants.RequestId_VisualBasicCompile: CompilerServerLogger.Log("Request to compile VB"); return(BasicCompile(req, cancellationToken)); case BuildProtocolConstants.RequestId_Analyze: CompilerServerLogger.Log("Request to analyze managed code"); return(Analyze(req, cancellationToken)); default: CompilerServerLogger.Log("Got request with id '{0}'", req.Id); for (int i = 0; i < req.Arguments.Length; ++i) { CompilerServerLogger.Log("Request argument '{0}[{1}]' = '{2}'", req.Arguments[i].ArgumentId, req.Arguments[i].ArgumentIndex, req.Arguments[i].Value); } // We can't do anything with a request we don't know about. return(new BuildResponse(0, "", "")); } }
/// <summary> /// An incoming request as occurred. This is called on a new thread to handle /// the request. /// </summary> public BuildResponse HandleRequest(BuildRequest req, CancellationToken cancellationToken) { switch (req.Language) { case BuildProtocolConstants.RequestLanguage.CSharpCompile: CompilerServerLogger.Log("Request to compile C#"); return(CSharpCompile(req, cancellationToken)); case BuildProtocolConstants.RequestLanguage.VisualBasicCompile: CompilerServerLogger.Log("Request to compile VB"); return(BasicCompile(req, cancellationToken)); default: CompilerServerLogger.Log("Got request with id '{0}'", req.Language); for (int i = 0; i < req.Arguments.Length; ++i) { CompilerServerLogger.Log("Request argument '{0}[{1}]' = '{2}'", req.Arguments[i].ArgumentId, req.Arguments[i].ArgumentIndex, req.Arguments[i].Value); } // We can't do anything with a request we don't know about. return(new CompletedBuildResponse(-1, utf8output: false, output: "", errorOutput: "")); } }
/// <summary> /// Checks the completed connection objects and updates the server state based on their /// results. /// </summary> private void HandleCompletedConnections() { var shutdown = false; var processedCount = 0; var i = 0; while (i < _connectionList.Count) { var current = _connectionList[i]; if (!current.IsCompleted) { i++; continue; } _connectionList.RemoveAt(i); processedCount++; var completionData = current.Result; switch (completionData.Reason) { case CompletionReason.RequestCompleted: CompilerServerLogger.Log("Client request completed"); if (completionData.NewKeepAlive is { } keepAlive) { CompilerServerLogger.Log($"Client changed keep alive to {keepAlive}"); ChangeKeepAlive(keepAlive); } if (completionData.ShutdownRequest) { CompilerServerLogger.Log("Client requested shutdown"); shutdown = true; } // These are all normal shutdown states. Nothing to do here. break; case CompletionReason.RequestError: CompilerServerLogger.LogError("Client request failed"); shutdown = true; break; default: CompilerServerLogger.LogError("Unexpected enum value"); shutdown = true; break; } _diagnosticListener.ConnectionCompleted(completionData); } if (shutdown) { CompilerServerLogger.Log($"Shutting down server"); _state = State.ShuttingDown; } }
/// <summary> /// Checks the completed connection objects. /// </summary> /// <returns>False if the server needs to begin shutting down</returns> private void HandleCompletedConnections() { var shutdown = false; var processedCount = 0; var i = 0; while (i < _connectionList.Count) { var current = _connectionList[i]; if (!current.IsCompleted) { i++; continue; } _connectionList.RemoveAt(i); processedCount++; var connectionData = current.Result; ChangeKeepAlive(connectionData.KeepAlive); switch (connectionData.CompletionReason) { case CompletionReason.CompilationCompleted: case CompletionReason.CompilationNotStarted: CompilerServerLogger.Log("Client completed"); // 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. CompilerServerLogger.LogError("Unexpected client disconnect. Shutting down server"); shutdown = true; break; case CompletionReason.ClientException: CompilerServerLogger.LogError($"Unexpected client exception. Shutting down server"); shutdown = true; break; case CompletionReason.ClientShutdownRequest: CompilerServerLogger.Log($"Client requesting server shutdown"); shutdown = true; break; default: throw new InvalidOperationException($"Unexpected enum value {connectionData.CompletionReason}"); } _diagnosticListener.ConnectionCompleted(connectionData.CompletionReason); } if (shutdown) { CompilerServerLogger.Log($"Shutting down server"); _state = State.ShuttingDown; } }
private void HandleCompletedTimeoutTask() { CompilerServerLogger.Log("Timeout triggered. Shutting down server."); _diagnosticListener.KeepAliveReached(); _listenCancellationTokenSource.Cancel(); _timeoutTask = null; _state = State.ShuttingDown; }
/// <summary> /// Checks the completed connection objects and updates the server state based on their /// results. /// </summary> private void HandleCompletedConnections() { var shutdown = false; var i = 0; while (i < _connectionList.Count) { var current = _connectionList[i]; if (!current.IsCompleted) { i++; continue; } _connectionList.RemoveAt(i); // These task should never fail. Unexpected errors will be caught and translated into // a RequestError message Debug.Assert(current.Status == TaskStatus.RanToCompletion); var completionData = current.Result; switch (completionData.Reason) { case CompletionReason.RequestCompleted: CompilerServerLogger.Log("Client request completed"); if (completionData.NewKeepAlive is { } keepAlive) { CompilerServerLogger.Log($"Client changed keep alive to {keepAlive}"); ChangeKeepAlive(keepAlive); } if (completionData.ShutdownRequest) { CompilerServerLogger.Log("Client requested shutdown"); shutdown = true; } break; case CompletionReason.RequestError: CompilerServerLogger.LogError("Client request failed"); shutdown = true; break; default: CompilerServerLogger.LogError("Unexpected enum value"); shutdown = true; break; } _diagnosticListener.ConnectionCompleted(completionData); } if (shutdown) { ChangeToShuttingDown("Error handling client connection"); } }
public override int Run(TextWriter consoleOutput, CancellationToken cancellationToken) { int runResult; CompilerServerLogger.Log("****Running VB compiler..."); runResult = base.Run(consoleOutput, cancellationToken); CompilerServerLogger.Log("****VB Compilation complete.\r\n****Return code: {0}\r\n****Output:\r\n{1}\r\n", runResult, consoleOutput.ToString()); return(runResult); }
public override int Run(TextWriter consoleOutput, CancellationToken cancellationToken = default(CancellationToken)) { int returnCode; CompilerServerLogger.Log("****Running C# compiler..."); returnCode = base.Run(consoleOutput, cancellationToken); CompilerServerLogger.Log("****C# Compilation complete.\r\n****Return code: {0}\r\n****Output:\r\n{1}\r\n", returnCode, consoleOutput.ToString()); return(returnCode); }
protected virtual int RunServerCore(string pipeName, IClientConnectionHost connectionHost, IDiagnosticListener listener, TimeSpan?keepAlive, CancellationToken cancellationToken) { CompilerServerLogger.Log("Keep alive timeout is: {0} milliseconds.", keepAlive?.TotalMilliseconds ?? 0); FatalError.Handler = FailFast.OnFatalException; var dispatcher = new ServerDispatcher(connectionHost, listener); dispatcher.ListenAndDispatchConnections(keepAlive, cancellationToken); return(CommonCompiler.Succeeded); }
private static int RunCore(IClientConnectionHost connectionHost, TimeSpan?keepAliveTimeout) { CompilerServerLogger.Log("Keep alive timeout is: {0} milliseconds.", keepAliveTimeout?.TotalMilliseconds ?? 0); FatalError.Handler = FailFast.OnFatalException; var dispatcher = new ServerDispatcher(connectionHost, new EmptyDiagnosticListener()); dispatcher.ListenAndDispatchConnections(keepAliveTimeout); return(CommonCompiler.Succeeded); }
/// <summary> /// Called from the Connection class when the connection is complete. /// </summary> private void ConnectionCompleted() { Interlocked.Decrement(ref this.activeConnectionCount); if (this.activeConnectionCount == 0) { Quiet(); } CompilerServerLogger.Log("Removed connection, {0} remaining.", this.activeConnectionCount); }
/// <summary> /// Invoke the C# compiler with the given arguments and current directory, and send output and error /// to the given TextWriters. /// </summary> private int CSharpCompile(string currentDirectory, string libDirectory, string[] commandLineArguments, TextWriter output, CancellationToken cancellationToken) { CompilerServerLogger.Log("CurrentDirectory = '{0}'", currentDirectory); CompilerServerLogger.Log("LIB = '{0}'", libDirectory); for (int i = 0; i < commandLineArguments.Length; ++i) { CompilerServerLogger.Log("Argument[{0}] = '{1}'", i, commandLineArguments[i]); } return(CSharpCompilerServer.RunCompiler(commandLineArguments, currentDirectory, libDirectory, output, cancellationToken)); }
/// <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())); } }
private static async Task <CompletionData> WriteBuildResponseAsync(IClientConnection clientConnection, BuildResponse response, CompletionData completionData, CancellationToken cancellationToken) { var message = response switch { RejectedBuildResponse r => $"Writing {r.Type} response '{r.Reason}' for {clientConnection.LoggingIdentifier}", _ => $"Writing {response.Type} response for {clientConnection.LoggingIdentifier}" }; CompilerServerLogger.Log(message); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); return(completionData); }
/// <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); }