diff --git a/ReleaseHistory.md b/ReleaseHistory.md index 2d169fffe..b3818a721 100644 --- a/ReleaseHistory.md +++ b/ReleaseHistory.md @@ -1,4 +1,8 @@ # SARIF Package Release History (SDK, Driver, Converters, and Multitool) + +## UNRELEASED +* BUG: Resolve process hangs when a file path is provided with a wildcard, but without a `-r` (recurse) flag during the multi-threaded analysis file enumeration phase. + ## **v4.5.4 [Sdk](https://www.nuget.org/packages/Sarif.Sdk/v4.5.4) | [Driver](https://www.nuget.org/packages/Sarif.Driver/v4.5.4) | [Converters](https://www.nuget.org/packages/Sarif.Converters/v4.5.4) | [Multitool](https://www.nuget.org/packages/Sarif.Multitool/v4.5.4) | [Multitool Library](https://www.nuget.org/packages/Sarif.Multitool.Library/v4.5.4) * BUG: Fix incorrect base class in rule ADO2012. diff --git a/src/Sarif.Driver/OrderedFileSpecifier.cs b/src/Sarif.Driver/OrderedFileSpecifier.cs index 5f3dc78c1..0498d8e2e 100644 --- a/src/Sarif.Driver/OrderedFileSpecifier.cs +++ b/src/Sarif.Driver/OrderedFileSpecifier.cs @@ -26,7 +26,7 @@ public OrderedFileSpecifier(string specifier, FileSystem = fileSystem ?? Sarif.FileSystem.Instance; } - private const int ChannelCapacity = 10 * 1024; // max ~2.5 MB memory given 256 char max path length. + internal const int ChannelCapacity = 10 * 1024; // max ~2.5 MB memory given 256 char max path length. private readonly bool recurse; private readonly string specifier; private readonly long maxFileSizeInKilobytes; @@ -87,27 +87,24 @@ private IEnumerable EnumeratedArtifacts() Task directoryEnumerationTask; - if (this.recurse) + directoryEnumerationTask = Task.Run(() => { - directoryEnumerationTask = Task.Run(() => + try { - try + if (this.recurse) { EnqueueAllFilesUnderDirectory(directory, filesToProcessChannel.Writer, filter, new SortedSet(StringComparer.Ordinal)); } - finally + else { - filesToProcessChannel.Writer.Complete(); + WriteFilesInDirectoryToChannel(directory, filesToProcessChannel, filter, new SortedSet(StringComparer.Ordinal)); } - }); - } - else - { - WriteFilesInDirectoryToChannel(directory, filesToProcessChannel, filter, new SortedSet(StringComparer.Ordinal)); - - filesToProcessChannel.Writer.Complete(); - directoryEnumerationTask = Task.CompletedTask; - } + } + finally + { + filesToProcessChannel.Writer.Complete(); + } + }); ChannelReader reader = filesToProcessChannel.Reader; while (!reader.Completion.IsCompleted) diff --git a/src/Test.FunctionalTests.Sarif/MultitoolCommandLineTests.cs b/src/Test.FunctionalTests.Sarif/MultitoolCommandLineTests.cs index 1b7691c1a..0fd37f4ff 100644 --- a/src/Test.FunctionalTests.Sarif/MultitoolCommandLineTests.cs +++ b/src/Test.FunctionalTests.Sarif/MultitoolCommandLineTests.cs @@ -5,6 +5,9 @@ using System.IO; using FluentAssertions; +using FluentAssertions.Execution; + +using Microsoft.CodeAnalysis.Sarif.Driver; using Xunit; @@ -35,5 +38,66 @@ public void Multitool_LaunchesAndRunsSuccessfully() process.ExitCode.Should().Be(0); } } + +#if DEBUG + [Fact] + [Trait(TestTraits.WindowsOnly, "true")] + public void Multitool_LaunchesAndRunsSuccessfully_WithNumberOfFilesExceedingChannelCapacity() + { + using var assertionScope = new AssertionScope(); + + string multitoolPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), + @"..\..\Sarif.Multitool\netcoreapp3.1\Sarif.Multitool.exe")); + + string directoryPath = Path.Combine(Path.GetTempPath(), "SarifMultitoolTestFilesWithNumberOfFilesExceedingChannelCapacity"); + + if (Directory.Exists(directoryPath)) + { + Directory.Delete(directoryPath, true); + } + + Directory.CreateDirectory(directoryPath); + + int fileCount = OrderedFileSpecifier.ChannelCapacity + 1; + + for (int i = 1; i <= fileCount; i++) + { + string filename = $"file_{i}.txt"; + string filepath = Path.Combine(directoryPath, filename); + File.WriteAllText(filepath, " "); + } + + var startInfo = new ProcessStartInfo(multitoolPath, $@"analyze-test {directoryPath}\*") + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + RedirectStandardOutput = true, + UseShellExecute = false + }; + + using (var process = Process.Start(startInfo)) + { + var timer = new System.Timers.Timer(30000); // 30 seconds. + timer.Elapsed += (sender, e) => + { + if (!process.HasExited) + { + process.Kill(); + } + }; + timer.Start(); + string output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + timer.Stop(); + process.ExitCode.Should().Be(0); + output.Should().Contain( + $"Done. {fileCount:n0} files scanned.", + $"analyzing {fileCount:n0} small files should not result in freezing and should finish within 30 seconds, " + + "typically completing in just 5 seconds"); + } + + Directory.Delete(directoryPath, true); + } +#endif } }