Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
lilith committed Jan 27, 2024
1 parent 7b420ae commit dfda9ff
Show file tree
Hide file tree
Showing 37 changed files with 370 additions and 417 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/dotnet-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
uses: actions/setup-dotnet@v3
if: contains(env.DOTNET_VERSION_LIST, '6.0.') == 'false' || contains(env.DOTNET_VERSION_LIST, '7.0.') == 'false' || contains(env.DOTNET_VERSION_LIST, '8.0.') == 'false'
with:
dotnet-version: '${{ runner.dotnet }}'
dotnet-version: "6\n7\n8\n"

- name: Setup .NET 4.8.1 if on windows
uses: actions/setup-dotnet@v3
Expand Down Expand Up @@ -129,7 +129,7 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v4
id: nuget-artifact-upload-step
if: matrix.pack
if: npack
with:
name: NuGetPackages
path: NuGetPackages/Release/*.nupkg
Expand Down
8 changes: 1 addition & 7 deletions Imageflow.Server.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29503.13
Expand Down Expand Up @@ -60,8 +60,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imageflow.Server.Host", "sr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imageflow.Server.Configuration.Tests", "tests\Imageflow.Server.Configuration.Tests\Imageflow.Server.Configuration.Tests.csproj", "{200CD4DA-C49B-413B-8FCF-8DA73A2803E0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imazen.LegacyDotNetCompatTests", "tests\Imazen.LegacyDotNetCompatTests\Imazen.LegacyDotNetCompatTests.csproj", "{8EF6CC3F-027C-486C-B30B-09773E0C7966}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imazen.Routing", "src\Imazen.Routing\Imazen.Routing.csproj", "{5EF0F9EA-D6DE-4E34-9141-4C44DA08A726}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Imazen.Abstractions", "src\Imazen.Abstractions\Imazen.Abstractions.csproj", "{A04B9BE0-4931-4305-B9AB-B79737130F20}"
Expand Down Expand Up @@ -152,10 +150,6 @@ Global
{200CD4DA-C49B-413B-8FCF-8DA73A2803E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{200CD4DA-C49B-413B-8FCF-8DA73A2803E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{200CD4DA-C49B-413B-8FCF-8DA73A2803E0}.Release|Any CPU.Build.0 = Release|Any CPU
{8EF6CC3F-027C-486C-B30B-09773E0C7966}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8EF6CC3F-027C-486C-B30B-09773E0C7966}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8EF6CC3F-027C-486C-B30B-09773E0C7966}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8EF6CC3F-027C-486C-B30B-09773E0C7966}.Release|Any CPU.Build.0 = Release|Any CPU
{5EF0F9EA-D6DE-4E34-9141-4C44DA08A726}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5EF0F9EA-D6DE-4E34-9141-4C44DA08A726}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5EF0F9EA-D6DE-4E34-9141-4C44DA08A726}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\NugetPackageDefaults.targets" />
<Import Project="..\NugetPackages.targets" />
<!-- Override net framework version -->
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand Down
4 changes: 2 additions & 2 deletions src/Imageflow.Server.Storage.AzureBlob/AzureBlobService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ public async Task<CodeResult<IBlobWrapper>> Fetch(string virtualPath)
var blobClient = containerClient.GetBlobClient(key);
var reference = new AzureBlobStorageReference(containerClient.Uri.AbsoluteUri, key);
var s = await blobClient.DownloadStreamingAsync();

return CodeResult<IBlobWrapper>.Ok(new BlobWrapper(new AzureBlob(reference, s.Value)));
var latencyZone = new LatencyTrackingZone($"azure::blob/{mapping.Container}", 100);
return CodeResult<IBlobWrapper>.Ok(new BlobWrapper(latencyZone,new AzureBlob(reference, s.Value)));

}
catch (RequestFailedException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ public async Task<IResult<IBlobWrapper, IBlobCacheFetchFailure>> CacheFetch(IBlo
{
var response = await blob.DownloadStreamingAsync(new BlobDownloadOptions(), cancellationToken);
SetContainerExists(groupConfig.Location.ContainerName, true);
return BlobCacheFetchFailure.OkResult(new BlobWrapper(new AzureBlob(storage, response)));
return BlobCacheFetchFailure.OkResult(new BlobWrapper(null,new AzureBlob(storage, response)));

}
catch (Azure.RequestFailedException ex)
Expand Down
3 changes: 2 additions & 1 deletion src/Imageflow.Server.Storage.S3/Caching/S3BlobCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,9 @@ public async Task<BlobFetchResult> CacheFetch(IBlobCacheRequest request, Cancell
var result = await client.GetObjectAsync(req, cancellationToken);
if (result.HttpStatusCode == HttpStatusCode.OK)
{
var latencyZone = new LatencyTrackingZone($"s3::bucket/{bucket}", 100);
return BlobCacheFetchFailure.OkResult(
new BlobWrapper(new S3Blob(result)));
new BlobWrapper(latencyZone,new S3Blob(result)));
}

// 404/403 are cache misses and return these
Expand Down
3 changes: 2 additions & 1 deletion src/Imageflow.Server.Storage.S3/S3Service.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ public async Task<CodeResult<IBlobWrapper>> Fetch(string virtualPath)
var client = mapping.S3Client ?? this.s3Client;
var req = new Amazon.S3.Model.GetObjectRequest() { BucketName = mapping.Bucket, Key = key };

var latencyZone = new LatencyTrackingZone($"s3::bucket/{mapping.Bucket}", 100);
var s = await client.GetObjectAsync(req);
return new BlobWrapper(new S3Blob(s));
return new BlobWrapper(latencyZone,new S3Blob(s));

} catch (AmazonS3Exception se) {
if (se.StatusCode == System.Net.HttpStatusCode.NotFound || "NoSuchKey".Equals(se.ErrorCode, StringComparison.OrdinalIgnoreCase))
Expand Down
2 changes: 1 addition & 1 deletion src/Imageflow.Server/Imageflow.Server.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\NugetPackageDefaults.targets" />
<Import Project="..\NugetPackages.targets" />

<PropertyGroup>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
Expand Down
20 changes: 17 additions & 3 deletions src/Imazen.Abstractions/Blobs/BlobWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,32 @@ public class BlobWrapper : IBlobWrapper
{
private IConsumableBlob? consumable;
private IReusableBlob? reusable;
internal DateTime CreatedAtUtc { get; }
internal LatencyTrackingZone? LatencyZone { get; set; }

public BlobWrapper(IConsumableBlob consumable)
public BlobWrapper(LatencyTrackingZone? latencyZone, IConsumableBlob consumable)
{
this.consumable = consumable;
this.Attributes = consumable.Attributes;
CreatedAtUtc = DateTime.UtcNow;
LatencyZone = latencyZone;
}

public BlobWrapper(IReusableBlob reusable)
public BlobWrapper(LatencyTrackingZone? latencyZone, IReusableBlob reusable)
{
this.reusable = reusable;
this.Attributes = reusable.Attributes;
CreatedAtUtc = DateTime.UtcNow;
LatencyZone = latencyZone;
}
[Obsolete("Use the constructor that takes a first parameter of LatencyTrackingZone")]
public BlobWrapper(IConsumableBlob consumable)
{
this.consumable = consumable;
this.Attributes = consumable.Attributes;
CreatedAtUtc = DateTime.UtcNow;
}



public IBlobAttributes Attributes { get; }
public bool IsNativelyReusable => reusable != null;
Expand Down
2 changes: 1 addition & 1 deletion src/Imazen.Abstractions/Blobs/LatencyTrackingZone.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ namespace Imazen.Abstractions.Blobs;
/// </summary>
/// <param name="TrackingZone"></param>
public record LatencyTrackingZone(
string TrackingZone, int DefaultMs);
string TrackingZone, int DefaultMs, bool AlwaysShield = false);
5 changes: 4 additions & 1 deletion src/Imazen.Abstractions/Blobs/ReusableArraySegmentBlob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ public class ReusableArraySegmentBlob : IReusableBlob
public IBlobAttributes Attributes { get; }
private readonly ArraySegment<byte> data;
private bool disposed = false;
public TimeSpan CreationDuration { get; init; }
public DateTime CreationCompletionUtc { get; init; } = DateTime.UtcNow;

public ReusableArraySegmentBlob(ArraySegment<byte> data, IBlobAttributes metadata)
public ReusableArraySegmentBlob(ArraySegment<byte> data, IBlobAttributes metadata, TimeSpan creationDuration)
{
CreationDuration = creationDuration;
this.data = data;
this.Attributes = metadata;
// Precalculate since it will be called often
Expand Down
7 changes: 6 additions & 1 deletion src/Imazen.Abstractions/Blobs/SimpleReusableBlobFactory.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System.Diagnostics;
using System.Net.Http.Headers;

namespace Imazen.Abstractions.Blobs;

/// <summary>
Expand All @@ -22,6 +25,7 @@ public async ValueTask<IReusableBlob> ConsumeAndCreateReusableCopy(IConsumableBl
{
using (consumableBlob)
{
var sw = Stopwatch.StartNew();
#if NETSTANDARD2_1_OR_GREATER
await using var stream = consumableBlob.TakeStream();
#else
Expand All @@ -32,7 +36,8 @@ public async ValueTask<IReusableBlob> ConsumeAndCreateReusableCopy(IConsumableBl
ms.Position = 0;
var byteArray = ms.ToArray();
var arraySegment = new ArraySegment<byte>(byteArray);
var reusable = new ReusableArraySegmentBlob(arraySegment, consumableBlob.Attributes);
sw.Stop();
var reusable = new ReusableArraySegmentBlob(arraySegment, consumableBlob.Attributes, sw.Elapsed);
return reusable;
}
}
Expand Down
12 changes: 7 additions & 5 deletions src/Imazen.HybridCache/AsyncCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ public AsyncCache(AsyncCacheOptions options, ICacheCleanupManager cleanupManager
SubscribesToFreshResults = true,
RequiresInlineExecution = false
};
LatencyZone = new LatencyTrackingZone($"Hybrid Disk Cache ('{UniqueName}')", 30);
}

ICacheDatabase<ICacheDatabaseRecord> Database { get; }


private LatencyTrackingZone LatencyZone { get; set; }

private AsyncCacheOptions Options { get; }
private HashBasedPathBuilder PathBuilder { get; }
private ILogger Logger { get; }
Expand Down Expand Up @@ -178,7 +180,7 @@ private static bool IsFileLocked(IOException exception)
if (openedStream != null)
{
//TODO: add contended hit detail
return AsyncCacheResult.FromHit(record, entry.RelativePath, PathBuilder, openedStream, this, this);
return AsyncCacheResult.FromHit(record, entry.RelativePath, PathBuilder, openedStream, LatencyZone, this, this);
}

return null;
Expand All @@ -203,7 +205,7 @@ private static bool IsFileLocked(IOException exception)
{
return AsyncCacheResult.FromHit(record, entry.RelativePath, PathBuilder, new FileStream(
entry.PhysicalPath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096,
FileOptions.Asynchronous | FileOptions.SequentialScan), this, this);
FileOptions.Asynchronous | FileOptions.SequentialScan), LatencyZone, this, this);

}
catch (FileNotFoundException)
Expand Down Expand Up @@ -747,7 +749,7 @@ private static class AsyncCacheResult

internal static IResult<IBlobWrapper, IBlobCacheFetchFailure> FromHit(ICacheDatabaseRecord? record,
string entryRelativePath, HashBasedPathBuilder interpreter,
FileStream stream, IBlobCache notifyOfResult, IBlobCache notifyOfExternalHit)
FileStream stream, LatencyTrackingZone latencyZone, IBlobCache notifyOfResult, IBlobCache notifyOfExternalHit)
{
var blob = new ConsumableBlob(new BlobAttributes()
{
Expand All @@ -759,7 +761,7 @@ internal static IResult<IBlobWrapper, IBlobCacheFetchFailure> FromHit(ICacheData
BlobStorageReference = new FileBlobStorageReference(entryRelativePath, interpreter, record)

}, stream);
return Result<IBlobWrapper, IBlobCacheFetchFailure>.Ok(new BlobWrapper(blob));
return Result<IBlobWrapper, IBlobCacheFetchFailure>.Ok( new BlobWrapper(latencyZone, blob));

}

Expand Down
30 changes: 15 additions & 15 deletions src/Imazen.Routing/Caching/MemoryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static async IAsyncEnumerable<T> AsAsyncEnumerable<T>(this IEnumerable<T>
/// Items that are under the MinKeepNewItemsFor grace period should be kept in a separate list.
/// They can graduate to the main list if they are accessed more than x times
/// </summary>
public class MemoryCache(MemoryCacheOptions Options) : IBlobCache
public class MemoryCache(MemoryCacheOptions options) : IBlobCache
{
private record CacheEntry(string CacheKey, IBlobWrapper BlobWrapper, UsageTracker UsageTracker)
{
Expand All @@ -76,7 +76,7 @@ public string GetFullyQualifiedRepresentation()
}
ConcurrentDictionary<string, CacheEntry> __cache = new ConcurrentDictionary<string, CacheEntry>();

public string UniqueName => Options.UniqueName;
public string UniqueName => options.UniqueName;


public BlobCacheCapabilities InitialCacheCapabilities { get; } = new BlobCacheCapabilities
Expand Down Expand Up @@ -110,21 +110,21 @@ public Task<CacheFetchResult> CacheFetch(IBlobCacheRequest request, Cancellation
return Task.FromResult(BlobCacheFetchFailure.MissResult(this, this));
}

private long MemoryUsedSync;
private long ItemCountSync;
private long memoryUsedSync;
private long itemCountSync;
private bool TryRemove(string cacheKey, [MaybeNullWhen(false)] out CacheEntry removed)
{
if (!__cache.TryRemove(cacheKey, out removed)) return false;
Interlocked.Add(ref MemoryUsedSync, -removed.BlobWrapper.EstimateAllocatedBytes ?? 0);
Interlocked.Decrement(ref ItemCountSync);
Interlocked.Add(ref memoryUsedSync, -removed.BlobWrapper.EstimateAllocatedBytes ?? 0);
Interlocked.Decrement(ref itemCountSync);
return true;
}

private bool TryEnsureCapacity(long size)
{
List<CacheEntry>? snapshotOfEntries = null;
int nextCandidateIndex = 0;
while (ItemCountSync > Options.MaxItems || MemoryUsedSync > Options.MaxMemoryUtilizationMb * 1024 * 1024)
while (itemCountSync > options.MaxItems || memoryUsedSync + size > options.MaxMemoryUtilizationMb * 1024 * 1024)
{
if (snapshotOfEntries == null)
{
Expand All @@ -138,7 +138,7 @@ private bool TryEnsureCapacity(long size)
return false; // We've run out of candidates. We can't make space.
}
var candidate = snapshotOfEntries[nextCandidateIndex++];
if (candidate.UsageTracker.LastAccessedUtc > DateTimeOffset.UtcNow - Options.MinKeepNewItemsFor)
if (candidate.UsageTracker.LastAccessedUtc > DateTimeOffset.UtcNow - options.MinKeepNewItemsFor)
{
nextCandidateIndex++; // Skip this item
continue; // This item is too new to evict.
Expand Down Expand Up @@ -169,7 +169,7 @@ private bool TryAdd(string cacheKey, IBlobWrapper blob)
}
}
var replacementSizeDifference = (long)blob.EstimateAllocatedBytes! - (existingBlob?.EstimateAllocatedBytes ?? 0);
if (blob.EstimateAllocatedBytes > Options.MaxItemSizeKb * 1024)
if (blob.EstimateAllocatedBytes > options.MaxItemSizeKb * 1024)
{
return false;
}
Expand All @@ -179,14 +179,14 @@ private bool TryAdd(string cacheKey, IBlobWrapper blob)
}

var entry = new CacheEntry(cacheKey, blob, UsageTracker.Create());
if (entry == __cache.AddOrUpdate(cacheKey, entry, (key, existing) => existing with { BlobWrapper = blob }))
if (entry == __cache.AddOrUpdate(cacheKey, entry, (_, existing) => existing with { BlobWrapper = blob }))
{
Interlocked.Increment(ref ItemCountSync);
Interlocked.Add(ref MemoryUsedSync, blob.EstimateAllocatedBytes ?? 0);
ItemCountSync++;
Interlocked.Increment(ref itemCountSync);
Interlocked.Add(ref memoryUsedSync, blob.EstimateAllocatedBytes ?? 0);
itemCountSync++;
return true;
}
Interlocked.Add(ref MemoryUsedSync, replacementSizeDifference);
Interlocked.Add(ref memoryUsedSync, replacementSizeDifference);

return false;
}
Expand Down Expand Up @@ -237,7 +237,7 @@ public Task<CodeResult> CacheDelete(IBlobStorageReference reference, Cancellatio
{
if (reference is MemoryCacheStorageReference memoryCacheStorageReference)
{
if (TryRemove(memoryCacheStorageReference.CacheKey, out var removed))
if (TryRemove(memoryCacheStorageReference.CacheKey, out _))
{
return Task.FromResult(CodeResult.Ok());
}
Expand Down
Loading

0 comments on commit dfda9ff

Please sign in to comment.