Beispiel #1
0
        public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken)
        {
            using (var memoryStream = new MemoryStream())
                using (var writer = new BinaryWriter(memoryStream, Encoding.Unicode))
                {
                    // Format the response
                    ServerLogger.Log("Formatting Response");
                    writer.Write((int)Type);

                    AddResponseBody(writer);
                    writer.Flush();

                    cancellationToken.ThrowIfCancellationRequested();

                    // Send the response to the client

                    // Write the length of the response
                    var length = checked ((int)memoryStream.Length);

                    ServerLogger.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
                    ServerLogger.Log("Writing response of size {0}", length);
                    memoryStream.Position = 0;
                    await memoryStream
                    .CopyToAsync(outStream, bufferSize : length, cancellationToken : cancellationToken)
                    .ConfigureAwait(false);
                }
        }
Beispiel #2
0
            public async override Task WaitForDisconnectAsync(CancellationToken cancellationToken)
            {
                if (!(Stream is PipeStream pipeStream))
                {
                    return;
                }

                // We have to poll for disconnection by reading, PipeStream.IsConnected isn't reliable unless you
                // actually do a read - which will cause it to update its state.
                while (!cancellationToken.IsCancellationRequested && pipeStream.IsConnected)
                {
                    await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);

                    try
                    {
                        ServerLogger.Log($"Before poking pipe {Identifier}.");
                        await Stream.ReadAsync(Array.Empty <byte>(), 0, 0, cancellationToken);

                        ServerLogger.Log($"After poking pipe {Identifier}.");
                    }
                    catch (OperationCanceledException)
                    {
                    }
                    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.
                        ServerLogger.LogException(e, $"Error poking pipe {Identifier}.");
                    }
                }
            }
Beispiel #3
0
            public async override Task <Connection> WaitForConnectionAsync(CancellationToken cancellationToken)
            {
                // Create the pipe and begin waiting for a connection. This  doesn't block, but could fail
                // in certain circumstances, such as the OS refusing to create the pipe for some reason
                // or the pipe was disconnected before we starting listening.
                var pipeStream = new NamedPipeServerStream(
                    PipeName,
                    PipeDirection.InOut,
                    NamedPipeServerStream.MaxAllowedServerInstances, // Maximum connections.
                    PipeTransmissionMode.Byte,
                    _pipeOptions,
                    PipeBufferSize,  // Default input buffer
                    PipeBufferSize); // Default output buffer

                ServerLogger.Log("Waiting for new connection");
                await pipeStream.WaitForConnectionAsync(cancellationToken);

                ServerLogger.Log("Pipe connection detected.");

                if (Environment.Is64BitProcess || Memory.IsMemoryAvailable())
                {
                    ServerLogger.Log("Memory available - accepting connection");
                    return(new NamedPipeConnection(pipeStream, GetNextIdentifier()));
                }

                pipeStream.Close();
                throw new Exception("Insufficient resources to process new connection.");
            }
Beispiel #4
0
        /// <summary>
        /// This task does not complete until we are completely done reading.
        /// </summary>
        internal static async Task ReadAllAsync(
            Stream stream,
            byte[] buffer,
            int count,
            CancellationToken cancellationToken)
        {
            var totalBytesRead = 0;

            do
            {
                ServerLogger.Log("Attempting to read {0} bytes from the stream", count - totalBytesRead);
                var bytesRead = await stream.ReadAsync(
                    buffer,
                    totalBytesRead,
                    count - totalBytesRead,
                    cancellationToken)
                                .ConfigureAwait(false);

                if (bytesRead == 0)
                {
                    ServerLogger.Log("Unexpected -- read 0 bytes from the stream.");
                    throw new EndOfStreamException("Reached end of stream before end of read.");
                }
                ServerLogger.Log("Read {0} bytes", bytesRead);
                totalBytesRead += bytesRead;
            } while (totalBytesRead < count);
            ServerLogger.Log("Finished read");
        }
Beispiel #5
0
        public static ServerRequest Create(
            string workingDirectory,
            string tempDirectory,
            IList <string> args,
            string keepAlive = null)
        {
            ServerLogger.Log("Creating ServerRequest");
            ServerLogger.Log($"Working directory: {workingDirectory}");
            ServerLogger.Log($"Temp directory: {tempDirectory}");

            var requestLength = args.Count + 1;
            var requestArgs   = new List <RequestArgument>(requestLength)
            {
                new RequestArgument(RequestArgument.ArgumentId.CurrentDirectory, 0, workingDirectory),
                new RequestArgument(RequestArgument.ArgumentId.TempDirectory, 0, tempDirectory)
            };

            if (keepAlive != null)
            {
                requestArgs.Add(new RequestArgument(RequestArgument.ArgumentId.KeepAlive, 0, keepAlive));
            }

            for (var i = 0; i < args.Count; ++i)
            {
                var arg = args[i];
                ServerLogger.Log($"argument[{i}] = {arg}");
                requestArgs.Add(new RequestArgument(RequestArgument.ArgumentId.CommandLineArgument, i, arg));
            }

            return(new ServerRequest(ServerProtocol.ProtocolVersion, requestArgs));
        }
Beispiel #6
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));
            }
Beispiel #7
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);
            }
        }
Beispiel #8
0
        // Based on: https://github.com/dotnet/roslyn/blob/14aed138a01c448143b9acf0fe77a662e3dfe2f4/src/Compilers/Shared/BuildServerConnection.cs#L290
        public static async Task <Client> ConnectAsync(string pipeName, TimeSpan?timeout, CancellationToken cancellationToken)
        {
            var timeoutMilliseconds = timeout == null ? Timeout.Infinite : (int)timeout.Value.TotalMilliseconds;

            try
            {
                // Machine-local named pipes are named "\\.\pipe\<pipename>".
                // We use the SHA1 of the directory the compiler exes live in as the pipe name.
                // The NamedPipeClientStream class handles the "\\.\pipe\" part for us.
                ServerLogger.Log("Attempt to open named pipe '{0}'", pipeName);

                var stream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, _pipeOptions);
                cancellationToken.ThrowIfCancellationRequested();

                ServerLogger.Log("Attempt to connect named pipe '{0}'", pipeName);
                try
                {
                    await stream.ConnectAsync(timeoutMilliseconds, cancellationToken);
                }
                catch (Exception e) when(e is IOException || e is TimeoutException)
                {
                    // Note: IOException can also indicate timeout.
                    // From docs:
                    // - TimeoutException: Could not connect to the server within the specified timeout period.
                    // - IOException: The server is connected to another client and the  time-out period has expired.
                    ServerLogger.Log($"Connecting to server timed out after {timeoutMilliseconds} ms");
                    return(null);
                }

                ServerLogger.Log("Named pipe '{0}' connected", pipeName);
                cancellationToken.ThrowIfCancellationRequested();

#if NETFRAMEWORK
                // Verify that we own the pipe.
                if (!CheckPipeConnectionOwnership(stream))
                {
                    ServerLogger.Log("Owner of named pipe is incorrect");
                    return(null);
                }
#endif

                return(new NamedPipeClient(stream, GetNextIdentifier()));
            }
            catch (Exception e) when(!(e is TaskCanceledException || e is OperationCanceledException))
            {
                ServerLogger.LogException(e, "Exception while connecting to process");
                return(null);
            }
        }
        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);
        }
Beispiel #10
0
            protected override void Dispose(bool disposing)
            {
                ServerLogger.Log($"Pipe {Identifier}: Closing.");

                try
                {
                    Stream.Dispose();
                }
                catch (Exception ex)
                {
                    // The client connection failing to close isn't fatal to the server process.  It is simply a client
                    // for which we can no longer communicate and that's okay because the Close method indicates we are
                    // done with the client already.
                    var message = $"Pipe {Identifier}: Error closing pipe.";
                    ServerLogger.LogException(ex, message);
                }
            }
Beispiel #11
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 <ServerRequest> ReadAsync(Stream inStream, CancellationToken cancellationToken)
        {
            // Read the length of the request
            var lengthBuffer = new byte[4];

            ServerLogger.Log("Reading length of request");
            await ServerProtocol.ReadAllAsync(inStream, lengthBuffer, 4, cancellationToken).ConfigureAwait(false);

            var length = BitConverter.ToInt32(lengthBuffer, 0);

            // Back out if the request is > 1MB
            if (length > 0x100000)
            {
                ServerLogger.Log("Request is over 1MB in length, cancelling read.");
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

            // Read the full request
            var requestBuffer = new byte[length];
            await ServerProtocol.ReadAllAsync(inStream, requestBuffer, length, cancellationToken).ConfigureAwait(false);

            cancellationToken.ThrowIfCancellationRequested();

            ServerLogger.Log("Parsing request");
            // Parse the request into the Request data structure.
            using (var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode))
            {
                var protocolVersion = reader.ReadUInt32();
                var argumentCount   = reader.ReadUInt32();

                var argumentsBuilder = new List <RequestArgument>((int)argumentCount);

                for (var i = 0; i < argumentCount; i++)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    argumentsBuilder.Add(RequestArgument.ReadFromBinaryReader(reader));
                }

                return(new ServerRequest(protocolVersion, argumentsBuilder));
            }
        }
Beispiel #12
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 <ServerResponse> ReadAsync(Stream stream, CancellationToken cancellationToken = default(CancellationToken))
        {
            ServerLogger.Log("Reading response length");
            // Read the response length
            var lengthBuffer = new byte[4];
            await ServerProtocol.ReadAllAsync(stream, lengthBuffer, 4, cancellationToken).ConfigureAwait(false);

            var length = BitConverter.ToUInt32(lengthBuffer, 0);

            // Read the response
            ServerLogger.Log("Reading response of length {0}", length);
            var responseBuffer = new byte[length];
            await ServerProtocol.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(CompletedServerResponse.Create(reader));

                case ResponseType.MismatchedVersion:
                    return(new MismatchedVersionServerResponse());

                case ResponseType.Shutdown:
                    return(ShutdownServerResponse.Create(reader));

                case ResponseType.Rejected:
                    return(new RejectedServerResponse());

                default:
                    throw new InvalidOperationException("Received invalid response type from server.");
                }
            }
        }
Beispiel #13
0
        /// <summary>
        /// Write a Request to the stream.
        /// </summary>
        public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken = default(CancellationToken))
        {
            using (var memoryStream = new MemoryStream())
                using (var writer = new BinaryWriter(memoryStream, Encoding.Unicode))
                {
                    // Format the request.
                    ServerLogger.Log("Formatting request");
                    writer.Write(ProtocolVersion);
                    writer.Write(Arguments.Count);
                    foreach (var arg in Arguments)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                        arg.WriteToBinaryWriter(writer);
                    }
                    writer.Flush();

                    cancellationToken.ThrowIfCancellationRequested();

                    // Write the length of the request
                    var length = checked ((int)memoryStream.Length);

                    // Back out if the request is > 1 MB
                    if (memoryStream.Length > 0x100000)
                    {
                        ServerLogger.Log("Request is over 1MB in length, cancelling write");
                        throw new ArgumentOutOfRangeException();
                    }

                    // Send the request to the server
                    ServerLogger.Log("Writing length of request.");
                    await outStream
                    .WriteAsync(BitConverter.GetBytes(length), 0, 4, cancellationToken)
                    .ConfigureAwait(false);

                    ServerLogger.Log("Writing request of size {0}", length);
                    // Write the request
                    memoryStream.Position = 0;
                    await memoryStream
                    .CopyToAsync(outStream, bufferSize : length, cancellationToken : cancellationToken)
                    .ConfigureAwait(false);
                }
        }
Beispiel #14
0
        private static int RunApplication(string[] args)
        {
            DebugMode.HandleDebugSwitch(ref args);

            var cancel = new CancellationTokenSource();

            Console.CancelKeyPress += (sender, e) => { cancel.Cancel(); };

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

            // Prevent shadow copying.
            var loader  = new DefaultExtensionAssemblyLoader(baseDirectory: null);
            var checker = new DefaultExtensionDependencyChecker(loader, outputWriter, errorWriter);

            var application = new Application(
                cancel.Token,
                loader,
                checker,
                (path, properties) => MetadataReference.CreateFromFile(path, properties),
                outputWriter,
                errorWriter);

            var result = application.Execute(args);

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

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

            Console.Write(output);
            Console.Error.Write(error);

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

            return(result);
        }
        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(Environment.ProcessId);
                        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));
            }
        }
Beispiel #16
0
        // Internal for testing.
        internal static bool TryCreateServerCore(string clientDir, string pipeName, out int?processId, bool debug = false)
        {
            processId = null;

            // The server should be in the same directory as the client
            var expectedCompilerPath = Path.Combine(clientDir, ServerName);
            var expectedPath         = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH") ?? "dotnet";
            var argumentList         = new string[]
            {
                expectedCompilerPath,
                debug ? "--debug" : "",
                "server",
                "-p",
                pipeName
            };
            var processArguments = ArgumentEscaper.EscapeAndConcatenate(argumentList);

            if (!File.Exists(expectedCompilerPath))
            {
                return(false);
            }

            if (PlatformInformation.IsWindows)
            {
                // Currently, there isn't a way to use the Process class to create a process without
                // inheriting handles(stdin/stdout/stderr) from its parent. This might cause the parent process
                // to block on those handles. So we use P/Invoke. This code was taken from MSBuild task starting code.
                // The work to customize this behavior is being tracked by https://github.com/dotnet/corefx/issues/306.

                var startInfo = new STARTUPINFO();
                startInfo.cb         = Marshal.SizeOf(startInfo);
                startInfo.hStdError  = NativeMethods.InvalidIntPtr;
                startInfo.hStdInput  = NativeMethods.InvalidIntPtr;
                startInfo.hStdOutput = NativeMethods.InvalidIntPtr;
                startInfo.dwFlags    = NativeMethods.STARTF_USESTDHANDLES;
                var dwCreationFlags = NativeMethods.NORMAL_PRIORITY_CLASS | NativeMethods.CREATE_NO_WINDOW;

                ServerLogger.Log("Attempting to create process '{0}'", expectedPath);

                var builder = new StringBuilder($@"""{expectedPath}"" {processArguments}");

                var success = NativeMethods.CreateProcess(
                    lpApplicationName: null,
                    lpCommandLine: builder,
                    lpProcessAttributes: NativeMethods.NullPtr,
                    lpThreadAttributes: NativeMethods.NullPtr,
                    bInheritHandles: false,
                    dwCreationFlags: dwCreationFlags,
                    lpEnvironment: NativeMethods.NullPtr, // Inherit environment
                    lpCurrentDirectory: clientDir,
                    lpStartupInfo: ref startInfo,
                    lpProcessInformation: out var processInfo);

                if (success)
                {
                    ServerLogger.Log("Successfully created process with process id {0}", processInfo.dwProcessId);
                    NativeMethods.CloseHandle(processInfo.hProcess);
                    NativeMethods.CloseHandle(processInfo.hThread);
                    processId = processInfo.dwProcessId;
                }
                else
                {
                    ServerLogger.Log("Failed to create process. GetLastError={0}", Marshal.GetLastWin32Error());
                }
                return(success);
            }
            else
            {
                try
                {
                    var startInfo = new ProcessStartInfo()
                    {
                        FileName               = expectedPath,
                        Arguments              = processArguments,
                        UseShellExecute        = false,
                        WorkingDirectory       = clientDir,
                        RedirectStandardInput  = true,
                        RedirectStandardOutput = true,
                        RedirectStandardError  = true,
                        CreateNoWindow         = true
                    };

                    var process = Process.Start(startInfo);
                    processId = process.Id;

                    return(true);
                }
                catch
                {
                    return(false);
                }
            }
        }