8
8
using Imazen . Abstractions . Resulting ;
9
9
using Imazen . Common . Concurrency ;
10
10
using Imazen . Common . Concurrency . BoundedTaskCollection ;
11
+ using Imazen . Routing . Health ;
11
12
using Imazen . Routing . Helpers ;
12
13
using Imazen . Routing . HttpAbstractions ;
13
14
using Imazen . Routing . Requests ;
@@ -21,6 +22,7 @@ namespace Imazen.Routing.Promises.Pipelines;
21
22
public class CacheEngine : IBlobPromisePipeline
22
23
{
23
24
25
+
24
26
public CacheEngine ( IBlobPromisePipeline ? next , CacheEngineOptions options )
25
27
{
26
28
Options = options ;
@@ -30,6 +32,7 @@ public CacheEngine(IBlobPromisePipeline? next, CacheEngineOptions options)
30
32
{
31
33
Locks = new AsyncLockProvider ( ) ;
32
34
}
35
+ HealthTracker = options . HealthTracker as CacheHealthTracker ;
33
36
}
34
37
35
38
public async ValueTask < CodeResult < ICacheableBlobPromise > > GetFinalPromiseAsync ( ICacheableBlobPromise promise , IBlobRequestRouter router ,
@@ -49,7 +52,7 @@ public async ValueTask<CodeResult<ICacheableBlobPromise>> GetFinalPromiseAsync(I
49
52
return CodeResult < ICacheableBlobPromise > . Ok (
50
53
new ServerlessCachePromise ( wrappedPromise . FinalRequest , wrappedPromise , this ) ) ;
51
54
}
52
-
55
+ private CacheHealthTracker ? HealthTracker { get ; }
53
56
private IBlobPromisePipeline ? Next { get ; }
54
57
private AsyncLockProvider ? Locks { get ; }
55
58
@@ -70,7 +73,7 @@ private async Task FinishUpload(IBlobCacheRequest cacheReq, ICacheableBlobPromis
70
73
await Task . WhenAll ( Options . SaveToCaches . Select ( x => x . CachePut ( cacheEventDetails , cancellationToken ) ) ) ;
71
74
}
72
75
73
- public async ValueTask < CodeResult < IBlobWrapper > > Fetch ( ICacheableBlobPromise promise , IBlobRequestRouter router , CancellationToken cancellationToken = default )
76
+ internal async ValueTask < CodeResult < IBlobWrapper > > Fetch ( ICacheableBlobPromise promise , IBlobRequestRouter router , CancellationToken cancellationToken = default )
74
77
{
75
78
if ( ! promise . ReadyToWriteCacheKeyBasisData )
76
79
{
@@ -95,16 +98,15 @@ public async ValueTask<CodeResult<IBlobWrapper>> Fetch(ICacheableBlobPromise pro
95
98
return await FetchInner ( cacheRequest , promise , router , cancellationToken ) ;
96
99
}
97
100
}
98
-
99
101
100
-
101
- public async ValueTask < CodeResult < IBlobWrapper > > FetchInner ( IBlobCacheRequest cacheRequest , ICacheableBlobPromise promise , IBlobRequestRouter router , CancellationToken cancellationToken = default )
102
+
103
+ private async ValueTask < CodeResult < IBlobWrapper > > FetchInner ( IBlobCacheRequest cacheRequest , ICacheableBlobPromise promise , IBlobRequestRouter router , CancellationToken cancellationToken = default )
102
104
{
103
105
// First check the upload queue.
104
106
if ( Options . UploadQueue ? . TryGet ( cacheRequest . CacheKeyHashString , out var uploadTask ) == true )
105
107
{
106
108
Options . Logger . LogTrace ( "Located requested resource from the upload queue {CacheKeyHashString}" , cacheRequest . CacheKeyHashString ) ;
107
- return CodeResult < IBlobWrapper > . Ok ( uploadTask . Blob ) ;
109
+ return CodeResult < IBlobWrapper > . Ok ( uploadTask . Blob . ForkReference ( ) ) ;
108
110
}
109
111
// Then check the caches
110
112
List < KeyValuePair < IBlobCache , Task < CacheFetchResult > > > ? allFetchAttempts = null ;
@@ -266,9 +268,9 @@ private void LogFetchTaskStatus(bool isFresh,
266
268
LogFetchTaskStatus ( isFresh , cacheHit , fetchTasks ) ;
267
269
}
268
270
269
- if ( Options . UploadQueue == null )
271
+ if ( Options . UploadQueue == null && ! Options . DelayRequestUntilUploadsComplete )
270
272
{
271
- Log . LogWarning ( "No upload queue configured" ) ;
273
+ Log . LogWarning ( "No upload queue configured, and synchronous mode disabled. Not saving to any caches. " ) ;
272
274
return null ;
273
275
}
274
276
@@ -323,23 +325,27 @@ private void LogFetchTaskStatus(bool isFresh,
323
325
324
326
325
327
326
- private async Task BufferAndEnqueueSaveToCaches ( IBlobCacheRequest cacheRequest , IBlobWrapper blob , bool isFresh ,
328
+ private async ValueTask BufferAndEnqueueSaveToCaches ( IBlobCacheRequest cacheRequest , IBlobWrapper blob , bool isFresh ,
327
329
IBlobCache ? cacheHit , List < KeyValuePair < IBlobCache , Task < CacheFetchResult > > > ? fetchTasks , CancellationToken bufferCancellationToken = default )
328
330
{
329
- if ( EnqueueSaveToCaches ( cacheRequest , blob , isFresh , cacheHit , fetchTasks ) )
331
+ // Here's also a good place to handle the cachefetchresults; \
332
+ // HealthTracker[cacheHit].ReportBehavior();
333
+ //
334
+
335
+ if ( await EnqueueSaveToCaches ( cacheRequest , blob , isFresh , cacheHit , fetchTasks ) )
330
336
{
331
337
await blob . EnsureReusable ( bufferCancellationToken ) ;
332
338
Log . LogTrace ( "Called EnsureReusable on {CacheKeyHashString}" , cacheRequest . CacheKeyHashString ) ;
333
339
}
334
340
}
335
341
336
- private bool EnqueueSaveToCaches ( IBlobCacheRequest cacheRequest , IBlobWrapper mainBlob , bool isFresh ,
342
+ private ValueTask < bool > EnqueueSaveToCaches ( IBlobCacheRequest cacheRequest , IBlobWrapper mainBlob , bool isFresh ,
337
343
IBlobCache ? cacheHit , List < KeyValuePair < IBlobCache , Task < CacheFetchResult > > > ? fetchTasks )
338
344
{
339
345
340
346
var cachesToSaveTo = GetUploadCacheCandidates ( isFresh , ref cacheHit , fetchTasks ) ;
341
347
342
- if ( cachesToSaveTo == null || Options . UploadQueue == null ) return false ; // Nothing to do
348
+ if ( cachesToSaveTo == null || ( Options . UploadQueue == null && ! Options . DelayRequestUntilUploadsComplete ) ) return new ValueTask < bool > ( false ) ; // Nothing to do
343
349
344
350
var blob = mainBlob . ForkReference ( ) ;
345
351
mainBlob . IndicateInterest ( ) ;
@@ -360,39 +366,56 @@ private bool EnqueueSaveToCaches(IBlobCacheRequest cacheRequest, IBlobWrapper ma
360
366
throw new InvalidOperationException ( ) ;
361
367
}
362
368
363
-
364
- var enqueueResult = Options . UploadQueue . Queue ( new BlobTaskItem ( cacheRequest . CacheKeyHashString , blob ) , async ( taskItem , cancellationToken ) =>
369
+ var blobTaskItem = new BlobTaskItem ( cacheRequest . CacheKeyHashString , blob ) ;
370
+
371
+ Task < PutResult [ ] > BulkUploader ( BlobTaskItem taskItem , CancellationToken cancellationToken )
365
372
{
366
373
// We need to dispose of the blob wrapper after all uploads are complete.
367
374
using ( taskItem . Blob )
368
375
{
369
- var tasks = cachesToSaveTo . Select ( async cache =>
370
- {
371
- var waitingInQueue = DateTime . UtcNow - taskItem . JobCreatedAt ;
376
+ var tasks = cachesToSaveTo . Select ( PerCacheUpload )
377
+ . ToArray ( ) ;
378
+ return Task . WhenAll ( tasks ) ;
372
379
373
- var sw = Stopwatch . StartNew ( ) ;
374
- try
375
- {
376
- Log . LogTrace ( "[put started] CachePut {key} to {CacheName}" , taskItem . UniqueKey ,
377
- cache . UniqueName ) ;
378
- var result = await cache . CachePut ( eventDetails , cancellationToken ) ;
379
- sw . Stop ( ) ;
380
- var r = new PutResult ( cache , eventDetails , result , null , sw . Elapsed , waitingInQueue ) ;
381
- LogPutResult ( r ) ;
382
- return r ;
383
- }
384
- catch ( Exception e )
385
- {
386
- sw . Stop ( ) ;
387
- var r = new PutResult ( cache , eventDetails , null , e , sw . Elapsed , waitingInQueue ) ;
388
- LogPutResult ( r ) ;
389
- return r ;
390
- }
380
+ async Task < PutResult > PerCacheUpload ( IBlobCache cache )
381
+ {
382
+ var waitingInQueue = DateTime . UtcNow - taskItem . JobCreatedAt ;
383
+
384
+ var sw = Stopwatch . StartNew ( ) ;
385
+ try
386
+ {
387
+ Log . LogTrace ( "[put started] CachePut {key} to {CacheName}" , taskItem . UniqueKey , cache . UniqueName ) ;
388
+ var result = await cache . CachePut ( eventDetails , cancellationToken ) ;
389
+ sw . Stop ( ) ;
390
+ var r = new PutResult ( cache , eventDetails , result , null , sw . Elapsed , waitingInQueue ) ;
391
+ LogPutResult ( r ) ;
392
+ return r ;
391
393
}
392
- ) . ToArray ( ) ;
393
- HandleUploadAnswers ( await Task . WhenAll ( tasks ) ) ;
394
+ catch ( Exception e )
395
+ {
396
+ sw . Stop ( ) ;
397
+ var r = new PutResult ( cache , eventDetails , null , e , sw . Elapsed , waitingInQueue ) ;
398
+ LogPutResult ( r ) ;
399
+ return r ;
400
+ }
401
+ }
394
402
}
395
- } ) ;
403
+ }
404
+
405
+ async Task BulkUploaderAsync ( BlobTaskItem item , CancellationToken ct ) => HandleUploadAnswers ( await BulkUploader ( item , ct ) ) ;
406
+
407
+ if ( Options . DelayRequestUntilUploadsComplete )
408
+ {
409
+ var uploadTask = BulkUploader ( blobTaskItem , default ) ;
410
+ var finalTask = uploadTask . ContinueWith ( t =>
411
+ {
412
+ HandleUploadAnswers ( t . Result ) ;
413
+ return true ;
414
+ } , TaskContinuationOptions . ExecuteSynchronously ) ;
415
+ return new ValueTask < bool > ( finalTask ) ;
416
+ }
417
+ if ( Options . UploadQueue == null ) return new ValueTask < bool > ( false ) ;
418
+ var enqueueResult = Options . UploadQueue . Queue ( blobTaskItem , BulkUploaderAsync ) ;
396
419
if ( enqueueResult == TaskEnqueueResult . QueueFull )
397
420
{
398
421
Log . LogWarning ( "[CACHE PUT ERROR] Upload queue is full, not enqueuing {CacheKeyHashString} for upload to {Caches}" , cacheRequest . CacheKeyHashString , string . Join ( ", " , cachesToSaveTo . Select ( x => x . UniqueName ) ) ) ;
@@ -409,7 +432,8 @@ private bool EnqueueSaveToCaches(IBlobCacheRequest cacheRequest, IBlobWrapper ma
409
432
{
410
433
Log . LogTrace ( "Enqueued {CacheKeyHashString} for upload to {Caches}" , cacheRequest . CacheKeyHashString , string . Join ( ", " , cachesToSaveTo . Select ( x => x . UniqueName ) ) ) ;
411
434
}
412
- return enqueueResult == TaskEnqueueResult . Enqueued ;
435
+
436
+ return new ValueTask < bool > ( enqueueResult == TaskEnqueueResult . Enqueued ) ;
413
437
}
414
438
record struct PutResult ( IBlobCache Cache , CacheEventDetails EventDetails , CodeResult ? Result , Exception ? Exception , TimeSpan Executing , TimeSpan Waiting ) ;
415
439
@@ -432,13 +456,9 @@ private void LogPutResult(PutResult result)
432
456
433
457
private void HandleUploadAnswers ( PutResult [ ] results )
434
458
{
435
- //TODO?
459
+ //TODO? Cache put failures should probably affect health??
436
460
437
461
}
438
-
439
-
440
-
441
-
442
462
}
443
463
444
464
internal record ServerlessCachePromise ( IRequestSnapshot FinalRequest , ICacheableBlobPromise FreshPromise , CacheEngine CacheEngine ) : ICacheableBlobPromise
0 commit comments