Replace fire-and-forget PDF trigger with bounded queue#48
Merged
Conversation
The POST /pdf/v1/ trigger previously used Task.Run with no backpressure, risking unbounded memory and giving dishonest 202 responses when capacity was exceeded. Replaced with a bounded Channel<string> drained by a BackgroundService, with a SemaphoreSlim capping concurrency. * PdfGenerationService extracts shared lock + generate logic * PdfGenerationQueue (IPdfGenerationQueue) owns the bounded channel; TryEnqueue returns false when full (BoundedChannelFullMode.Wait) * PdfGenerationBackgroundService drains the queue; SemaphoreSlim bounds concurrent in-flight MemoryStream allocations * PdfTriggerResult enum replaces bool return; 503 + Retry-After when queue full, fixes latent NotFound -> 200 bug on disabled services * New PdfTriggerQueueCapacity (50) and PdfTriggerMaxConcurrency (2) config keys under TextServices: * Unit tests for PdfHandler trigger result codes and queue capacity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
POST /pdf/v1/{**id}trigger endpoint previously used a bareTask.Runwith no backpressure:MemoryStreamallocations under load202responses: if a trigger was silently dropped, the caller would wait then hit the slow synchronous GET path anywayChanges
New types:
PdfGenerationService/IPdfGenerationService— extracts shared lock + generate logic fromPdfHandler; used by both the GET and background pathsPdfGenerationQueue/IPdfGenerationQueue— boundedChannel<string>wrapper;TryEnqueuereturnsfalsewhen full (BoundedChannelFullMode.Wait)PdfGenerationBackgroundService—BackgroundServicedraining the queue;SemaphoreSlimcaps concurrent in-flight generations (and therefore peakMemoryStreammemory)Updated types:
PdfTriggerResultenum replaces theboolreturn fromPdfTriggerRequest, enabling honest HTTP mapping:202when queued,503 + Retry-Afterwhen queue full,404when no artefact (fixes a latent bug where disabled-service/no-artefact was returning200)SearchApiOptionsgainsPdfTriggerQueueCapacity(default50) andPdfTriggerMaxConcurrency(default2)ServiceCollectionExtensions.AddPdfServices()consolidates PDF DI registrationTests:
PdfHandlerTests(10 tests covering allPdfTriggerResultandPdfRequestpaths) andPdfGenerationQueueTests(2 capacity tests).Test plan
dotnet test src/TextServices.Tests— all 477 tests passPOST /pdf/v1/{id}on a valid ID → 202 withLocationheaderPOST /pdf/v1/{id}when PDF already exists → 200 withlocationbodyPOST /pdf/v1/missing-id→ 404PdfTriggerQueueCapacityto1andPdfTriggerMaxConcurrencyto1in dev config; fire two simultaneous POSTs to different IDs → second returns 503🤖 Generated with Claude Code