From bb44c02b20ff1afa76ab825a00a0955af497574b Mon Sep 17 00:00:00 2001 From: Patrick Garcia Date: Sat, 23 Sep 2023 16:44:16 +0200 Subject: [PATCH] chore: use AsyncLock instead of regular lock since methods are async --- src/Foundatio/Storage/FolderFileStorage.cs | 19 ++-- src/Foundatio/Storage/InMemoryFileStorage.cs | 95 ++++++++++---------- 2 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/Foundatio/Storage/FolderFileStorage.cs b/src/Foundatio/Storage/FolderFileStorage.cs index 38ca2bc6..aaaadb1a 100644 --- a/src/Foundatio/Storage/FolderFileStorage.cs +++ b/src/Foundatio/Storage/FolderFileStorage.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Foundatio.AsyncEx; using Foundatio.Extensions; using Foundatio.Serializer; using Foundatio.Utility; @@ -12,7 +13,7 @@ namespace Foundatio.Storage { public class FolderFileStorage : IFileStorage { - private readonly object _lockObject = new(); + private readonly AsyncLock _lock = new(); private readonly ISerializer _serializer; protected readonly ILogger _logger; @@ -138,7 +139,7 @@ private Stream CreateFileStream(string filePath) { return File.Create(filePath); } - public Task RenameFileAsync(string path, string newPath, CancellationToken cancellationToken = default) { + public async Task RenameFileAsync(string path, string newPath, CancellationToken cancellationToken = default) { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (String.IsNullOrEmpty(newPath)) @@ -149,7 +150,7 @@ public Task RenameFileAsync(string path, string newPath, CancellationToken _logger.LogInformation("Renaming {Path} to {NewPath}", normalizedPath, normalizedNewPath); try { - lock (_lockObject) { + using (await _lock.LockAsync().AnyContext()) { string directory = Path.GetDirectoryName(normalizedNewPath); if (directory != null) { _logger.LogInformation("Creating {Directory} directory", directory); @@ -170,13 +171,13 @@ public Task RenameFileAsync(string path, string newPath, CancellationToken } } catch (Exception ex) { _logger.LogError(ex, "Error renaming {Path} to {NewPath}", normalizedPath, normalizedNewPath); - return Task.FromResult(false); + return false; } - return Task.FromResult(true); + return true; } - public Task CopyFileAsync(string path, string targetPath, CancellationToken cancellationToken = default) { + public async Task CopyFileAsync(string path, string targetPath, CancellationToken cancellationToken = default) { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (String.IsNullOrEmpty(targetPath)) @@ -187,7 +188,7 @@ public Task CopyFileAsync(string path, string targetPath, CancellationToke _logger.LogInformation("Copying {Path} to {TargetPath}", normalizedPath, normalizedTargetPath); try { - lock (_lockObject) { + using (await _lock.LockAsync().AnyContext()) { string directory = Path.GetDirectoryName(normalizedTargetPath); if (directory != null) { _logger.LogInformation("Creating {Directory} directory", directory); @@ -198,10 +199,10 @@ public Task CopyFileAsync(string path, string targetPath, CancellationToke } } catch (Exception ex) { _logger.LogError(ex, "Error copying {Path} to {TargetPath}: {Message}", normalizedPath, normalizedTargetPath, ex.Message); - return Task.FromResult(false); + return false; } - return Task.FromResult(true); + return true; } public Task DeleteFileAsync(string path, CancellationToken cancellationToken = default) { diff --git a/src/Foundatio/Storage/InMemoryFileStorage.cs b/src/Foundatio/Storage/InMemoryFileStorage.cs index 30947a32..2eb46d56 100644 --- a/src/Foundatio/Storage/InMemoryFileStorage.cs +++ b/src/Foundatio/Storage/InMemoryFileStorage.cs @@ -5,20 +5,21 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Foundatio.Utility; +using Foundatio.AsyncEx; using Foundatio.Extensions; using Foundatio.Serializer; +using Foundatio.Utility; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace Foundatio.Storage { public class InMemoryFileStorage : IFileStorage { private readonly Dictionary> _storage = new(StringComparer.OrdinalIgnoreCase); - private readonly object _lock = new(); + private readonly AsyncLock _lock = new(); private readonly ISerializer _serializer; protected readonly ILogger _logger; - public InMemoryFileStorage() : this(o => o) {} + public InMemoryFileStorage() : this(o => o) { } public InMemoryFileStorage(InMemoryFileStorageOptions options) { if (options == null) @@ -30,7 +31,7 @@ public InMemoryFileStorage(InMemoryFileStorageOptions options) { _logger = options.LoggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; } - public InMemoryFileStorage(Builder config) + public InMemoryFileStorage(Builder config) : this(config(new InMemoryFileStorageOptionsBuilder()).Build()) { } public long MaxFileSize { get; set; } @@ -40,20 +41,20 @@ public InMemoryFileStorage(Builder GetFileStreamAsync(string path, CancellationToken cancellationToken = default) => GetFileStreamAsync(path, FileAccess.Read, cancellationToken); - public Task GetFileStreamAsync(string path, FileAccess fileAccess, CancellationToken cancellationToken = default) { + public async Task GetFileStreamAsync(string path, FileAccess fileAccess, CancellationToken cancellationToken = default) { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); string normalizedPath = path.NormalizePath(); _logger.LogTrace("Getting file stream for {Path}", normalizedPath); - lock (_lock) { + using (await _lock.LockAsync().AnyContext()) { if (!_storage.ContainsKey(normalizedPath)) { _logger.LogError("Unable to get file stream for {Path}: File Not Found", normalizedPath); - return Task.FromResult(null); + return null; } - return Task.FromResult(new MemoryStream(_storage[normalizedPath].Item2)); + return new MemoryStream(_storage[normalizedPath].Item2); } } @@ -71,13 +72,16 @@ public async Task GetFileInfoAsync(string path) { return null; } - public Task ExistsAsync(string path) { + public async Task ExistsAsync(string path) { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); string normalizedPath = path.NormalizePath(); _logger.LogTrace("Checking if {Path} exists", normalizedPath); - return Task.FromResult(_storage.ContainsKey(normalizedPath)); + + using (await _lock.LockAsync().AnyContext()) { + return _storage.ContainsKey(normalizedPath); + } } private static byte[] ReadBytes(Stream input) { @@ -86,7 +90,7 @@ private static byte[] ReadBytes(Stream input) { return ms.ToArray(); } - public Task SaveFileAsync(string path, Stream stream, CancellationToken cancellationToken = default) { + public async Task SaveFileAsync(string path, Stream stream, CancellationToken cancellationToken = default) { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (stream == null) @@ -94,12 +98,12 @@ public Task SaveFileAsync(string path, Stream stream, CancellationToken ca string normalizedPath = path.NormalizePath(); _logger.LogTrace("Saving {Path}", normalizedPath); - + var contents = ReadBytes(stream); if (contents.Length > MaxFileSize) throw new ArgumentException($"File size {contents.Length.ToFileSizeDisplay()} exceeds the maximum size of {MaxFileSize.ToFileSizeDisplay()}."); - lock (_lock) { + using (await _lock.LockAsync().AnyContext()) { _storage[normalizedPath] = Tuple.Create(new FileSpec { Created = SystemClock.UtcNow, Modified = SystemClock.UtcNow, @@ -111,10 +115,10 @@ public Task SaveFileAsync(string path, Stream stream, CancellationToken ca _storage.Remove(_storage.OrderByDescending(kvp => kvp.Value.Item1.Created).First().Key); } - return Task.FromResult(true); + return true; } - public Task RenameFileAsync(string path, string newPath, CancellationToken cancellationToken = default) { + public async Task RenameFileAsync(string path, string newPath, CancellationToken cancellationToken = default) { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (String.IsNullOrEmpty(newPath)) @@ -123,11 +127,11 @@ public Task RenameFileAsync(string path, string newPath, CancellationToken string normalizedPath = path.NormalizePath(); string normalizedNewPath = newPath.NormalizePath(); _logger.LogInformation("Renaming {Path} to {NewPath}", normalizedPath, normalizedNewPath); - - lock (_lock) { + + using (await _lock.LockAsync().AnyContext()) { if (!_storage.ContainsKey(normalizedPath)) { _logger.LogDebug("Error renaming {Path} to {NewPath}: File not found", normalizedPath, normalizedNewPath); - return Task.FromResult(false); + return false; } _storage[normalizedNewPath] = _storage[normalizedPath]; @@ -136,10 +140,10 @@ public Task RenameFileAsync(string path, string newPath, CancellationToken _storage.Remove(normalizedPath); } - return Task.FromResult(true); + return true; } - public Task CopyFileAsync(string path, string targetPath, CancellationToken cancellationToken = default) { + public async Task CopyFileAsync(string path, string targetPath, CancellationToken cancellationToken = default) { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); if (String.IsNullOrEmpty(targetPath)) @@ -148,11 +152,11 @@ public Task CopyFileAsync(string path, string targetPath, CancellationToke string normalizedPath = path.NormalizePath(); string normalizedTargetPath = targetPath.NormalizePath(); _logger.LogInformation("Copying {Path} to {TargetPath}", normalizedPath, normalizedTargetPath); - - lock (_lock) { + + using (await _lock.LockAsync().AnyContext()) { if (!_storage.ContainsKey(normalizedPath)) { _logger.LogDebug("Error copying {Path} to {TargetPath}: File not found", normalizedPath, normalizedTargetPath); - return Task.FromResult(false); + return false; } _storage[normalizedTargetPath] = _storage[normalizedPath]; @@ -160,60 +164,61 @@ public Task CopyFileAsync(string path, string targetPath, CancellationToke _storage[normalizedTargetPath].Item1.Modified = SystemClock.UtcNow; } - return Task.FromResult(true); + return true; } - public Task DeleteFileAsync(string path, CancellationToken cancellationToken = default) { + public async Task DeleteFileAsync(string path, CancellationToken cancellationToken = default) { if (String.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path)); string normalizedPath = path.NormalizePath(); _logger.LogTrace("Deleting {Path}", normalizedPath); - - lock (_lock) { + + using (await _lock.LockAsync().AnyContext()) { if (!_storage.ContainsKey(normalizedPath)) { _logger.LogError("Unable to delete {Path}: File not found", normalizedPath); - return Task.FromResult(false); + return false; } _storage.Remove(normalizedPath); } - return Task.FromResult(true); + return true; } - public Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellation = default) { + public async Task DeleteFilesAsync(string searchPattern = null, CancellationToken cancellation = default) { if (String.IsNullOrEmpty(searchPattern) || searchPattern == "*") { - lock(_lock) + using (await _lock.LockAsync().AnyContext()) { _storage.Clear(); + } - return Task.FromResult(0); + return 0; } searchPattern = searchPattern.NormalizePath(); int count = 0; - if (searchPattern[searchPattern.Length - 1] == Path.DirectorySeparatorChar) + if (searchPattern[searchPattern.Length - 1] == Path.DirectorySeparatorChar) searchPattern = $"{searchPattern}*"; else if (!searchPattern.EndsWith(Path.DirectorySeparatorChar + "*") && !Path.HasExtension(searchPattern)) searchPattern = Path.Combine(searchPattern, "*"); var regex = new Regex($"^{Regex.Escape(searchPattern).Replace("\\*", ".*?")}$"); - - lock (_lock) { + + using (await _lock.LockAsync().AnyContext()) { var keys = _storage.Keys.Where(k => regex.IsMatch(k)).Select(k => _storage[k].Item1).ToList(); - + _logger.LogInformation("Deleting {FileCount} files matching {SearchPattern} (Regex={SearchPatternRegex})", keys.Count, searchPattern, regex); foreach (var key in keys) { _logger.LogTrace("Deleting {Path}", key.Path); _storage.Remove(key.Path); count++; } - + _logger.LogTrace("Finished deleting {FileCount} files matching {SearchPattern}", count, searchPattern); } - return Task.FromResult(count); + return count; } public async Task GetPagedFileListAsync(int pageSize = 100, string searchPattern = null, CancellationToken cancellationToken = default) { @@ -225,21 +230,21 @@ public async Task GetPagedFileListAsync(int pageSize = 100, searchPattern = searchPattern.NormalizePath(); - var result = new PagedFileListResult(s => Task.FromResult(GetFiles(searchPattern, 1, pageSize))); + var result = new PagedFileListResult(async s => await GetFilesAsync(searchPattern, 1, pageSize, cancellationToken)); await result.NextPageAsync().AnyContext(); return result; } - private NextPageResult GetFiles(string searchPattern, int page, int pageSize) { + private async Task GetFilesAsync(string searchPattern, int page, int pageSize, CancellationToken cancellationToken = default) { var list = new List(); int pagingLimit = pageSize; int skip = (page - 1) * pagingLimit; if (pagingLimit < Int32.MaxValue) pagingLimit++; - + var regex = new Regex($"^{Regex.Escape(searchPattern).Replace("\\*", ".*?")}$"); - lock (_lock) { + using (await _lock.LockAsync().AnyContext()) { _logger.LogTrace(s => s.Property("Limit", pagingLimit).Property("Skip", skip), "Getting file list matching {SearchPattern}...", regex); list.AddRange(_storage.Keys.Where(k => regex.IsMatch(k)).Select(k => _storage[k].Item1.DeepClone()).Skip(skip).Take(pagingLimit).ToList()); } @@ -251,10 +256,10 @@ private NextPageResult GetFiles(string searchPattern, int page, int pageSize) { } return new NextPageResult { - Success = true, - HasMore = hasMore, + Success = true, + HasMore = hasMore, Files = list, - NextPageFunc = hasMore ? _ => Task.FromResult(GetFiles(searchPattern, page + 1, pageSize)) : null + NextPageFunc = hasMore ? async _ => await GetFilesAsync(searchPattern, page + 1, pageSize, cancellationToken) : null }; }