davsclaus commented on code in PR #27834:
URL: https://github.com/apache/camel/pull/27834#discussion_r4275137236


##########
components/camel-azure/camel-azure-servicebus/src/main/java/org/apache/camel/component/azure/servicebus/ServiceBusConsumer.java:
##########
@@ -405,5 +407,43 @@ public void onFailure(Exchange exchange) {
                 pendingExchanges.decrementAndGet();
             }
         }
+
+        /**
+         * Describes the failure for the dead-letter queue, bounded so that 
the diagnostics cannot be what makes the
+         * broker reject the message.
+         */
+        private DeadLetterOptions deadLetterOptions(Exception cause) {
+            String stackTrace = Arrays.stream(cause.getStackTrace())
+                    .map(StackTraceElement::toString)
+                    .collect(Collectors.joining("\n"));
+
+            return new DeadLetterOptions()
+                    .setDeadLetterReason(StringHelper.limitLength(
+                            String.format("%s: %s", 
cause.getClass().getName(), cause.getMessage()),
+                            MAX_DEAD_LETTER_REASON_LENGTH, TRUNCATED_SUFFIX))
+                    .setDeadLetterErrorDescription(StringHelper.limitLength(
+                            stackTrace, MAX_DEAD_LETTER_DESCRIPTION_LENGTH, 
TRUNCATED_SUFFIX));

Review Comment:
   `limitLength` appends the suffix after cutting to `maxLength`, so the result 
can be `MAX + 3` characters long. If the cap is meant as a hard limit (for 
example 4096), use `MAX_... - TRUNCATED_SUFFIX.length()` as the `maxLength`.



##########
components/camel-azure/camel-azure-servicebus/src/main/java/org/apache/camel/component/azure/servicebus/ServiceBusConsumer.java:
##########
@@ -385,17 +394,10 @@ public void onFailure(Exchange exchange) {
                 if (getConfiguration().getServiceBusReceiveMode() == 
ServiceBusReceiveMode.PEEK_LOCK) {
                     if (getConfiguration().isEnableDeadLettering() && 
(ObjectHelper.isEmpty(getConfiguration().getSubQueue())
                             || 
ObjectHelper.equal(getConfiguration().getSubQueue(), SubQueue.NONE))) {
-                        DeadLetterOptions deadLetterOptions = new 
DeadLetterOptions();
                         if (cause != null) {
-                            deadLetterOptions
-                                    .setDeadLetterReason(
-                                            String.format("%s: %s", 
cause.getClass().getName(), cause.getMessage()));
-                            
deadLetterOptions.setDeadLetterErrorDescription(Arrays.stream(cause.getStackTrace())
-                                    .map(StackTraceElement::toString)
-                                    .collect(Collectors.joining("\n")));
-                            messageContext.deadLetter(deadLetterOptions);
+                            deadLetter(exchange, deadLetterOptions(cause));
                         } else {
-                            messageContext.deadLetter();
+                            deadLetter(exchange, null);
                         }
                     } else {
                         messageContext.abandon();

Review Comment:
   Non-blocking: `abandon()` has the same unguarded shape, in the same method. 
A failure here only shows up as the generic WARN from the unit of work. You 
could route it through `getExceptionHandler()` the same way as the dead-letter 
call.



##########
components/camel-azure/camel-azure-servicebus/src/main/java/org/apache/camel/component/azure/servicebus/ServiceBusConsumer.java:
##########
@@ -405,5 +407,43 @@ public void onFailure(Exchange exchange) {
                 pendingExchanges.decrementAndGet();
             }
         }
+
+        /**
+         * Describes the failure for the dead-letter queue, bounded so that 
the diagnostics cannot be what makes the
+         * broker reject the message.
+         */
+        private DeadLetterOptions deadLetterOptions(Exception cause) {
+            String stackTrace = Arrays.stream(cause.getStackTrace())
+                    .map(StackTraceElement::toString)
+                    .collect(Collectors.joining("\n"));
+
+            return new DeadLetterOptions()
+                    .setDeadLetterReason(StringHelper.limitLength(
+                            String.format("%s: %s", 
cause.getClass().getName(), cause.getMessage()),
+                            MAX_DEAD_LETTER_REASON_LENGTH, TRUNCATED_SUFFIX))
+                    .setDeadLetterErrorDescription(StringHelper.limitLength(
+                            stackTrace, MAX_DEAD_LETTER_DESCRIPTION_LENGTH, 
TRUNCATED_SUFFIX));
+        }
+
+        /**
+         * Dead-letters the message, reporting a rejected request instead of 
letting it leave this synchronization.
+         * <p/>
+         * Nothing else would report it: the exception would surface inside 
the Azure SDK's processor callback, the
+         * message would stay locked until the lock expired, and Service Bus 
would redeliver it until
+         * {@code maxDeliveryCount} was reached - dead-lettering it with its 
own reason, which is the opposite of what
+         * {@code enableDeadLettering} is for.

Review Comment:
   Small correction: an exception from `onFailure` does not reach the Azure SDK 
callback. `UnitOfWorkHelper.doneSynchronization` catches it and logs `WARN 
Exception occurred during onCompletion. This exception will be ignored.` The 
new value here is that it goes to the consumer's `ExceptionHandler` 
(bridgeErrorHandler, custom handlers) with a clearer message. Could the javadoc 
say that instead?



##########
docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc:
##########
@@ -414,6 +414,20 @@ Previously it returned only the first chunk of the event 
log (at most 500 events
 any sign. A wide `after`/`before` window now returns every event in one list: 
that is one request per 500 events plus
 one that finds the end of the log, and more memory. Narrow the window when the 
event log is large.
 
+=== camel-azure-servicebus - the dead-letter reason and description are bounded
+
+When the consumer dead-letters a message after a failed route with 
`enableDeadLettering=true`, the reason and the
+error description it writes are now capped - the reason at 1024 characters and 
the description, which holds the stack
+trace, at 8192. A value that was cut ends with `...` so it is clear it was 
truncated rather than that the trace ended
+there.
+
+Azure recommends putting the exception type in the reason and its stack trace 
in the description, but warns that doing
+so can push a message past the 256 KB quota of the Standard tier. Nothing 
bounded either field before, so a long
+stack trace could make the broker reject the dead-letter request - and because 
that rejection was not handled, the
+message was then left locked and redelivered until `maxDeliveryCount` was 
reached, at which point the broker
+dead-lettered it with its own `MaxDeliveryCountExceeded` reason. A rejected 
request is now reported through the
+consumer's exception handler instead.

Review Comment:
   The upgrade guide is for migration notes, so this paragraph could be a 
single sentence: "A dead-letter request the broker rejects is now reported 
through the consumer's exception handler." Also, "that rejection was not 
handled" is not quite right: Camel's unit of work caught it and logged a WARN.



##########
components/camel-azure/camel-azure-servicebus/src/main/java/org/apache/camel/component/azure/servicebus/ServiceBusConsumer.java:
##########
@@ -46,13 +46,22 @@
 import org.apache.camel.support.PluginHelper;
 import org.apache.camel.support.SynchronizationAdapter;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class ServiceBusConsumer extends DefaultConsumer implements 
ShutdownAware {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(ServiceBusConsumer.class);
     private static final int LOCK_RENEW_INTERVAL_SECONDS = 10;
+    // Azure recommends putting the exception type in the dead-letter reason 
and its stack trace in the description,
+    // but warns that doing so "might result in some messages exceeding the 
256 KB quota limit for the Standard tier".
+    // A rejected dead-letter request leaves the message locked, so cap both 
rather than risk the quota: the head of a
+    // stack trace carries the cause, the tail is error-handler frames.
+    private static final int MAX_DEAD_LETTER_REASON_LENGTH = 1024;
+    private static final int MAX_DEAD_LETTER_DESCRIPTION_LENGTH = 8192;

Review Comment:
   The .NET SDK checks both the reason and the description against 
`Constants.MaxDeadLetterReasonLength = 4096` (`AmqpReceiver.cs`, 
`Argument.AssertNotTooLong`), and the JS SDK has the same 4096 constant. With 
8192, a typical Camel stack trace of 4-8K characters would still go above that 
limit. Suggest 4096 for the description (and 4096 or lower for the reason).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to