/// <summary> /// Connect to the pipe for a given directory and return it. /// Throws on cancellation. /// </summary> /// <param name="pipeName">Name of the named pipe to connect to.</param> /// <param name="timeoutMs">Timeout to allow in connecting to process.</param> /// <param name="cancellationToken">Cancellation token to cancel connection to server.</param> /// <returns> /// An open <see cref="NamedPipeClientStream"/> to the server process or null on failure. /// </returns> internal static async Task <NamedPipeClientStream> TryConnectToServerAsync( string pipeName, int timeoutMs, CancellationToken cancellationToken) { NamedPipeClientStream pipeStream; try { // If the pipe path would be too long, there cannot be a server at the other end. // We're not using a saved temp path here because pipes are created with // Path.GetTempPath() in corefx NamedPipeClientStream and we want to replicate that behavior. if (IsPipePathTooLong(pipeName, Path.GetTempPath())) { return(null); } // 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. Log("Attempt to open named pipe '{0}'", pipeName); pipeStream = NamedPipeUtil.CreateClient(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); cancellationToken.ThrowIfCancellationRequested(); Log("Attempt to connect named pipe '{0}'", pipeName); try { await pipeStream.ConnectAsync(timeoutMs, cancellationToken).ConfigureAwait(false); } 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. Log($"Connecting to server timed out after {timeoutMs} ms"); return(null); } Log("Named pipe '{0}' connected", pipeName); cancellationToken.ThrowIfCancellationRequested(); // Verify that we own the pipe. if (!NamedPipeUtil.CheckPipeConnectionOwnership(pipeStream)) { Log("Owner of named pipe is incorrect"); return(null); } return(pipeStream); } catch (Exception e) when(!(e is TaskCanceledException || e is OperationCanceledException)) { LogException(e, "Exception while connecting to process"); return(null); } }
/// <summary> /// Connect to the pipe for a given directory and return it. /// Throws on cancellation. /// </summary> /// <param name="pipeName">Name of the named pipe to connect to.</param> /// <param name="timeoutMs">Timeout to allow in connecting to process.</param> /// <param name="cancellationToken">Cancellation token to cancel connection to server.</param> /// <returns> /// An open <see cref="NamedPipeClientStream"/> to the server process or null on failure. /// </returns> internal static async Task <NamedPipeClientStream> TryConnectToServerAsync( string pipeName, int timeoutMs, CancellationToken cancellationToken) { NamedPipeClientStream pipeStream; 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. Log("Attempt to open named pipe '{0}'", pipeName); pipeStream = NamedPipeUtil.CreateClient(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); cancellationToken.ThrowIfCancellationRequested(); Log("Attempt to connect named pipe '{0}'", pipeName); try { // NamedPipeClientStream.ConnectAsync on the "full" framework has a bug where it // tries to move potentially expensive work (actually connecting to the pipe) to // a background thread with Task.Factory.StartNew. However, that call will merely // queue the work onto the TaskScheduler associated with the "current" Task which // does not guarantee it will be processed on a background thread and this could // lead to a hang. // To avoid this, we first force ourselves to a background thread using Task.Run. // This ensures that the Task created by ConnectAsync will run on the default // TaskScheduler (i.e., on a threadpool thread) which was the intent all along. await Task.Run(() => pipeStream.ConnectAsync(timeoutMs, cancellationToken)).ConfigureAwait(false); } 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. Log($"Connecting to server timed out after {timeoutMs} ms"); return(null); } Log("Named pipe '{0}' connected", pipeName); cancellationToken.ThrowIfCancellationRequested(); // Verify that we own the pipe. if (!NamedPipeUtil.CheckPipeConnectionOwnership(pipeStream)) { Log("Owner of named pipe is incorrect"); return(null); } return(pipeStream); } catch (Exception e) when(!(e is TaskCanceledException || e is OperationCanceledException)) { LogException(e, "Exception while connecting to process"); return(null); } }