private static async Task ListenCoreAsync( string pipeName, AsyncQueue <ListenResult> queue, Func <string> getClientLoggingIdentifier, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { NamedPipeServerStream?pipeStream = null; try { // Create the pipe and begin waiting for a connection. This // doesn't block, but could fail in certain circumstances, such // as Windows refusing to create the pipe for some reason // (out of handles?), or the pipe was disconnected before we // starting listening CompilerServerLogger.Log($"Constructing pipe and waiting for connections '{pipeName}'"); pipeStream = NamedPipeUtil.CreateServer(pipeName); // The WaitForConnectionAsync API does not fully respect the provided CancellationToken // on all platforms: // // https://github.com/dotnet/runtime/issues/40289 // // To mitigate this we need to setup a cancellation Task and dispose the NamedPipeServerStream // if it ever completes. Once all of the NamedPipeServerStream for the given pipe name are // disposed they will all exit the WaitForConnectionAsync method var connectTask = pipeStream.WaitForConnectionAsync(cancellationToken); if (!PlatformInformation.IsWindows) { var cancelTask = Task.Delay(TimeSpan.FromMilliseconds(-1), cancellationToken); var completedTask = await Task.WhenAny(new[] { connectTask, cancelTask }).ConfigureAwait(false); if (completedTask == cancelTask) { throw new OperationCanceledException(); } } await connectTask.ConfigureAwait(false); CompilerServerLogger.Log("Pipe connection established."); var connection = new NamedPipeClientConnection(pipeStream, getClientLoggingIdentifier()); queue.Enqueue(new ListenResult(connection: connection)); } catch (OperationCanceledException) { // Expected when the host is shutting down. CompilerServerLogger.Log($"Pipe connection cancelled"); pipeStream?.Dispose(); } catch (Exception ex) { CompilerServerLogger.LogException(ex, $"Pipe connection error"); queue.Enqueue(new ListenResult(exception: ex)); pipeStream?.Dispose(); } } }
/// <summary> /// 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."); }
private static async Task <(NamedPipeClientStream Client, NamedPipeServerStream Server)> CreateNamedPipePair() { var pipeName = Guid.NewGuid().ToString("N").Substring(0, 10); var serverStream = NamedPipeUtil.CreateServer(pipeName); var clientStream = NamedPipeUtil.CreateClient(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); var listenTask = serverStream.WaitForConnectionAsync(); await clientStream.ConnectAsync().ConfigureAwait(false); await listenTask.ConfigureAwait(false); return(clientStream, serverStream); }
public async Task ServerShutdownsDuringProcessing() { using (var readyMre = new ManualResetEvent(initialState: false)) using (var doneMre = new ManualResetEvent(initialState: false)) { var pipeName = Guid.NewGuid().ToString(); var mutexName = BuildServerConnection.GetServerMutexName(pipeName); bool created = false; bool connected = false; var thread = new Thread( () => { using (var stream = NamedPipeUtil.CreateServer(pipeName)) { var mutex = new Mutex( initiallyOwned: true, name: mutexName, createdNew: out created ); readyMre.Set(); stream.WaitForConnection(); connected = true; // Client is waiting for a response. Close the mutex now. Then close the connection // so the client gets an error. mutex.ReleaseMutex(); mutex.Dispose(); stream.Close(); doneMre.WaitOne(); } } ); // Block until the mutex and named pipe is setup. thread.Start(); readyMre.WaitOne(); var exitCode = await RunShutdownAsync(pipeName, waitForProcess : false); // Let the fake server exit. doneMre.Set(); thread.Join(); Assert.Equal(CommonCompiler.Succeeded, exitCode); Assert.True(connected); Assert.True(created); } }
public async Task NoServerConnection() { using (var readyMre = new ManualResetEvent(initialState: false)) using (var doneMre = new ManualResetEvent(initialState: false)) { var pipeName = Guid.NewGuid().ToString(); var mutexName = BuildServerConnection.GetServerMutexName(pipeName); bool created = false; bool connected = false; var thread = new Thread( () => { using ( var mutex = new Mutex( initiallyOwned: true, name: mutexName, createdNew: out created ) ) using (var stream = NamedPipeUtil.CreateServer(pipeName)) { readyMre.Set(); // Get a client connection and then immediately close it. Don't give any response. stream.WaitForConnection(); connected = true; stream.Close(); doneMre.WaitOne(); mutex.ReleaseMutex(); } } ); // Block until the mutex and named pipe is setup. thread.Start(); readyMre.WaitOne(); var exitCode = await RunShutdownAsync(pipeName, waitForProcess : false); // Let the fake server exit. doneMre.Set(); thread.Join(); Assert.Equal(CommonCompiler.Failed, exitCode); Assert.True(connected); Assert.True(created); } }