Skip to content

3.1: roadmap from #51 — processor features, poison-queue replay, telemetry, AOT, Blob and health-check packages - #52

Merged
jasonshave merged 10 commits into
masterfrom
roadmap-3.1
Oct 16, 2026
Merged

jasonshave merged 10 commits into
masterfrom
roadmap-3.1

Conversation

@jasonshave

Copy link
Copy Markdown
Owner

Implements the 3.1 roadmap in #51: the four fix-before-3.1 items, every quick win, and every bigger feature, including two new packages. Not included: the "4.0 candidates" (breaking changes) and the items #51 lists as considered and skipped. Nothing is released by merging this; tagging v3.1.0 stays a separate step after validation.

What changes for someone using the library

Fixed before 3.1 ships

  • Messages from a future version are refused, not misread. A 3.1 app that meets a newer format now dead-letters the message intact (replayable after upgrading) instead of reading it into an object with empty fields. The format rules are written down in the README ("Wire format").
  • Poison messages no longer vanish after 7 days. They stay until deleted or replayed; PoisonTimeToLive sets an expiry if wanted.
  • Duration and size histograms have real buckets (seconds up to an hour; bytes up to 64 KiB) and units.
  • IntelliSense works. The package ships its doc comments, and every public member has one.

New

  • Processing: one receive loop per processor that only takes as many messages as it has free handlers; lambda handlers; middleware; routing several message types on one queue (and a CloudEvent router); an error policy (DeadLetterOn<T>(), OnHandlerError); pause/resume and status (IQueueProcessorController); a rate limiter; MaxRenewalDuration and a long-running-handler warning; QueueName, Headers and IsLastAttempt on each message; processor options from configuration.
  • Sending: headers, Delay, gzip compression, and a clear error before sending anything the service would reject (over 64 KiB, or a delay the message would expire within).
  • Poison queue: IDeadLetterQueue shows why each message was dead-lettered and replays them (optionally filtered).
  • Setup: UseStorageAccount(...) so clients only name their queue (the shape Aspire hands an app); an account endpoint plus QueueName is accepted; injecting an unregistered client says which clients exist.
  • Own-loop code: ReadAllAsync replaces the hand-written polling loop.
  • Telemetry: time-in-queue, receive requests by result (empty polls are billable), active handlers, oldest handler age, renewals, dead-letters by reason, opt-in queue/poison depth gauges, span events, a log scope per delivery, quieter logs, and opt-in OpenTelemetry-standard metric names on a separate meter.
  • In-memory test queues now run the same queue code as production, telemetry and size limit included.
  • Trimming / native AOT: annotated and analyzer-clean. A trimmed test app using the library and the Azure SDK published with zero warnings and sent and received a message against Azurite.
  • New packages: AzureStorage.QueueService.Blobs (messages over 64 KiB, kept in Blob storage) and AzureStorage.QueueService.HealthChecks. Both are in the solution, so nuget.yml packs and publishes them with the core at the same version.

Decisions for you to check

  1. Case-insensitive JSON reading is now the default (the issue's "behaviour change to decide on"). It fixes camelCase messages silently arriving empty and being completed. Writing camelCase by default is left for 4.0.
  2. Compression lives in the envelope, not in a serializer wrapper as the issue suggested. That way every 3.1 receiver decompresses without being configured to.
  3. A poison-queue copy is now an envelope with a record of why. Tools that read poison messages as plain JSON will see the wrapper; ReplayAsync strips it.
  4. Binding client settings from IConfiguration is marked not trim-safe. The source-generated binder can't fill ClientOptions, so using it would silently drop "MessageEncoding": "Base64" from config. Processor options use the generator.
  5. The health-check package adds a Timeout (10 s) on the queue check that the issue didn't ask for, so an unreachable account can't hold a probe open for minutes.

The full list of behaviour changes is in the README under "Upgrading from 3.0 to 3.1".

Tests and CI

198 tests, all passing locally in about 3 s across four test assemblies, and stable across three runs. Two new test projects come with the packages. No test waits on real time. CI now also maps Azurite's blob port (10000), which the Blob package's two end-to-end tests need.

🤖 Generated with Claude Code

Jason Shave and others added 10 commits October 16, 2026 14:32
Two IHealthChecksBuilder extensions:
- AddAzureStorageQueueProcessors: reads IQueueProcessorController.GetStatus()
  (no storage calls). Unhealthy after N consecutive receive failures,
  Degraded when paused, stopping, or a handler exceeds MaxHandlerAge.
- AddAzureStorageQueue: reads queue properties (billable call), optionally
  the poison queue count; per-run timeout (10 s default).

Not yet added to the .sln, so CI and nuget.yml do not build or pack it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rage

New package implementing ILargeMessageStore over a BlobContainerClient
(claim-check for messages over 64 KiB), with UseBlobStorageForLargeMessages
builder extensions (container instance, factory, or container name derived
from the queue client's own account and credentials). Download and delete
only accept blobs inside the store's own container; references never carry
a SAS token. Tests: reference refusal and endpoint derivation (no IO), plus
end-to-end completion and dead-letter runs against Azurite blob storage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jasonshave
jasonshave enabled auto-merge (squash) October 16, 2026 20:47
@jasonshave
jasonshave merged commit 916c499 into master Oct 16, 2026
1 check passed
@jasonshave
jasonshave deleted the roadmap-3.1 branch October 16, 2026 20:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant