Defer Message Action Should Have an Attribute as a Backstop#4028
Conversation
Mark ADR 0051 (error-handling-examples) as Accepted and create design-approved marker for spec 0021-Error-Examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sample intentionally throws on every 5th message. Without resetting the unacceptable message count, the pump would shut down. Setting the window to TimeSpan.Zero resets the count each pump cycle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Four-project sample demonstrating RejectMessageOnErrorAsync on RabbitMQ: - Greetings library with handler that rejects every 5th message - GreetingsSender with TimedMessageGenerator IHostedService - GreetingsReceiverConsole with RmqSubscription and DLQ routing - DlqConsole consuming rejected messages with rejection metadata Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Align DeferMessageAction with RejectMessageAction and DontAckAction by adding standard exception constructors and a TimeSpan? Delay property. This structural change prepares for the DeferMessageOnError backstop handler without altering existing behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three-project sample demonstrating DeferMessageAction on Kafka: - Handler throws DeferMessageAction directly (no attribute yet) - ConcurrentDictionary tracks retries per message ID - Every 3rd message defers twice then succeeds on 3rd attempt - Unique groupId kafka-DeferOnError-Sample avoids consumer collisions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DeferMessageOnErrorAttribute decorates a handler method to catch unhandled exceptions and convert them to DeferMessageAction with a configurable delay. The delay flows from the attribute through InitializerParams to the handler. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Async counterpart to DeferMessageOnErrorHandler. Catches unhandled exceptions in async pipelines and converts them to DeferMessageAction with a configurable delay from the attribute. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three-project sample demonstrating DeferMessageAction on RabbitMQ: - Handler throws DeferMessageAction directly (no attribute yet) - ConcurrentDictionary tracks retries per message ID - Every 3rd message defers twice then succeeds on 3rd attempt - InMemorySchedulerFactory provides requeue delay support Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies DeferMessageOnErrorHandler passes through transparently when the inner handler completes without throwing an exception. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies DeferMessageOnErrorHandlerAsync passes through transparently when the inner handler completes without throwing an exception. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies both sync and async attributes return correct handler types, timing, step values, and InitializerParams containing the delay. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both Proactor and Reactor now capture DeferMessageAction.Delay when catching the exception and pass it to RequeueMessage, which falls back to RequeueDelay when no delay is specified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tted) Three-project sample demonstrating DontAckOnErrorAsync on Kafka: - Handler uses [DontAckOnErrorAsync(step: 0)], throws every 5th message - On Kafka, not committing the offset causes re-delivery on next poll - Unique groupId kafka-DontAckOnError-Sample avoids consumer collisions - README contrasts Kafka nack (no-op) with RabbitMQ nack (BasicNack) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Gates Passed
4 Quality Gates Passed
See analysis details in CodeScene
Quality Gate Profile: Clean Code Collective
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.
|
test comment from agent - please ignore |
Pull Request: Fix Unreliable CI Acceptance TestsOverviewThis PR addresses the reliability issues in Brighter's CI acceptance tests for MessagingGateways, Inboxes, and Outboxes. The tests were exhibiting erratic behavior in GitHub Actions, often failing due to timing-related issues that were difficult to reproduce locally. ProblemThe CI build ( Root Causes
Solution1. Comprehensive Service Health ChecksAdded or improved health checks for all services in
(↑ indicates increased from previous value) 2. Active Kafka Readiness VerificationBefore: - name: Sleep to let Kafka spin up
uses: jakejarvis/wait-action@master
with:
time: '30s'After: - name: Wait for Kafka to be ready
run: |
max_attempts=30
while [ $attempt -lt $max_attempts ]; do
if kafkacat -b localhost:9092 -L > /dev/null 2>&1; then
echo "Kafka is ready!"
break
fi
sleep 2
doneBenefits:
3. Increased Job TimeoutsAll acceptance test jobs increased from 5 minutes → 8 minutes:
Note: kafka-ci already had 20 minutes (unchanged) 4. Comprehensive DocumentationAdded two documentation files:
ImpactExpected Improvements✅ Service Startup Reliability
✅ Kafka Test Reliability
✅ Test Execution Success
✅ Maintainability
Tests Expected to Improve
Testing & ValidationPre-merge Validation✅ CI workflow YAML syntax validated Post-merge MonitoringRecommended monitoring approach:
Success Metrics
Rollback PlanIf issues arise:
Future WorkShort Term
Medium Term
Long Term
Files ChangedTotal: 3 files changed, 379 insertions(+), 26 deletions(-) Related Issues
Commits
Reviewer NotesKey Changes to Review
Questions to Consider
ConclusionThis PR makes infrastructure-only changes to improve CI test reliability without modifying any test or application code. The changes are conservative, well-documented, and easily reversible. The improvements address root causes rather than symptoms, providing a foundation for long-term reliability. |
|
Allows Defer Message Action to match the pattern of having a backstop attribute. Ensures that we pass the defer period to requeue, and overwrite the subscription configured requeue |
PR Review: Defer Message Action Attribute BackstopThis PR adds a Core Framework Changes ✓The changes to
Bug: Backstop Handlers Swallow a Directly-Thrown
|
…Command#4028) * Accept ADR 0051 for error handling example applications Mark ADR 0051 (error-handling-examples) as Accepted and create design-approved marker for spec 0021-Error-Examples. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add unacceptableMessageLimitWindow to KafkaTaskQueueWithDLQ sample The sample intentionally throws on every 5th message. Without resetting the unacceptable message count, the pump would shut down. Setting the window to TimeSpan.Zero resets the count each pump cycle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add RMQTaskQueueWithDLQ sample for RabbitMQ RejectOnError with DLQ Four-project sample demonstrating RejectMessageOnErrorAsync on RabbitMQ: - Greetings library with handler that rejects every 5th message - GreetingsSender with TimedMessageGenerator IHostedService - GreetingsReceiverConsole with RmqSubscription and DLQ routing - DlqConsole consuming rejected messages with rejection metadata Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * tidy: add constructors and Delay property to DeferMessageAction Align DeferMessageAction with RejectMessageAction and DontAckAction by adding standard exception constructors and a TimeSpan? Delay property. This structural change prepares for the DeferMessageOnError backstop handler without altering existing behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add KafkaDeferOnError sample for Kafka defer with retry counter Three-project sample demonstrating DeferMessageAction on Kafka: - Handler throws DeferMessageAction directly (no attribute yet) - ConcurrentDictionary tracks retries per message ID - Every 3rd message defers twice then succeeds on 3rd attempt - Unique groupId kafka-DeferOnError-Sample avoids consumer collisions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add sync DeferMessageOnErrorHandler and attribute DeferMessageOnErrorAttribute decorates a handler method to catch unhandled exceptions and convert them to DeferMessageAction with a configurable delay. The delay flows from the attribute through InitializerParams to the handler. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add async DeferMessageOnErrorHandlerAsync and attribute Async counterpart to DeferMessageOnErrorHandler. Catches unhandled exceptions in async pipelines and converts them to DeferMessageAction with a configurable delay from the attribute. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add RMQDeferOnError sample for RabbitMQ defer with retry counter Three-project sample demonstrating DeferMessageAction on RabbitMQ: - Handler throws DeferMessageAction directly (no attribute yet) - ConcurrentDictionary tracks retries per message ID - Every 3rd message defers twice then succeeds on 3rd attempt - InMemorySchedulerFactory provides requeue delay support Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add sync defer handler success path characterization test Verifies DeferMessageOnErrorHandler passes through transparently when the inner handler completes without throwing an exception. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add async defer handler success path characterization test Verifies DeferMessageOnErrorHandlerAsync passes through transparently when the inner handler completes without throwing an exception. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: add attribute configuration tests for DeferMessageOnError Verifies both sync and async attributes return correct handler types, timing, step values, and InitializerParams containing the delay. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: pump extracts delay from DeferMessageAction and passes to requeue Both Proactor and Reactor now capture DeferMessageAction.Delay when catching the exception and pass it to RequeueMessage, which falls back to RequeueDelay when no delay is specified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add KafkaDontAckOnError sample for Kafka nack (offset not committed) Three-project sample demonstrating DontAckOnErrorAsync on Kafka: - Handler uses [DontAckOnErrorAsync(step: 0)], throws every 5th message - On Kafka, not committing the offset causes re-delivery on next poll - Unique groupId kafka-DontAckOnError-Sample avoids consumer collisions - README contrasts Kafka nack (no-op) with RabbitMQ nack (BasicNack) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Allows Defer Message Action to match the pattern of having a backstop attribute. Ensures that we pass the defer period to requeue, and overwrite the subscription configured requeue