예제 #1
0
            public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken)
            {
                if (!TryParseArguments(request, out var parsed))
                {
                    return(new RejectedServerResponse());
                }

                var exitCode    = 0;
                var output      = string.Empty;
                var app         = new Application(cancellationToken);
                var commandArgs = parsed.args.ToArray();

                if (ServerLogger.IsLoggingEnabled)
                {
                    using (var writer = new StringWriter())
                    {
                        app.Out   = writer;
                        app.Error = writer;
                        exitCode  = app.Execute(commandArgs);
                        output    = writer.ToString();
                        ServerLogger.Log(output);
                    }
                }
                else
                {
                    using (var writer = new StreamWriter(Stream.Null))
                    {
                        app.Out   = writer;
                        app.Error = writer;
                        exitCode  = app.Execute(commandArgs);
                    }
                }

                return(new CompletedServerResponse(exitCode, utf8output: false, output: string.Empty));
            }
예제 #2
0
        public async Task ServerRequest_WriteRead_RoundtripsProperly()
        {
            // Arrange
            var request = new ServerRequest(
                ServerProtocol.ProtocolVersion,
                ImmutableArray.Create(
                    new RequestArgument(RequestArgument.ArgumentId.CurrentDirectory, argumentIndex: 0, value: "directory"),
                    new RequestArgument(RequestArgument.ArgumentId.CommandLineArgument, argumentIndex: 1, value: "file")));
            var memoryStream = new MemoryStream();

            // Act
            await request.WriteAsync(memoryStream, CancellationToken.None);

            // Assert
            Assert.True(memoryStream.Position > 0);
            memoryStream.Position = 0;
            var read = await ServerRequest.ReadAsync(memoryStream, CancellationToken.None);

            Assert.Equal(ServerProtocol.ProtocolVersion, read.ProtocolVersion);
            Assert.Equal(2, read.Arguments.Count);
            Assert.Equal(RequestArgument.ArgumentId.CurrentDirectory, read.Arguments[0].Id);
            Assert.Equal(0, read.Arguments[0].ArgumentIndex);
            Assert.Equal("directory", read.Arguments[0].Value);
            Assert.Equal(RequestArgument.ArgumentId.CommandLineArgument, read.Arguments[1].Id);
            Assert.Equal(1, read.Arguments[1].ArgumentIndex);
            Assert.Equal("file", read.Arguments[1].Value);
        }
예제 #3
0
            public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken)
            {
                if (!TryParseArguments(request, out var parsed))
                {
                    return(new RejectedServerResponse());
                }

                var exitCode    = 0;
                var commandArgs = parsed.args.ToArray();

                var outputWriter = new StringWriter();
                var errorWriter  = new StringWriter();

                var checker = new DefaultExtensionDependencyChecker(Loader, outputWriter, errorWriter);
                var app     = new Application(cancellationToken, Loader, checker, AssemblyReferenceProvider, outputWriter, errorWriter);

                exitCode = app.Execute(commandArgs);

                var output = outputWriter.ToString();
                var error  = errorWriter.ToString();

                outputWriter.Dispose();
                errorWriter.Dispose();

                // This will no-op if server logging is not enabled.
                ServerLogger.Log(output);
                ServerLogger.Log(error);

                return(new CompletedServerResponse(exitCode, utf8output: false, output, error));
            }
예제 #4
0
        public async Task ShutdownRequest_WriteRead_RoundtripsProperly()
        {
            // Arrange
            var memoryStream = new MemoryStream();
            var request      = ServerRequest.CreateShutdown();

            // Act
            await request.WriteAsync(memoryStream, CancellationToken.None);

            // Assert
            memoryStream.Position = 0;
            var read = await ServerRequest.ReadAsync(memoryStream, CancellationToken.None);

            var argument1 = request.Arguments[0];

            Assert.Equal(RequestArgument.ArgumentId.Shutdown, argument1.Id);
            Assert.Equal(0, argument1.ArgumentIndex);
            Assert.Equal("", argument1.Value);

            var argument2 = request.Arguments[1];

            Assert.Equal(RequestArgument.ArgumentId.CommandLineArgument, argument2.Id);
            Assert.Equal(1, argument2.ArgumentIndex);
            Assert.Equal("shutdown", argument2.Value);
        }
예제 #5
0
            public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken)
            {
                if (!TryParseArguments(request, out var parsed))
                {
                    return(new RejectedServerResponse());
                }

                var exitCode    = 0;
                var output      = string.Empty;
                var commandArgs = parsed.args.ToArray();

                var writer = ServerLogger.IsLoggingEnabled ? new StringWriter() : TextWriter.Null;

                var checker = new DefaultExtensionDependencyChecker(Loader, writer);
                var app     = new Application(cancellationToken, Loader, checker)
                {
                    Out   = writer,
                    Error = writer,
                };

                exitCode = app.Execute(commandArgs);

                if (ServerLogger.IsLoggingEnabled)
                {
                    output = writer.ToString();
                    ServerLogger.Log(output);
                }

                return(new CompletedServerResponse(exitCode, utf8output: false, output: string.Empty));
            }
예제 #6
0
        /// <summary>
        /// Try to process the request using the server. Returns a null-containing Task if a response
        /// from the server cannot be retrieved.
        /// </summary>
        private static async Task <ServerResponse> TryProcessRequest(
            Client client,
            ServerRequest request,
            CancellationToken cancellationToken)
        {
            ServerResponse response;

            using (client)
            {
                // Write the request
                try
                {
                    ServerLogger.Log("Begin writing request");
                    await request.WriteAsync(client.Stream, cancellationToken).ConfigureAwait(false);

                    ServerLogger.Log("End writing request");
                }
                catch (Exception e)
                {
                    ServerLogger.LogException(e, "Error writing build request.");
                    return(new RejectedServerResponse());
                }

                // Wait for the compilation and a monitor to detect if the server disconnects
                var serverCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

                ServerLogger.Log("Begin reading response");

                var responseTask = ServerResponse.ReadAsync(client.Stream, serverCts.Token);
                var monitorTask  = client.WaitForDisconnectAsync(serverCts.Token);
                await Task.WhenAny(new[] { responseTask, monitorTask }).ConfigureAwait(false);

                ServerLogger.Log("End reading response");

                if (responseTask.IsCompleted)
                {
                    // await the task to log any exceptions
                    try
                    {
                        response = await responseTask.ConfigureAwait(false);
                    }
                    catch (Exception e)
                    {
                        ServerLogger.LogException(e, "Error reading response");
                        response = new RejectedServerResponse();
                    }
                }
                else
                {
                    ServerLogger.Log("Server disconnect");
                    response = new RejectedServerResponse();
                }

                // Cancel whatever task is still around
                serverCts.Cancel();
                Debug.Assert(response != null);
                return(response);
            }
        }
            public override ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken)
            {
                if (ExecuteFunc != null)
                {
                    return(ExecuteFunc(request, cancellationToken));
                }

                return(EmptyServerResponse);
            }
예제 #8
0
        internal static async Task <ServerResponse> Send(string pipeName, ServerRequest request)
        {
            using (var client = await Client.ConnectAsync(pipeName, timeout: null, cancellationToken: default).ConfigureAwait(false))
            {
                await request.WriteAsync(client.Stream).ConfigureAwait(false);

                return(await ServerResponse.ReadAsync(client.Stream).ConfigureAwait(false));
            }
        }
예제 #9
0
        protected async override Task <int> ExecuteCoreAsync()
        {
            if (!IsServerRunning())
            {
                // server isn't running right now
                Out.Write("Server is not running.");
                return(0);
            }

            try
            {
                using (var client = await Client.ConnectAsync(Pipe.Value(), timeout: TimeSpan.FromSeconds(5), cancellationToken: Cancelled))
                {
                    if (client == null)
                    {
                        throw new InvalidOperationException("Couldn't connect to the server.");
                    }

                    var request = ServerRequest.CreateShutdown();
                    await request.WriteAsync(client.Stream, Cancelled).ConfigureAwait(false);

                    var response = ((ShutdownServerResponse)await ServerResponse.ReadAsync(client.Stream, Cancelled));

                    if (Wait.HasValue())
                    {
                        try
                        {
                            var process = Process.GetProcessById(response.ServerProcessId);
                            process.WaitForExit();
                        }
                        catch (Exception ex)
                        {
                            // There is an inherent race here with the server process.  If it has already shutdown
                            // by the time we try to access it then the operation has succeeded.
                            Error.Write(ex);
                        }

                        Out.Write("Server pid:{0} shut down completed.", response.ServerProcessId);
                    }
                }
            }
            catch (Exception ex) when(IsServerRunning())
            {
                // Ignore an exception that occurred while the server was shutting down.
                Error.Write(ex);
            }

            return(0);
        }
예제 #10
0
        private Task <ServerResponse> ExecuteRequestAsync(ServerRequest buildRequest, CancellationToken cancellationToken)
        {
            Func <ServerResponse> func = () =>
            {
                ServerLogger.Log("Begin processing request");

                var response = _compilerHost.Execute(buildRequest, cancellationToken);

                ServerLogger.Log("End processing request");
                return(response);
            };

            var task = new Task <ServerResponse>(func, cancellationToken, TaskCreationOptions.LongRunning);

            task.Start();
            return(task);
        }
예제 #11
0
        public void CreateShutdown_CreatesCorrectShutdownRequest()
        {
            // Arrange & Act
            var request = ServerRequest.CreateShutdown();

            // Assert
            Assert.Equal(2, request.Arguments.Count);

            var argument1 = request.Arguments[0];

            Assert.Equal(RequestArgument.ArgumentId.Shutdown, argument1.Id);
            Assert.Equal(0, argument1.ArgumentIndex);
            Assert.Equal("", argument1.Value);

            var argument2 = request.Arguments[1];

            Assert.Equal(RequestArgument.ArgumentId.CommandLineArgument, argument2.Id);
            Assert.Equal(1, argument2.ArgumentIndex);
            Assert.Equal("shutdown", argument2.Value);
        }
예제 #12
0
        public async Task AcceptConnection_ShutdownRequest_ReturnsShutdownResponse()
        {
            // Arrange
            var stream = new TestableStream();
            await ServerRequest.CreateShutdown().WriteAsync(stream.ReadStream, CancellationToken.None);

            stream.ReadStream.Position = 0;

            var connection     = CreateConnection(stream);
            var connectionHost = CreateConnectionHost();
            var compilerHost   = CreateCompilerHost();
            var dispatcher     = new DefaultRequestDispatcher(connectionHost, compilerHost, CancellationToken.None);

            // Act
            var connectionResult = await dispatcher.AcceptConnection(
                Task.FromResult <Connection>(connection), accept : true, cancellationToken : CancellationToken.None);

            // Assert
            Assert.Equal(ConnectionResult.Reason.ClientShutdownRequest, connectionResult.CloseReason);
            stream.WriteStream.Position = 0;
            var response = await ServerResponse.ReadAsync(stream.WriteStream).ConfigureAwait(false);

            Assert.Equal(ServerResponse.ResponseType.Shutdown, response.Type);
        }
예제 #13
0
        private static async Task <ServerResponse> RunOnServerCore(
            IList <string> arguments,
            ServerPaths buildPaths,
            string pipeName,
            string keepAlive,
            int?timeoutOverride,
            Func <string, string, bool, bool> tryCreateServerFunc,
            CancellationToken cancellationToken,
            bool debug)
        {
            if (pipeName == null)
            {
                return(new RejectedServerResponse());
            }

            if (buildPaths.TempDirectory == null)
            {
                return(new RejectedServerResponse());
            }

            var           clientDir              = buildPaths.ClientDirectory;
            var           timeoutNewProcess      = timeoutOverride ?? TimeOutMsNewProcess;
            var           timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess;
            var           clientMutexName        = MutexName.GetClientMutexName(pipeName);
            Task <Client> pipeTask = null;

            using (var clientMutex = new Mutex(initiallyOwned: true, name: clientMutexName, createdNew: out var holdsMutex))
            {
                try
                {
                    if (!holdsMutex)
                    {
                        try
                        {
                            holdsMutex = clientMutex.WaitOne(timeoutNewProcess);

                            if (!holdsMutex)
                            {
                                return(new RejectedServerResponse());
                            }
                        }
                        catch (AbandonedMutexException)
                        {
                            holdsMutex = true;
                        }
                    }

                    // Check for an already running server
                    var serverMutexName  = MutexName.GetServerMutexName(pipeName);
                    var wasServerRunning = WasServerMutexOpen(serverMutexName);
                    var timeout          = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess;

                    if (wasServerRunning || tryCreateServerFunc(clientDir, pipeName, debug))
                    {
                        pipeTask = Client.ConnectAsync(pipeName, TimeSpan.FromMilliseconds(timeout), cancellationToken);
                    }
                }
                finally
                {
                    if (holdsMutex)
                    {
                        clientMutex.ReleaseMutex();
                    }
                }
            }

            if (pipeTask != null)
            {
                var client = await pipeTask.ConfigureAwait(false);

                if (client != null)
                {
                    var request = ServerRequest.Create(
                        buildPaths.WorkingDirectory,
                        buildPaths.TempDirectory,
                        arguments,
                        keepAlive);

                    return(await TryProcessRequest(client, request, cancellationToken).ConfigureAwait(false));
                }
            }

            return(new RejectedServerResponse());
        }
예제 #14
0
        private static async Task <ServerResponse> RunOnServerCore(
            IList <string> arguments,
            ServerPaths serverPaths,
            string pipeName,
            string keepAlive,
            int?timeoutOverride,
            TryCreateServerCoreDelegate <string, string, int?, bool, bool> tryCreateServerFunc,
            CancellationToken cancellationToken,
            bool debug)
        {
            if (pipeName == null)
            {
                return(new RejectedServerResponse());
            }

            if (serverPaths.TempDirectory == null)
            {
                return(new RejectedServerResponse());
            }

            var           clientDir              = serverPaths.ClientDirectory;
            var           timeoutNewProcess      = timeoutOverride ?? TimeOutMsNewProcess;
            var           timeoutExistingProcess = timeoutOverride ?? TimeOutMsExistingProcess;
            var           clientMutexName        = MutexName.GetClientMutexName(pipeName);
            Task <Client> pipeTask = null;

            Mutex clientMutex = null;
            var   holdsMutex  = false;

            try
            {
                try
                {
                    clientMutex = new Mutex(initiallyOwned: true, name: clientMutexName, createdNew: out holdsMutex);
                }
                catch (Exception ex)
                {
                    // The Mutex constructor can throw in certain cases. One specific example is docker containers
                    // where the /tmp directory is restricted. In those cases there is no reliable way to execute
                    // the server and we need to fall back to the command line.
                    // Example: https://github.com/dotnet/roslyn/issues/24124

                    ServerLogger.LogException(ex, "Client mutex creation failed.");

                    return(new RejectedServerResponse());
                }

                if (!holdsMutex)
                {
                    try
                    {
                        holdsMutex = clientMutex.WaitOne(timeoutNewProcess);

                        if (!holdsMutex)
                        {
                            return(new RejectedServerResponse());
                        }
                    }
                    catch (AbandonedMutexException)
                    {
                        holdsMutex = true;
                    }
                }

                // Check for an already running server
                var serverMutexName  = MutexName.GetServerMutexName(pipeName);
                var wasServerRunning = WasServerMutexOpen(serverMutexName);
                var timeout          = wasServerRunning ? timeoutExistingProcess : timeoutNewProcess;

                if (wasServerRunning || tryCreateServerFunc(clientDir, pipeName, out var _, debug))
                {
                    pipeTask = Client.ConnectAsync(pipeName, TimeSpan.FromMilliseconds(timeout), cancellationToken);
                }
            }
            finally
            {
                if (holdsMutex)
                {
                    clientMutex?.ReleaseMutex();
                }

                clientMutex?.Dispose();
            }

            if (pipeTask != null)
            {
                var client = await pipeTask.ConfigureAwait(false);

                if (client != null)
                {
                    var request = ServerRequest.Create(
                        serverPaths.WorkingDirectory,
                        serverPaths.TempDirectory,
                        arguments,
                        keepAlive);

                    return(await TryProcessRequest(client, request, cancellationToken).ConfigureAwait(false));
                }
            }

            return(new RejectedServerResponse());
        }
예제 #15
0
 private bool TryParseArguments(ServerRequest request, out (string workingDirectory, string tempDirectory, string[] args) parsed)
예제 #16
0
        internal static async Task <int> SendShutdown(string pipeName)
        {
            var response = await Send(pipeName, ServerRequest.CreateShutdown());

            return(((ShutdownServerResponse)response).ServerProcessId);
        }
예제 #17
0
 public abstract ServerResponse Execute(ServerRequest request, CancellationToken cancellationToken);
예제 #18
0
        internal async Task <ConnectionResult> AcceptConnection(Task <Connection> task, bool accept, CancellationToken cancellationToken)
        {
            Connection connection;

            try
            {
                connection = await task;
            }
            catch (Exception ex)
            {
                // Unable to establish a connection with the client.  The client is responsible for
                // handling this case. Nothing else for us to do here.
                ServerLogger.LogException(ex, "Error creating client named pipe");
                return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted));
            }

            try
            {
                using (connection)
                {
                    ServerRequest request;
                    try
                    {
                        ServerLogger.Log("Begin reading request.");
                        request = await ServerRequest.ReadAsync(connection.Stream, cancellationToken).ConfigureAwait(false);

                        ServerLogger.Log("End reading request.");
                    }
                    catch (Exception e)
                    {
                        ServerLogger.LogException(e, "Error reading build request.");
                        return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted));
                    }

                    if (request.IsShutdownRequest())
                    {
                        // Reply with the PID of this process so that the client can wait for it to exit.
                        var response = new ShutdownServerResponse(Process.GetCurrentProcess().Id);
                        await response.WriteAsync(connection.Stream, cancellationToken);

                        // We can safely disconnect the client, then when this connection gets cleaned up by the event loop
                        // the server will go to a shutdown state.
                        return(new ConnectionResult(ConnectionResult.Reason.ClientShutdownRequest));
                    }
                    else if (!accept)
                    {
                        // We're already in shutdown mode, respond gracefully so the client can run in-process.
                        var response = new RejectedServerResponse();
                        await response.WriteAsync(connection.Stream, cancellationToken).ConfigureAwait(false);

                        return(new ConnectionResult(ConnectionResult.Reason.CompilationNotStarted));
                    }
                    else
                    {
                        // If we get here then this is a real request that we will accept and process.
                        //
                        // Kick off both the compilation and a task to monitor the pipe for closing.
                        var buildCancelled = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

                        var watcher = connection.WaitForDisconnectAsync(buildCancelled.Token);
                        var worker  = ExecuteRequestAsync(request, buildCancelled.Token);

                        // await will end when either the work is complete or the connection is closed.
                        await Task.WhenAny(worker, watcher);

                        // Do an 'await' on the completed task, preference being compilation, to force
                        // any exceptions to be realized in this method for logging.
                        ConnectionResult.Reason reason;
                        if (worker.IsCompleted)
                        {
                            var response = await worker;

                            try
                            {
                                ServerLogger.Log("Begin writing response.");
                                await response.WriteAsync(connection.Stream, cancellationToken);

                                ServerLogger.Log("End writing response.");

                                reason = ConnectionResult.Reason.CompilationCompleted;

                                _eventBus.CompilationCompleted();
                            }
                            catch
                            {
                                reason = ConnectionResult.Reason.ClientDisconnect;
                            }
                        }
                        else
                        {
                            await watcher;
                            reason = ConnectionResult.Reason.ClientDisconnect;
                        }

                        // Begin the tear down of the Task which didn't complete.
                        buildCancelled.Cancel();

                        return(new ConnectionResult(reason, request.KeepAlive));
                    }
                }
            }
            catch (Exception ex)
            {
                ServerLogger.LogException(ex, "Error handling connection");
                return(new ConnectionResult(ConnectionResult.Reason.ClientException));
            }
        }