gnodet-bot commented on code in PR #28147:
URL: https://github.com/apache/camel/pull/28147#discussion_r4457914450


##########
core/camel-core/src/test/java/org/apache/camel/processor/errorhandler/RouteStopCutOffLogTest.java:
##########
@@ -64,18 +72,38 @@ public void theCutOffIsOneWarnLine() throws Exception {
                              + (event.getThrown() != null ? " THROWN" : ""));
             }
         });
-        context.getShutdownStrategy().setTimeout(1);
+        // Use a bounded shutdown timeout so that stopRoute force-stops the 
route (stopping its
+        // error handlers) after the timeout, since the inflight exchange will 
not complete on its own.
+        // Using direct: (not seda:) ensures the route stop does not interrupt 
the processing thread.
+        context.getShutdownStrategy().setTimeout(5);
         context.getShutdownStrategy().setTimeUnit(TimeUnit.SECONDS);
 
-        context.getInflightRepository().setInflightBrowseEnabled(true);
+        // Send the exchange asynchronously; capture it via a future so we can 
wait for it to complete
+        CompletableFuture<Exchange> future = 
template.asyncSend("direct:start", exchange -> {
+            exchange.getIn().setBody(List.of("A"));
+        });
 
-        template.sendBody("seda:start", List.of("A", "B"));
-        // the exchange waits for a consumer on direct:shipment, as after a 
save that sends to a route not written yet
-        await().atMost(5, TimeUnit.SECONDS).until(() -> 
context.getInflightRepository().browse().stream()
-                .anyMatch(e -> "shipment".equals(e.getNodeId())));
+        // Wait for the processor inside the split to enter, then stop the 
route while the
+        // exchange is inflight inside the split. The stopped split error 
handler will call
+        // runNotAllowed(), set RouteStoppingException, and the outer stopped 
handler will log
+        // the cut-off WARN.
+        assertTrue(entered.await(5, TimeUnit.SECONDS), "Processor did not 
enter within 5 seconds");
         context.getRouteController().stopRoute("slow");
+        // Release the processor after the route is stopped; it exits 
normally, and the
+        // stopped error handler chain emits the WARN and completes the 
exchange.
+        release.countDown();
+
+        // Wait for the exchange to complete before asserting the log messages
+        Exchange result = null;
+        try {
+            result = future.get(10, TimeUnit.SECONDS);
+        } catch (TimeoutException e) {
+            org.junit.jupiter.api.Assertions.fail(
+                    "Exchange did not complete within 10 seconds — likely a 
hang in the error handler chain", e);
+        }

Review Comment:
   **Nit: use the static import instead of fully-qualified 
`org.junit.jupiter.api.Assertions.fail`.**
   
   The other test files in this PR already import `fail` statically. Add to 
this file:
   
   ```suggestion
   import static org.junit.jupiter.api.Assertions.fail;
   ```
   
   And at line 101:
   ```java
               fail("Exchange did not complete within 10 seconds — likely a 
hang in the error handler chain", e);
   ```



##########
core/camel-core/src/test/java/org/apache/camel/processor/errorhandler/RouteStopCutOffLogNestedTest.java:
##########
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.processor.errorhandler;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.Processor;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.log.ConsumingAppender;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * CAMEL-25823: verifies that a route-stop cut-off produces exactly one WARN 
line even when the exchange passes through
+ * multiple nested stopped error handlers. Two scenarios are tested:
+ *
+ * <ol>
+ * <li>{@code filter} — a filter wrapping a {@code .process()} step gives the 
exchange two stopped error-handler
+ * channels in route B (the {@code .process()} step's channel and the filter's 
channel) plus route A's running channel.
+ * The {@code setInterrupted(true)} marker set by the filter's stopped channel 
prevents route A's running handler from
+ * logging the same cut-off again.</li>
+ * <li>{@code multicast+split} — a {@code split} nested inside a {@code 
multicast} creates a sub-exchange copy.
+ * {@code MulticastProcessor.doDone()} propagates the {@code interrupted} flag 
from the sub-exchange to the original so

Review Comment:
   **Nit (carried over): comment credits `doDone()` with the propagation, but 
the reactive path goes through `taskCallback`.**
   
   `doDone()` covers only the `stopOnException` early-return path. For the 
normal reactive path (no `stopOnException`), the propagation happens in 
`MulticastReactiveTask.taskCallback`. Suggested correction for all four 
occurrences in this file (lines 53, 142, 169, 233) and line 147 in 
`RouteStopCutOffCallerRouteTest.java`:
   
   > `taskCallback` (reactive path) propagates `CUTOFF_WARN_LOGGED` and 
`interrupted` from the sub-exchange to the original immediately before 
`exchangeResult.accept()`. `doDone()` covers the `stopOnException` early-return 
path.



##########
core/camel-core/src/test/java/org/apache/camel/processor/errorhandler/RouteStopCutOffLogTest.java:
##########
@@ -126,10 +154,22 @@ protected RouteBuilder createRouteBuilder() {
         return new RouteBuilder() {
             @Override
             public void configure() {
-                from("seda:start").routeId("slow")
+                from("direct:start").routeId("slow")
                         .split(body())
-                            .log("Picked ${body}")
-                            .to("direct:shipment").id("shipment")
+                        .log("Picked ${body}")
+                        // Block here so the test can stop the route while an 
exchange is inflight
+                        // inside the split. When the route is stopped, the 
stopped error-handler
+                        // chain emits exactly one cut-off WARN.
+                        .process(exchange -> {
+                            entered.countDown();
+                            try {
+                                if (!release.await(30, TimeUnit.SECONDS)) {
+                                    throw new IllegalStateException("release 
latch timed out");
+                                }
+                            } catch (InterruptedException e) {
+                                Thread.currentThread().interrupt();
+                            }
+                        })
                         .end()
                         .to("mock:result");
             }

Review Comment:
   ⚠️ **Blocking: original DirectProducer / `seda + direct:shipment` scenario 
no longer covered.**
   
   The PR replaces the entire test body with the latch pattern. Main's version 
(`seda:start` → `split().to("direct:shipment")`) specifically tests the 
CAMEL-25502 scenario: `DirectProducer` sets `interrupted=true` on the exchange 
*before* the error handler's `runNotAllowed()` runs, which is exactly why 
`!isInterrupted()` was wrong as the gate — the WARN was silently suppressed on 
this path.
   
   The new latch pattern stops the route *before* the split item reaches 
`DirectProducer`, so `DirectProducer` never runs and the `interrupted` pre-set 
never happens. This means there is no test coverage for the scenario this PR 
claims to fix.
   
   Per davsclaus's request: keep main's version unchanged and add the latch 
scenario as a new `@Test` method (`theCutOffIsOneWarnLineViaLatch()` or 
similar). This way both paths are covered:
   - Main's `seda + direct:shipment` → validates WARN is emitted when 
`DirectProducer` pre-sets `interrupted=true`  
   - New latch method → validates WARN is emitted when the split processor is 
stopped mid-execution



##########
core/camel-core-processor/src/main/java/org/apache/camel/processor/MulticastProcessor.java:
##########
@@ -705,6 +706,18 @@ public void run() {
                             return;
                         }
 
+                        // Propagate the interrupted flag and the 
cut-off-WARN-logged marker from the
+                        // sub-exchange to the original exchange immediately, 
before the outer error handler's
+                        // callback fires. Only propagate when a stopped error 
handler in a nested EIP
+                        // (e.g. split inside multicast) already logged the 
cut-off WARN -- identified by
+                        // the CUTOFF_WARN_LOGGED property set by 
RedeliveryErrorHandler.runNotAllowed().
+                        // If interrupted was set by DirectProducer without 
logging a WARN, we must NOT
+                        // propagate: the outer error handler must still log 
its WARN for this cut-off.
+                        if 
(exchange.getProperty(RedeliveryErrorHandler.CUTOFF_WARN_LOGGED, false, 
Boolean.class)) {
+                            
original.getExchangeExtension().setInterrupted(true);
+                            
original.setProperty(RedeliveryErrorHandler.CUTOFF_WARN_LOGGED, Boolean.TRUE);

Review Comment:
   ⚠️ **Blocking: `MulticastTransactedTask.doRun()` is still missing the 
`CUTOFF_WARN_LOGGED` propagation.**
   
   `MulticastReactiveTask.taskCallback` (this hunk, line 716-718) and 
`doDone()` (line 1019-1021) both received the marker-guarded copy. But 
`MulticastTransactedTask.doRun()` submits via the same 
`completion.submit(exchangeResult -> { exchangeResult.accept(exchange); ... })` 
at line 855-857 — without any propagation block. In a transacted route with a 
split inside a multicast, the outer stopped error handler never sees 
`CUTOFF_WARN_LOGGED=true` on the sub-exchange and logs a second WARN.
   
   The upgrade guide says "in any nested EIP" — `MulticastTransactedTask` must 
match. Add the same block to its completion lambda:
   
   ```java
   completion.submit(exchangeResult -> {
       // Propagate CUTOFF_WARN_LOGGED from sub-exchange to original before 
outer error
       // handler's callback fires, same as MulticastReactiveTask.taskCallback.
       if (exchange.getProperty(RedeliveryErrorHandler.CUTOFF_WARN_LOGGED, 
false, Boolean.class)) {
           original.getExchangeExtension().setInterrupted(true);
           original.setProperty(RedeliveryErrorHandler.CUTOFF_WARN_LOGGED, 
Boolean.TRUE);
       }
       // accept the exchange as a result
       exchangeResult.accept(exchange);
   
       // aggregate exchanges if any
       aggregate();
   });
   ```



-- 
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