Example #1
0
        /// <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));
        }
Example #2
0
        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.
        }
Example #3
0
        /// <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);
        }
Example #4
0
        /// <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();
                }
            }
        }
Example #6
0
        /// <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.");
                }
            }
        }
Example #7
0
            /// <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);
                }
            }
Example #8
0
        /// <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);
        }
Example #9
0
        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);
                }
            }
        }
Example #11
0
        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);
            }
        }
Example #12
0
        /// <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.");
        }
Example #13
0
        /// <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));
        }
Example #15
0
        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, "", ""));
            }
        }
Example #17
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: ""));
            }
        }
Example #18
0
        /// <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;
            }
        }
Example #19
0
        /// <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;
 }
Example #21
0
        /// <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);
        }
Example #23
0
        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);
        }
Example #25
0
        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);
        }
Example #26
0
        /// <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));
        }
Example #28
0
        /// <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()));
            }
        }
Example #29
0
        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);
        }
Example #30
0
        /// <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);
        }