/// <summary> /// Creates a pipe in which the specified ends are inheritable by child processes. /// In order for the pipe to be closable by the child, the inherited end should be closed after process creation. /// </summary> /// <remarks> /// This implementation creates a named pipe instance and immediately connects it. This is effectively the same implementation /// as <c>CreatePipe</c> (see %SDXROOT%\minkernel\kernelbase\pipe.c), but we compute a random pipe named rather than using the special /// 'anonymous' naming case of opening \\.\pipe or \Device\NamedPipe\ (without a subsequent path). /// We choose to not use <c>CreatePipe</c> since it opens both sides for synchronous I/O. Instead, for running build tools we want the /// BuildXL side opened for overlapped I/O and the build tool side opened for synchronous I/O (if the build tool side acts as a redirected /// console handle; recall that synchronous read and write attempts will fail on an async handle, which motivates the <c>CreatePipe</c> defaults /// in the first place). This is significant since many build tool console pipes are forever silent, and with sync handles we'd need N * 3 threads /// (stdin, stdout, stderr for N concurrent processes) to pessimistically drain them. /// </remarks> public static void CreateInheritablePipe(PipeInheritance inheritance, PipeFlags flags, out SafeFileHandle readHandle, out SafeFileHandle writeHandle) { string pipeName = @"\\.\pipe\BuildXL-" + Guid.NewGuid().ToString("N"); writeHandle = ProcessUtilities.CreateNamedPipe( pipeName, PipeOpenMode.PipeAccessOutbound | (((flags & PipeFlags.WriteSideAsync) != 0) ? PipeOpenMode.FileFlagOverlapped : 0), PipeMode.PipeTypeByte | PipeMode.PipeRejectRemoteClients, nMaxInstances: 1, nOutBufferSize: PipeBufferSize, nInBufferSize: PipeBufferSize, nDefaultTimeout: 0, lpSecurityAttributes: IntPtr.Zero); if (writeHandle.IsInvalid) { throw new NativeWin32Exception(Marshal.GetLastWin32Error(), "CreateNamedPipeW failed"); } var readSideFlags = FileFlagsAndAttributes.SecurityAnonymous; if ((flags & PipeFlags.ReadSideAsync) != 0) { readSideFlags |= FileFlagsAndAttributes.FileFlagOverlapped; } OpenFileResult openReadSideResult = FileUtilities.TryCreateOrOpenFile( pipeName, FileDesiredAccess.GenericRead, FileShare.None, FileMode.Open, readSideFlags, out readHandle); if (!openReadSideResult.Succeeded) { throw openReadSideResult.CreateExceptionForError(); } if ((inheritance & PipeInheritance.InheritRead) != 0) { SetInheritable(readHandle); } if ((inheritance & PipeInheritance.InheritWrite) != 0) { SetInheritable(writeHandle); } }
/// <summary> /// Creates a pipe in which the specified ends are inheritable by child processes. /// In order for the pipe to be closable by the child, the inherited end should be closed after process creation. /// </summary> /// <remarks> /// This implementation creates a named pipe instance and immediately connects it. This is effectively the same implementation /// as <c>CreatePipe</c> (see %SDXROOT%\minkernel\kernelbase\pipe.c), but we compute a random pipe named rather than using the special /// 'anonymous' naming case of opening \\.\pipe or \Device\NamedPipe\ (without a subsequent path). /// We choose to not use <c>CreatePipe</c> since it opens both sides for synchronous I/O. Instead, for running build tools we want the /// BuildXL side opened for overlapped I/O and the build tool side opened for synchronous I/O (if the build tool side acts as a redirected /// console handle; recall that synchronous read and write attempts will fail on an async handle, which motivates the <c>CreatePipe</c> defaults /// in the first place). This is significant since many build tool console pipes are forever silent, and with sync handles we'd need N * 3 threads /// (stdin, stdout, stderr for N concurrent processes) to pessimistically drain them. /// </remarks> public static void CreateInheritablePipe(PipeInheritance inheritance, PipeFlags flags, out SafeFileHandle readHandle, out SafeFileHandle writeHandle) { string pipeName = @"\\.\pipe\BuildXL-" + Guid.NewGuid().ToString("N"); writeHandle = ProcessUtilities.CreateNamedPipe( pipeName, PipeOpenMode.PipeAccessOutbound | (((flags & PipeFlags.WriteSideAsync) != 0) ? PipeOpenMode.FileFlagOverlapped : 0), PipeMode.PipeTypeByte | PipeMode.PipeRejectRemoteClients, nMaxInstances: 1, nOutBufferSize: PipeBufferSize, nInBufferSize: PipeBufferSize, nDefaultTimeout: 0, lpSecurityAttributes: IntPtr.Zero); if (writeHandle.IsInvalid) { throw new NativeWin32Exception(Marshal.GetLastWin32Error(), "CreateNamedPipeW failed"); } var readSideFlags = FileFlagsAndAttributes.SecurityAnonymous; if ((flags & PipeFlags.ReadSideAsync) != 0) { readSideFlags |= FileFlagsAndAttributes.FileFlagOverlapped; } int maxRetry = 3; while (true) { OpenFileResult openReadSideResult = FileUtilities.TryCreateOrOpenFile( pipeName, FileDesiredAccess.GenericRead, FileShare.None, FileMode.Open, readSideFlags, out readHandle); if (openReadSideResult.Succeeded) { break; } if (openReadSideResult.NativeErrorCode != NativeIOConstants.ErrorPipeBusy || maxRetry == 0) { throw openReadSideResult.CreateExceptionForError(); } bool success = false; // Wait for at most 5s. for (int i = 0; i < 10; ++i) { success = ProcessUtilities.WaitNamedPipe(pipeName, 500); if (success) { break; } } if (!success) { // After waiting for 5s, pipe is still not ready. throw new NativeWin32Exception(Marshal.GetLastWin32Error(), "WaitNamedPipe"); } --maxRetry; } if ((inheritance & PipeInheritance.InheritRead) != 0) { SetInheritable(readHandle); } if ((inheritance & PipeInheritance.InheritWrite) != 0) { SetInheritable(writeHandle); } }