diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs index af02bd7cc3..f16e5dc8ab 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs @@ -109,8 +109,8 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands return false; } - var finished = Task.WhenAny(resultTask, Task.Delay(TimeSpan.FromMinutes(1))); - if (!ReferenceEquals(resultTask, finished)) + var finishedIndex = Task.WaitAny(resultTask, Task.Delay(TimeSpan.FromMinutes(1))); + if (finishedIndex != 0) { Reporter.WriteWarning(Resources.FormatMethodTimedOut(methodName, serviceName, 1)); return false; @@ -121,6 +121,8 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands using (var outStream = File.Create(context.OutputPath)) { stream.CopyTo(outStream); + + outStream.Flush(); } } diff --git a/src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs b/src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs index 177f405ea1..293300d6a8 100644 --- a/src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs +++ b/src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs @@ -177,6 +177,8 @@ namespace Microsoft.Extensions.ApiDescription.Tasks using (var outStream = File.Create(destinationPath)) { await responseStream.CopyToAsync(outStream); + + await outStream.FlushAsync(); } } } diff --git a/src/dotnet-getdocument/Commands/InvokeCommand.cs b/src/dotnet-getdocument/Commands/InvokeCommand.cs index 4d24919c08..359b173235 100644 --- a/src/dotnet-getdocument/Commands/InvokeCommand.cs +++ b/src/dotnet-getdocument/Commands/InvokeCommand.cs @@ -186,8 +186,22 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands { if (cleanupExecutable && !string.IsNullOrEmpty(executable)) { - File.Delete(executable); - File.Delete(executable + ".config"); + // Ignore errors about in-use files. Should still be marked for delete after process cleanup. + try + { + File.Delete(executable); + } + catch (UnauthorizedAccessException) + { + } + + try + { + File.Delete(executable + ".config"); + } + catch (UnauthorizedAccessException) + { + } } } } diff --git a/src/dotnet-getdocument/Exe.cs b/src/dotnet-getdocument/Exe.cs index 08fc4217ba..bc17eee130 100644 --- a/src/dotnet-getdocument/Exe.cs +++ b/src/dotnet-getdocument/Exe.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Diagnostics; using System.Text; @@ -31,20 +32,32 @@ namespace Microsoft.Extensions.ApiDescription.Tool startInfo.WorkingDirectory = workingDirectory; } - var process = Process.Start(startInfo); - - if (interceptOutput) + using (var process = Process.Start(startInfo)) { - string line; - while ((line = process.StandardOutput.ReadLine()) != null) + if (interceptOutput) { - Reporter.WriteVerbose(line); + string line; + while ((line = process.StandardOutput.ReadLine()) != null) + { + Reporter.WriteVerbose(line); + } } + + // Follow precedent set in Razor integration tests and ensure process events and output are complete. + // https://github.com/aspnet/Razor/blob/d719920fdcc7d1db3a6f74cd5404d66fa098f057/test/Microsoft.NET.Sdk.Razor.Test/IntegrationTests/MSBuildProcessManager.cs#L91-L102 + // Timeout is double how long the inside man waits for the IDocumentProcessor to wrap up. + if (!process.WaitForExit((int)(TimeSpan.FromMinutes(2).TotalMilliseconds))) + { + process.Kill(); + + // Should be unreachable in almost every case. + throw new TimeoutException($"Process {executable} timed out after 2 minutes."); + } + + process.WaitForExit(); + + return process.ExitCode; } - - process.WaitForExit(); - - return process.ExitCode; } private static string ToArguments(IReadOnlyList args) diff --git a/src/dotnet-getdocument/Project.cs b/src/dotnet-getdocument/Project.cs index dc4a06ea36..db9734e532 100644 --- a/src/dotnet-getdocument/Project.cs +++ b/src/dotnet-getdocument/Project.cs @@ -89,6 +89,8 @@ namespace Microsoft.Extensions.ApiDescription.Tool // NB: Copy always in case it changes Reporter.WriteVerbose(Resources.FormatWritingFile(targetsPath)); input.CopyTo(output); + + output.Flush(); } } @@ -102,6 +104,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool "/target:WriteServiceProjectReferenceMetadata", "/verbosity:quiet", "/nologo", + "/nodeReuse:false", $"/property:ServiceProjectReferenceMetadataPath={metadataPath}", projectFile, }; @@ -134,8 +137,22 @@ namespace Microsoft.Extensions.ApiDescription.Tool } finally { - File.Delete(metadataPath); - File.Delete(targetsPath); + // Ignore errors about in-use files. Should still be marked for delete after process cleanup. + try + { + File.Delete(metadataPath); + } + catch (UnauthorizedAccessException) + { + } + + try + { + File.Delete(targetsPath); + } + catch (UnauthorizedAccessException) + { + } } var project = new Project