Conversation
Contributor
Author
|
Pushed two commits addressing the messaging/jobs design-review feedback. All changes are green against the messaging, queue, and jobs suites (plus the conformance harness); full core suite passes.
|
This was referenced Sep 7, 2026
Durable jobs + CRON: - Add hosted JobRuntimeService that pumps occurrence materialization, due-dispatch, recovery, and queued-job execution (runtime previously never ran end-to-end). Register via AddJobRuntimeService(). - Enforce lease ownership in TryTransitionAsync (expectedNodeId) so a stale worker cannot overwrite the reclaiming node's terminal state. - Renew the claim on a heartbeat during execution and cancel the run when the lease is lost (RenewClaimAsync was dead code). - Strong, process-unique node identity (machine:pid:token). - Capped exponential CRON retry backoff (per-definition override). - Replace the hand-rolled cron parser with the vendored Cronos (moved into core Foundatio.Cronos); materialize every missed occurrence in the misfire window, not just the latest. Messaging: - Resilient consumer/subscription loops: a poison message or transient receive error no longer silently kills the consumer. - Fix in-memory push path double-settle (tolerate already-settled receipts in the safety-net abandon). - Honor visibility timeouts in the in-memory transport (reaper redelivers unsettled messages) so its advertised at-least-once guarantee is real. - Log handler exceptions instead of swallowing them. - Drop the misleading write-only content-type header. - Add ReceiveDeadLetteredAsync so poison payloads are inspectable. Conformance harness: - Replace silent capability skips with Assert.Skip. - Gate ordering assertions on the declared OrderingGuarantee. - Add visibility-timeout, competing-consumer, and DLQ-read scenarios. Tests cover lease-stomp rejection, manual ack, poison survival, multi-occurrence CRON, and the hosted runtime running a queued job. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tCore (M1, M2) M1 — remove the large duplication between MessageQueue and PubSub: - New internal MessageClientCore owns serialization, header/trace construction, routing-agnostic send, runtime-store scheduled dispatch, received-message creation with poison handling, auto/manual ack settlement, and the resilient consumer/subscription loop. - Unify the two near-identical handle classes into one MessageListenerHandle implementing both IMessageConsumer and IMessageSubscription. - Collapse the duplicated HandleMessageAsync overloads into a single generic method. - MessageQueue and PubSub become thin adapters mapping their option shapes onto the core. Fixes the prior drift: pub/sub subscriptions now also honor RedeliveryBackoff. M2 — enforce ITransportInfo.MaxBatchSize: oversized sends are split into chunks of at most MaxBatchSize (test via a fake transport). Behavior preserved — verified by the existing messaging, queue, and jobs suites plus the added chunking test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Close gaps surfaced in design review of the messaging/jobs redesign: - Prove redelivery-delay and lock-renewal: InMemoryMessageTransport now implements ISupportsRedeliveryDelay (timer-based re-enqueue that wakes a blocked receiver) and ISupportsLockRenewal (extends the in-flight visibility window), with conformance tests for both. - Make the attempt counter transport-independent: ReceivedMessage.Attempts reconciles DeliveryCount with the message.attempts header so store-backed redelivery can't reset the count and loop forever. - Real back-pressure: the pull loop is now a SemaphoreSlim-gated continuous dispatcher (per-message slot release, opportunistic batch claim) instead of a Task.WhenAll batch barrier, eliminating head-of-line blocking. - Core-owned metrics: foundatio.messaging.* and foundatio.jobs.* counters and histograms emitted on FoundatioDiagnostics.Meter. - Receive-side trace continuity: handlers run inside a Consumer Activity linked to the producer's traceparent/tracestate. - Configurable job cancellation polling (default 1s instead of fixed 50ms). - Document the IQueue namespace collision and the using-alias remedy. Add BasicQueueTransport test double (pull-only, opaque headers, no time-based capabilities) and repoint the unsupported-lock and redelivery-fallback tests at it, keeping fallback coverage and proving the attempt reconciliation end-to-end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… type demux Address messaging/jobs design-review feedback on the transport-backed messaging API. The transport stays a thin set of primitives; the core owns serialization, routing, retry, and dead-lettering so behavior is identical across transports. Settlement: replace IReceivedMessage.AbandonAsync/DeadLetterAsync with a single RejectAsync(RejectOptions) verb (Terminal/Reason/RedeliveryDelay). Terminal reject moves the message to the transport's native dead-letter sink, else a configured RetryPolicy.DeadLetterDestination, else drops (honest at-most-once) instead of throwing. Consumers: one receive loop per source that demultiplexes by message type, so multiple typed consumers can share a destination without mis-dispatch; same-type consumers compete round-robin. An unmatched type increments foundatio.messaging.unhandled, throws UnhandledMessageTypeException (isolated per message so the loop and other handlers survive), retries, and dead-letters as "no-handler" after a lenient budget. Retry policy: add RetryPolicy (MaxAttempts/Backoff/DeadLetterDestination/UnmatchedMaxAttempts/UnmatchedBackoff), configurable via Messaging.ConfigureRetry and overridable per consumer. The broker delivery count is the crash-safe attempt counter; no broker-native redrive config. Capabilities: add MaxDeliveryDelay/MaxRedeliveryDelay/MaxVisibilityTimeout to the delay/visibility capability interfaces so an over-limit delay routes through the durable runtime store instead of being silently truncated by the broker. Docs: rewrite settlement section and add 'core owns behavior' + 'retry and dead-lettering' guidance. Add 7 tests covering cap-routing, Reject, multi-type demux, unmatched dead-letter, core-managed DLQ, and default-tier MaxAttempts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ti-type dispatch, job context, recovery, ownership, CRON)
Pub/sub addressing: the transport receive source for a subscription is now the topic-qualified "{topic}/{subscription}" composite (exposed as IMessageSubscription.Source), so the same subscription identity on two topics stays isolated instead of colliding on a bare name.
Multi-type dispatch: add IMessageTypeRegistry (stable name<->type, Type.FullName fallback, RegisterMessageType<T>) as the single wire-discriminator authority; interface/base-routed consumers now resolve the concrete payload type from the message.type header and deserialize the actual type (assignable to the route type) instead of raw-envelope-only. Removes the orphaned MessageTypeResolver/UseMessageTypeName router API.
Job execution context: IJobWithExecutionContext receives a JobExecutionContext (job id, attempt, store-backed progress, lease heartbeat, cancellation checks); remove the always-throwing ReportProgressAsync from IReceivedMessage.
Non-CRON job recovery: the runtime pump reclaims plain jobs stuck in Processing past their lease via IJobRuntimeStore.GetExpiredProcessingAsync (excludes CRON occurrences) + a lease+owner-aware TryReclaimExpiredAsync (re-queue while attempts remain, else dead-letter), closing the renew race that could double-run a live job.
Transport ownership: OwnsTransport flag so DI-built queue and pub/sub clients do not both dispose a shared singleton transport (the container disposes it once); direct construction still owns it.
CRON: mark the legacy in-process AddCronJob/AddJobScheduler/ScheduledJobService path as legacy/compat with docs pointing to the durable runtime (full reroute deferred).
Adds 8 tests (pub/sub isolation, interface concrete-deserialize, job context, stale recovery + reclaim guard + occurrence exclusion, DI dispose-once, delay cap routing) and updates the redesign guide. All messaging/queue/jobs tests pass; solution builds on net8 + net10.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s-transport Adds a temporary in-repo Foundatio.Aws provider (AwsMessageTransport over SQS/SNS) to validate the redesigned IMessageTransport contract against a real broker, plus the contract refinements that validation surfaced. Verified against LocalStack: 8 conformance tests pass, 5 skip for capabilities SQS lacks (priority, per-message expiration, push, transport-native dead-letter); in-memory conformance and the full messaging/queue/jobs suite remain green. Transport contract refinements driven by the AWS implementation: - TransportSendOptions.DestinationRole: the caller states queue vs topic so a transport routes without inferring (SNS publish vs SQS send). MessageClientCore sets it from the dispatch kind. - TransportMessage.ContentType: lets a text-native broker (SQS/SNS) store a text body (e.g. JSON) directly instead of base64; binary still base64s. - MessageDestinationStats: lifetime counters (Enqueued/Dequeued/Completed/Abandoned/Errors/Timeouts) are now nullable (null = not reported, e.g. SQS exposes no lifetime completed count); Queued/Working/Deadletter remain best-effort gauges. - ReceiptExpiredException documented as a best-effort, transport-specific signal (SQS delete is idempotent). - InMemoryMessageTransport now wakes a blocked receive when a visibility window lapses (reclaim timer), matching real brokers so the harness can long-poll uniformly. AWS provider: SQS queues + SNS topics/subscriptions (raw delivery + queue policy), capability max-bounds (15-min delay, 12h visibility/redelivery), well-known headers surfaced as native attributes for SNS filter policies, ResourcePrefix for run isolation, LocalStack docker-compose + README. Harness: whole-second timing windows, eventual-consistency-tolerant stats (gauges only), and capability/opt-in gating so the suite runs across in-memory and real brokers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ejsmith
force-pushed
the
feat/messaging-jobs
branch
from
September 12, 2026 23:51
7e6e0a1 to
56a88f0
Compare
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.
Adds a unified
IMessageBus, typed background jobs throughIJobClient, and shared monitoring for jobs and broker-delivered work.Developer experience
Inject the ordinary application APIs:
SubscribeNodeAsyncbroadcasts. ImplementIMessageHandler<T>; automatic batching keeps single-message calls efficient.IJoborIJob<TArgs>for delays, progress, cancellation, persisted retries, and CRON.IScheduledJobManagersupports schedule edits and manual runs.IJobRuntimeStore,JobState, andIJobMonitorcover ordinary jobs and tracked broker work, including paginated history, counts, metadata, heartbeats, and cancellation. ChooseJobs.UseInMemory()orJobs.UseRedis().MessageOutcomevalues for success, retry, dead letter, or unsettled work.MessageProcessingContextsupplies progress and cooperative cancellation.AddFoundatio()configures producers;AddFoundatioWorker(...)starts configured processing. The Quickstart runs without Docker; the messaging sample shows LocalStack, Redis, multiple replicas, and monitoring. Mediator #335 builds queued handlers and live events on these APIs.Performance
Short deliveries avoid starting an async renewal loop; a timer still supervises expiry from admission. Median jobs/sec, previous native version → this update:
Untracked memory throughput improves 5–8%, with about 5% fewer allocations and 17% less CPU at concurrency 64. Longer Redis checks were 4% slower, with 5% less CPU; no Redis/SQS speedup is claimed. System .NET 10.0.12, Release, repeated local runs. Workloads, latency and raw results. LocalStack does not measure production AWS capacity.
Additional details