perf: add buffering to `FileWritingService` (#435)

This commit is contained in:
Justin Perez 2023-02-17 09:04:43 -08:00 коммит произвёл GitHub
Родитель 6ad4dfc89a
Коммит 9df530aa70
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 46 добавлений и 4 удалений

Просмотреть файл

@ -1,14 +1,17 @@
namespace Microsoft.ComponentDetection.Common;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading.Tasks;
using Microsoft.ComponentDetection.Common.Exceptions;
public class FileWritingService : IFileWritingService
public sealed class FileWritingService : IFileWritingService
{
public const string TimestampFormatString = "yyyyMMddHHmmssfff";
private readonly object lockObject = new object();
private readonly string timestamp = DateTime.Now.ToString(TimestampFormatString);
private readonly ConcurrentDictionary<string, StreamWriter> bufferedStreams = new();
public string BasePath { get; private set; }
@ -26,10 +29,13 @@ public class FileWritingService : IFileWritingService
{
relativeFilePath = this.ResolveFilePath(relativeFilePath);
lock (this.lockObject)
if (!this.bufferedStreams.TryGetValue(relativeFilePath, out var streamWriter))
{
File.AppendAllText(relativeFilePath, text);
streamWriter = new StreamWriter(relativeFilePath, true);
this.bufferedStreams.TryAdd(relativeFilePath, streamWriter);
}
streamWriter.Write(text);
}
public void WriteFile(string relativeFilePath, string text)
@ -66,4 +72,33 @@ public class FileWritingService : IFileWritingService
throw new InvalidOperationException("Base path has not yet been initialized in File Writing Service!");
}
}
private void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
foreach (var (filename, streamWriter) in this.bufferedStreams)
{
streamWriter.Dispose();
this.bufferedStreams.TryRemove(filename, out _);
}
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
public async ValueTask DisposeAsync()
{
foreach (var (filename, streamWriter) in this.bufferedStreams)
{
await streamWriter.DisposeAsync();
this.bufferedStreams.TryRemove(filename, out _);
}
}
}

Просмотреть файл

@ -1,8 +1,10 @@
namespace Microsoft.ComponentDetection.Common;
using System;
using System.IO;
// All file paths are relative and will replace occurrences of {timestamp} with the shared file timestamp.
public interface IFileWritingService
public interface IFileWritingService : IDisposable, IAsyncDisposable
{
void Init(string basePath);

Просмотреть файл

@ -32,6 +32,9 @@ try
Console.WriteLine($"Execution finished, status: {exitCode}.");
// Manually dispose to flush logs as we force exit
await serviceProvider.DisposeAsync();
// force an exit, not letting any lingering threads not responding.
Environment.Exit(exitCode);
}

Просмотреть файл

@ -41,6 +41,7 @@ public class FileWritingServiceTests
var fileLocation = Path.Combine(this.tempFolder, relativeDir);
File.Create(fileLocation).Dispose();
this.serviceUnderTest.AppendToFile(relativeDir, "someSampleText");
this.serviceUnderTest.Dispose();
var text = File.ReadAllText(Path.Combine(this.tempFolder, relativeDir));
text
.Should().Be("someSampleText");
@ -62,6 +63,7 @@ public class FileWritingServiceTests
var relativeDir = "somefile_{timestamp}.txt";
this.serviceUnderTest.WriteFile(relativeDir, "sampleText");
this.serviceUnderTest.AppendToFile(relativeDir, "sampleText2");
this.serviceUnderTest.Dispose();
var files = Directory.GetFiles(this.tempFolder);
files
.Should().NotBeEmpty();