public void ListenAndDispatchConnections(TimeSpan?keepAlive, CancellationToken cancellationToken = default) { _state = State.Running; _keepAlive = keepAlive; _keepAliveIsDefault = true; try { _clientConnectionHost.BeginListening(); ListenAndDispatchConnectionsCore(cancellationToken); } finally { _state = State.Completed; _gcTask = null; _timeoutTask = null; if (_clientConnectionHost.IsListening) { _clientConnectionHost.EndListening(); } // This type is responsible for cleaning up resources associated with _listenTask. Once EndListening // is complete this task is guaranteed to be completed. If it ran to completion we need to // dispose of the value. Console.WriteLine(_listenTask?.Status); Debug.Assert(_listenTask is null || _listenTask.IsCompleted); if (_listenTask?.Status == TaskStatus.RanToCompletion) { try { _listenTask.Result.Dispose(); } catch (Exception ex) { CompilerServerLogger.LogException(ex, $"Error disposing of {nameof(_listenTask)}"); } } } CompilerServerLogger.Log($"End ListenAndDispatchConnections"); }
public static int Main(string[] args) { using var logger = new CompilerServerLogger(); NameValueCollection appSettings; try { #if BOOTSTRAP ExitingTraceListener.Install(); #endif #if NET472 appSettings = System.Configuration.ConfigurationManager.AppSettings; #else // Do not use AppSettings on non-desktop platforms appSettings = new NameValueCollection(); #endif } catch (Exception ex) { // It is possible for AppSettings to throw when the application or machine configuration // is corrupted. This should not prevent the server from starting, but instead just revert // to the default configuration. appSettings = new NameValueCollection(); logger.LogException(ex, "Error loading application settings"); } try { var controller = new BuildServerController(appSettings, logger); return(controller.Run(args)); } catch (Exception e) { // Assume the exception was the result of a missing compiler assembly. logger.LogException(e, "Cannot start server"); } return(CommonCompiler.Failed); }
public static int Main(string[] args) { NameValueCollection appSettings; try { appSettings = ConfigurationManager.AppSettings; } catch (Exception ex) { // It is possible for AppSettings to throw when the application or machine configuration // is corrupted. This should not prevent the server from starting, but instead just revert // to the default configuration. appSettings = new NameValueCollection(); CompilerServerLogger.LogException(ex, "Error loading application settings"); } var controller = new DesktopBuildServerController(appSettings); return(controller.Run(args)); }
/// <summary> /// Does the client of "pipeStream" have the same identity and elevation as we do? /// </summary> private static bool ClientAndOurIdentitiesMatch(NamedPipeServerStream pipeStream) { if (PlatformInformation.IsWindows) { var serverIdentity = GetIdentity(impersonating: false); (string name, bool admin)clientIdentity = default; pipeStream.RunAsClient(() => { clientIdentity = GetIdentity(impersonating: true); }); CompilerServerLogger.Log($"Server identity = '{serverIdentity.name}', server elevation='{serverIdentity.admin}'."); CompilerServerLogger.Log($"Client identity = '{clientIdentity.name}', client elevation='{serverIdentity.admin}'."); return (StringComparer.OrdinalIgnoreCase.Equals(serverIdentity.name, clientIdentity.name) && serverIdentity.admin == clientIdentity.admin); } else { return(BuildServerConnection.CheckIdentityUnix(pipeStream)); } }
public static int Main(string[] args) { CompilerServerLogger.Initialize("SRV"); CompilerServerLogger.Log("Process started"); string pipeName; bool shutdown; if (!ParseCommandLine(args, out pipeName, out shutdown)) { return(CommonCompiler.Failed); } var cancellationTokenSource = new CancellationTokenSource(); Console.CancelKeyPress += (sender, e) => { cancellationTokenSource.Cancel(); }; return(shutdown ? RunShutdown(pipeName, cancellationToken: cancellationTokenSource.Token) : RunServer(pipeName, cancellationToken: cancellationTokenSource.Token)); }
public ulong Reserved; //always 0 public static bool IsMemoryAvailable() { 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> /// Creates a Task representing the processing of the new connection. This will return a task that /// will never fail. It will always produce a <see cref="ConnectionData"/> value. Connection errors /// will end up being represented as <see cref="CompletionReason.ClientDisconnect"/> /// </summary> internal static async Task <ConnectionData> CreateHandleConnectionTask(Task <NamedPipeServerStream> pipeStreamTask, IRequestHandler handler, CancellationToken cancellationToken) { Connection connection; try { var pipeStream = await pipeStreamTask.ConfigureAwait(false); var clientConnection = new NamedPipeClientConnection(pipeStream); connection = new Connection(clientConnection, handler); } 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. CompilerServerLogger.LogException(ex, "Error creating client named pipe"); return(new ConnectionData(CompletionReason.CompilationNotStarted)); } return(await connection.ServeConnection(cancellationToken).ConfigureAwait(false)); }
private static int Run(TimeSpan?keepAliveTimeout, string compilerExeDirectory, string pipeName) { 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( pipeName, keepAliveTimeout); return(CommonCompiler.Succeeded); }
/// <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 NULL if the pipe couldn't be created..</returns> private NamedPipeServerStream ConstructPipe() { // Add the process ID onto the pipe name so each process gets a unique pipe name. // The client must user this algorithm too to connect. string pipeName = basePipeName + Process.GetCurrentProcess().Id.ToString(); try { 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); } catch (Exception e) { // Windows may not create the pipe for a number of reasons. CompilerServerLogger.LogException(e, string.Format("Construction of pipe '{0}' failed", pipeName)); return(null); } }
public static bool Check(string baseDirectory, IEnumerable <CommandLineAnalyzerReference> analyzerReferences, IAnalyzerAssemblyLoader loader, IEnumerable <string> ignorableReferenceNames = null) { if (ignorableReferenceNames == null) { ignorableReferenceNames = s_defaultIgnorableReferenceNames; } try { CompilerServerLogger.Log("Begin Analyzer Consistency Check"); return(CheckCore(baseDirectory, analyzerReferences, loader, ignorableReferenceNames)); } catch (Exception e) { CompilerServerLogger.LogException(e, "Analyzer Consistency Check"); return(false); } finally { CompilerServerLogger.Log("End Analyzer Consistency Check"); } }
public static int Main(string[] args) { NameValueCollection appSettings; try { #if NET46 appSettings = System.Configuration.ConfigurationManager.AppSettings; #else // Do not use AppSettings on non-desktop platforms appSettings = new NameValueCollection(); #endif } catch (Exception ex) { // It is possible for AppSettings to throw when the application or machine configuration // is corrupted. This should not prevent the server from starting, but instead just revert // to the default configuration. appSettings = new NameValueCollection(); CompilerServerLogger.LogException(ex, "Error loading application settings"); } try { var controller = new DesktopBuildServerController(appSettings); return(controller.Run(args)); } catch (FileNotFoundException e) { // Assume the exception was the result of a missing compiler assembly. LogException(e); } catch (TypeInitializationException e) when(e.InnerException is FileNotFoundException) { // Assume the exception was the result of a missing compiler assembly. LogException((FileNotFoundException)e.InnerException); } return(CommonCompiler.Failed); }
/// <summary> /// The timeout was fired -- check if we need to cancel the pipe. /// </summary> private void ServerDieTimeoutFired(Task timeoutTask) { // If the timeout wasn't cancelled and we have no connections // we should shut down if (!timeoutTask.IsCanceled && this.activeConnectionCount == 0) { // N.B. There is no way to cancel waiting for a connection other than closing the // pipe, so there is a race between closing the pipe and getting another // connection. We should close the pipe as soon as possible and do any necessary // cleanup afterwards this.waitingPipeStream.Close(); CompilerServerLogger.Log("Waiting for pipe connection timed out after {0} ms.", this.serverDieTimeout); } else { lock (this.timeoutLockObject) { this.timeoutCTS = null; } } }
public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken) { using (var writer = new BinaryWriter(new MemoryStream(), Encoding.Unicode)) { // Format the response CompilerServerLogger.Log("Formatting Response"); writer.Write((int)this.Type); this.AddResponseBody(writer); 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); } }
private const int DefaultServerDieTimeout = 3 * 60 * 60 * 1000; // 3 hours /// <summary> /// Main entry point for the process. Initialize the server dispatcher /// and wait for connections. /// </summary> public static int Main(string[] args) { int dieTimeout; // Try to get the die timeout from the app.config file. // Set to default if any failures try { string dieTimeoutStr = ConfigurationManager.AppSettings["dieTimeout"]; if (!int.TryParse(dieTimeoutStr, out dieTimeout)) { dieTimeout = DefaultServerDieTimeout; } else if (dieTimeout > 0) { // The die timeout in the app.config file is stored in // seconds, not milliseconds dieTimeout *= 1000; } } catch (ConfigurationErrorsException) { dieTimeout = DefaultServerDieTimeout; } CompilerFatalError.Handler = FailFast.OnFatalException; CompilerServerLogger.Initialize("SRV"); CompilerServerLogger.Log("Process started"); var dispatcher = new ServerDispatcher(BuildProtocolConstants.PipeName, new CompilerRequestHandler(), dieTimeout); //Debugger.Launch(); dispatcher.ListenAndDispatchConnections(); return(0); }
/// <summary> /// Invoke the VB compiler with the given arguments and current directory, and send output and error /// to the given TextWriters. /// </summary> private BuildResponse BasicCompile( string responseFileDirectory, string currentDirectory, string libDirectory, string[] commandLineArguments, 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(VisualBasicCompilerServer.RunCompiler( responseFileDirectory, commandLineArguments, currentDirectory, RuntimeEnvironment.GetRuntimeDirectory(), libDirectory, AnalyzerLoader, cancellationToken)); }
/// <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 currentDirectory, string libDirectory, 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( commandLineArguments, currentDirectory, libDirectory, output, cancellationToken, out utf8output)); }
/// <summary> /// Does the client of "pipeStream" have the same identity and elevation as we do? /// </summary> private bool ClientAndOurIdentitiesMatch() { var serverIdentity = GetIdentity(impersonating: false); Tuple <string, bool> clientIdentity = null; _pipeStream.RunAsClient(() => { clientIdentity = GetIdentity(impersonating: true); }); CompilerServerLogger.Log( "Pipe {0}: Server identity = '{1}', server elevation='{2}'.", _loggingIdentifier, serverIdentity.Item1, serverIdentity.Item2.ToString()); CompilerServerLogger.Log( "Pipe {0}: Client identity = '{1}', client elevation='{2}'.", _loggingIdentifier, clientIdentity.Item1, clientIdentity.Item2.ToString()); return (StringComparer.OrdinalIgnoreCase.Equals(serverIdentity.Item1, clientIdentity.Item1) && serverIdentity.Item2 == clientIdentity.Item2); }
/// <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. NamedPipeServerStream pipeStream = ConstructPipe(_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> /// This function never returns. It loops and dispatches requests /// until the process it terminated. Each incoming request is /// dispatched to a new thread which runs. /// </summary> public void ListenAndDispatchConnections() { Debug.Assert(SynchronizationContext.Current == null); // We loop here continuously, dispatching client connections as // they come in, until TimeoutFired causes an exception to be // thrown. Each time through the loop we either have accepted a // client connection, or timed out. After each connection, // we need to create a new instance of the pipe to listen on. bool firstConnection = true; while (true) { // 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. NamedPipeServerStream pipeStream = ConstructPipe(); if (pipeStream == null) { return; } this.waitingPipeStream = pipeStream; // If this is the first connection then we want to start a timeout // Otherwise, we should start the timeout when the last connection // finishes processing. if (firstConnection) { StartTimeoutTimer(); firstConnection = false; } CompilerServerLogger.Log("Waiting for new connection"); // Wait for a connection or the timeout // If a timeout occurs then the pipe will be closed and we will throw an exception // to the calling function. try { pipeStream.WaitForConnection(); } catch (ObjectDisposedException) { CompilerServerLogger.Log("Listening pipe closed; exiting."); break; } catch (IOException) { CompilerServerLogger.Log("The pipe was closed or the client has been disconnected"); break; } // We have a connection CompilerServerLogger.Log("Pipe connection detected."); // Cancel the timeouts this.keepAliveTimer.CancelIfActive(); // Dispatch the new connection on the thread pool var newConnection = DispatchConnection(pipeStream); // Connection object now owns the connected pipe. // Cleanup any connections that have completed, and then add // the new one. activeConnections.RemoveAll(t => t.IsCompleted); activeConnections.Add(newConnection); if (this.keepAliveTimer.StopAfterFirstConnection) { break; } // Next time around the loop, create a new instance of the pipe // to listen for another connection. } Task.WhenAll(activeConnections).Wait(); }
/// <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; // 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); 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( pipeName, keepAliveTimeout, watchAnalyzerFiles: true); return(CommonCompiler.Succeeded); }
/// <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="pipeName">Name of the pipe on which the instance will listen for requests.</param> /// <param name="cancellationToken">Used to cancel the connection sequence.</param> private async Task <NamedPipeServerStream> CreateListenTask(string pipeName, 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. NamedPipeServerStream pipeStream = ConstructPipe(pipeName); // Unfortunately the version of .Net we are using doesn't support the WaitForConnectionAsync // method. When it is available it should absolutely be used here. In the meantime we // have to deal with the idea that this WaitForConnection call will block a thread // for a significant period of time. It is unadvisable to do this to a thread pool thread // hence we will use an explicit thread here. var listenSource = new TaskCompletionSource <NamedPipeServerStream>(); var listenTask = listenSource.Task; var listenThread = new Thread(() => { try { CompilerServerLogger.Log("Waiting for new connection"); pipeStream.WaitForConnection(); CompilerServerLogger.Log("Pipe connection detected."); if (Environment.Is64BitProcess || MemoryHelper.IsMemoryAvailable()) { CompilerServerLogger.Log("Memory available - accepting connection"); listenSource.SetResult(pipeStream); return; } try { pipeStream.Close(); } catch { // Okay for Close failure here. } listenSource.SetException(new Exception("Insufficient resources to process new connection.")); } catch (Exception ex) { listenSource.SetException(ex); } }); listenThread.Start(); // Create a tasks that waits indefinitely (-1) and completes only when cancelled. var waitCancellationTokenSource = new CancellationTokenSource(); var waitTask = Task.Delay( Timeout.Infinite, CancellationTokenSource.CreateLinkedTokenSource(waitCancellationTokenSource.Token, cancellationToken).Token); await Task.WhenAny(listenTask, waitTask).ConfigureAwait(false); if (listenTask.IsCompleted) { waitCancellationTokenSource.Cancel(); return(await listenTask.ConfigureAwait(false)); } // The listen operation was cancelled. Close the pipe stream throw a cancellation exception to // simulate the cancel operation. waitCancellationTokenSource.Cancel(); try { pipeStream.Close(); } catch { // Okay for Close failure here. } throw new OperationCanceledException(); }
private void LogException(Exception e, string description) { CompilerServerLogger.LogException(e, string.Format(LogFormat, LoggingIdentifier, description)); }
private void Log(string message) { CompilerServerLogger.Log(LogFormat, LoggingIdentifier, message); }
private static void LogException(FileNotFoundException e) { CompilerServerLogger.LogException(e, "File not found"); }
/// <summary> /// This function never returns. It loops and dispatches requests /// until the process it terminated. Each incoming request is /// dispatched to a new thread which runs. /// </summary> public void ListenAndDispatchConnections() { Debug.Assert(SynchronizationContext.Current == null); // We loop here continuously, dispatching client connections as // they come in, until TimeoutFired causes an exception to be // thrown. Each time through the loop we either have accepted a // client connection, or timed out. After each connection, // we need to create a new instance of the pipe to listen on. bool firstConnection = true; while (true) { // 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. NamedPipeServerStream pipeStream = ConstructPipe(); if (pipeStream == null) { return; } this.waitingPipeStream = pipeStream; // If this is the first connection then we want to start a timeout // Otherwise, we should start the timeout when the last connection // finishes processing. if (firstConnection) { // Since no timeout could have been started before now, // this will definitely start a timeout StartTimeoutTimerIfNecessary(); firstConnection = false; } CompilerServerLogger.Log("Waiting for new connection"); // Wait for a connection or the timeout // If a timeout occurs then the pipe will be closed and we will throw an exception // to the calling function. try { pipeStream.WaitForConnection(); } catch (ObjectDisposedException) { CompilerServerLogger.Log("Listening pipe closed; exiting."); break; } catch (IOException) { CompilerServerLogger.Log("The pipe was closed or the client has been disconnected"); break; } // We have a connection CompilerServerLogger.Log("Pipe connection detected."); // Cancel the timeouts CancelTimeoutTimerIfNecessary(); // Dispatch the new connection on the thread pool // Assign to blank variable -- we want to fire & forget var _ = DispatchConnection(pipeStream); // Connection object now owns the connected pipe. // If our timeout is 0, then we stop after the first // connection. Otherwise, continue. if (this.serverDieTimeout == 0) { this.dieTimeoutBlock = new AutoResetEvent(false); this.dieTimeoutBlock.WaitOne(); break; } // Next time around the loop, create a new instance of the pipe // to listen for another connection. } }
private static bool CheckCore(string baseDirectory, IEnumerable <CommandLineAnalyzerReference> analyzerReferences, IAnalyzerAssemblyLoader loader, IEnumerable <string> ignorableReferenceNames) { var resolvedPaths = new List <string>(); foreach (var analyzerReference in analyzerReferences) { string resolvedPath = FileUtilities.ResolveRelativePath(analyzerReference.FilePath, basePath: null, baseDirectory: baseDirectory, searchPaths: SpecializedCollections.EmptyEnumerable <string>(), fileExists: File.Exists); if (resolvedPath != null) { resolvedPath = FileUtilities.TryNormalizeAbsolutePath(resolvedPath); if (resolvedPath != null) { resolvedPaths.Add(resolvedPath); } } // Don't worry about paths we can't resolve. The compiler will report an error for that later. } // First, check that the set of references is complete, modulo items in the safe list. foreach (var resolvedPath in resolvedPaths) { var missingDependencies = AssemblyUtilities.IdentifyMissingDependencies(resolvedPath, resolvedPaths); foreach (var missingDependency in missingDependencies) { if (!ignorableReferenceNames.Any(name => missingDependency.Name.StartsWith(name))) { CompilerServerLogger.Log($"Analyzer assembly {resolvedPath} depends on '{missingDependency}' but it was not found."); return(false); } } } // Register analyzers and their dependencies upfront, // so that assembly references can be resolved: foreach (var resolvedPath in resolvedPaths) { loader.AddDependencyLocation(resolvedPath); } // Load all analyzer assemblies: var loadedAssemblies = new List <Assembly>(); foreach (var resolvedPath in resolvedPaths) { loadedAssemblies.Add(loader.LoadFromPath(resolvedPath)); } // Third, check that the MVIDs of the files on disk match the MVIDs of the loaded assemblies. for (int i = 0; i < resolvedPaths.Count; i++) { var resolvedPath = resolvedPaths[i]; var loadedAssembly = loadedAssemblies[i]; var resolvedPathMvid = AssemblyUtilities.ReadMvid(resolvedPath); var loadedAssemblyMvid = loadedAssembly.ManifestModule.ModuleVersionId; if (resolvedPathMvid != loadedAssemblyMvid) { CompilerServerLogger.Log($"Analyzer assembly {resolvedPath} has MVID '{resolvedPathMvid}' but loaded assembly '{loadedAssembly.FullName}' has MVID '{loadedAssemblyMvid}'."); return(false); } } return(true); }
/// <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); #if NET472 PipeSecurity security; PipeOptions pipeOptions = PipeOptions.Asynchronous | PipeOptions.WriteThrough; if (!PlatformInformation.IsRunningOnMono) { security = new PipeSecurity(); SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; // Restrict access to just this account. PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite | PipeAccessRights.CreateNewInstance, AccessControlType.Allow); security.AddAccessRule(rule); security.SetOwner(identifier); } else { // Pipe security and additional access rights constructor arguments // are not supported by Mono // https://github.com/dotnet/roslyn/pull/30810 // https://github.com/mono/mono/issues/11406 security = null; // This enum value is implemented by Mono to restrict pipe access to // the current user const int CurrentUserOnly = unchecked ((int)0x20000000); pipeOptions |= (PipeOptions)CurrentUserOnly; } NamedPipeServerStream pipeStream = new NamedPipeServerStream( pipeName, PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, // Maximum connections. PipeTransmissionMode.Byte, pipeOptions, PipeBufferSize, // Default input buffer PipeBufferSize, // Default output buffer security, HandleInheritability.None); #else // The overload of NamedPipeServerStream with the PipeAccessRule // parameter was removed in netstandard. However, the default // constructor does not provide WRITE_DAC, so attempting to use // SetAccessControl will always fail. So, completely ignore ACLs on // netcore, and trust that our `ClientAndOurIdentitiesMatch` // verification will catch any invalid connections. // Issue to add WRITE_DAC support: // https://github.com/dotnet/corefx/issues/24040 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 #endif CompilerServerLogger.Log("Successfully constructed pipe '{0}'.", pipeName); return(pipeStream); }
private void Log(string message) { CompilerServerLogger.Log("Client {0}: {1}", _loggingIdentifier, message); }
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) { return(await WriteBuildResponseAsync( clientConnection, new MismatchedVersionBuildResponse(), CompletionData.RequestError, cancellationToken).ConfigureAwait(false)); } if (!string.Equals(request.CompilerHash, BuildProtocolConstants.GetCommitHash(), StringComparison.OrdinalIgnoreCase)) { return(await WriteBuildResponseAsync( clientConnection, new IncorrectHashBuildResponse(), CompletionData.RequestError, cancellationToken).ConfigureAwait(false)); } if (request.Arguments.Count == 1 && request.Arguments[0].ArgumentId == BuildProtocolConstants.ArgumentId.Shutdown) { return(await WriteBuildResponseAsync( clientConnection, new ShutdownBuildResponse(Process.GetCurrentProcess().Id), new CompletionData(CompletionReason.RequestCompleted, shutdownRequested : true), cancellationToken).ConfigureAwait(false)); } if (!allowCompilationRequests) { return(await WriteBuildResponseAsync( clientConnection, new RejectedBuildResponse("Compilation not allowed at this time"), CompletionData.RequestCompleted, cancellationToken).ConfigureAwait(false)); } if (!Environment.Is64BitProcess && !MemoryHelper.IsMemoryAvailable()) { return(await WriteBuildResponseAsync( clientConnection, new RejectedBuildResponse("Not enough resources to accept connection"), CompletionData.RequestError, cancellationToken).ConfigureAwait(false)); } return(await ProcessCompilationRequestAsync(clientConnection, request, cancellationToken).ConfigureAwait(false)); } }
private void LogException(Exception e, string message) { CompilerServerLogger.LogException(e, string.Format("Client {0}: {1}", _loggingIdentifier, message)); }