You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/07/18 19:27:30 UTC

(camel) branch fix/CAMEL-24185 created (now fa1a75a7f38c)

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a change to branch fix/CAMEL-24185
in repository https://gitbox.apache.org/repos/asf/camel.git


      at fa1a75a7f38c CAMEL-24185: Circuit Breaker EIP - suspend/resume should preserve breaker state

This branch includes the following new commits:

     new fa1a75a7f38c CAMEL-24185: Circuit Breaker EIP - suspend/resume should preserve breaker state

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(camel) 01/01: CAMEL-24185: Circuit Breaker EIP - suspend/resume should preserve breaker state

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch fix/CAMEL-24185
in repository https://gitbox.apache.org/repos/asf/camel.git

commit fa1a75a7f38c1bd42eaee186b399d2e2aa3b7f33
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Jul 18 21:27:15 2026 +0200

    CAMEL-24185: Circuit Breaker EIP - suspend/resume should preserve breaker state
    
    Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
    Signed-off-by: Claus Ibsen <cl...@gmail.com>
---
 .../faulttolerance/FaultToleranceProcessor.java    |  10 ++
 .../FaultToleranceSuspendResumeTest.java           | 109 ++++++++++++++++++++
 .../resilience4j/ResilienceProcessor.java          |  10 ++
 .../resilience4j/ResilienceSuspendResumeTest.java  | 114 +++++++++++++++++++++
 4 files changed, 243 insertions(+)

diff --git a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceProcessor.java b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceProcessor.java
index 0127ee790f06..cc6bd1b37e91 100644
--- a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceProcessor.java
+++ b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/main/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceProcessor.java
@@ -381,6 +381,16 @@ public class FaultToleranceProcessor extends BaseProcessorSupport
         ServiceHelper.startService(processorExchangeFactory, taskFactory, fallbackTaskFactory, processor);
     }
 
+    @Override
+    protected void doSuspend() throws Exception {
+        // noop - preserve circuit breaker state across suspend/resume
+    }
+
+    @Override
+    protected void doResume() throws Exception {
+        // noop - circuit breaker state was preserved during suspend
+    }
+
     @Override
     protected void doStop() throws Exception {
         if (shutdownExecutorService && executorService != null) {
diff --git a/components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceSuspendResumeTest.java b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceSuspendResumeTest.java
new file mode 100644
index 000000000000..4c259aa2c328
--- /dev/null
+++ b/components/camel-microprofile/camel-microprofile-fault-tolerance/src/test/java/org/apache/camel/component/microprofile/faulttolerance/FaultToleranceSuspendResumeTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.component.microprofile.faulttolerance;
+
+import org.apache.camel.Navigate;
+import org.apache.camel.Processor;
+import org.apache.camel.Route;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.CircuitBreakerConstants;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class FaultToleranceSuspendResumeTest extends CamelTestSupport {
+
+    @Test
+    public void testSuspendResumePreservesCircuitBreakerState() throws Exception {
+        // send a message to verify the route works
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        template.sendBody("direct:start", "Hello World");
+        MockEndpoint.assertIsSatisfied(context);
+
+        // find the FaultToleranceProcessor
+        FaultToleranceProcessor processor = findFaultToleranceProcessor();
+        assertNotNull(processor);
+
+        // verify circuit breaker starts in CLOSED state
+        assertEquals("CLOSED", processor.getCircuitBreakerState());
+
+        // suspend the processor
+        processor.suspend();
+
+        // circuit breaker state must still be CLOSED after suspend
+        assertEquals("CLOSED", processor.getCircuitBreakerState());
+
+        // resume the processor
+        processor.resume();
+
+        // circuit breaker state must still be CLOSED after resume
+        assertEquals("CLOSED", processor.getCircuitBreakerState());
+
+        // verify route still works after resume
+        MockEndpoint.resetMocks(context);
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:result").expectedPropertyReceived(CircuitBreakerConstants.RESPONSE_SUCCESSFUL_EXECUTION, true);
+        template.sendBody("direct:start", "Hello World");
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    private FaultToleranceProcessor findFaultToleranceProcessor() {
+        Route route = context.getRoute("start");
+        return findProcessor(route.getProcessor());
+    }
+
+    private FaultToleranceProcessor findProcessor(Processor processor) {
+        if (processor instanceof FaultToleranceProcessor) {
+            return (FaultToleranceProcessor) processor;
+        }
+        if (processor instanceof Navigate) {
+            Navigate<?> nav = (Navigate<?>) processor;
+            if (nav.hasNext()) {
+                for (Object next : nav.next()) {
+                    if (next instanceof Processor) {
+                        FaultToleranceProcessor found = findProcessor((Processor) next);
+                        if (found != null) {
+                            return found;
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").routeId("start")
+                        .circuitBreaker()
+                            .to("direct:foo")
+                        .onFallback()
+                            .transform().constant("Fallback message")
+                        .end()
+                        .to("log:result").to("mock:result");
+
+                from("direct:foo").transform().constant("Bye World");
+            }
+        };
+    }
+}
diff --git a/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java b/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
index 3293361bc040..af8310688af6 100644
--- a/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
+++ b/components/camel-resilience4j/src/main/java/org/apache/camel/component/resilience4j/ResilienceProcessor.java
@@ -176,6 +176,16 @@ public class ResilienceProcessor extends BaseProcessorSupport
         ServiceHelper.startService(processorExchangeFactory, taskFactory, fallbackTaskFactory, processor);
     }
 
+    @Override
+    protected void doSuspend() throws Exception {
+        // noop - preserve circuit breaker state across suspend/resume
+    }
+
+    @Override
+    protected void doResume() throws Exception {
+        // noop - circuit breaker state was preserved during suspend
+    }
+
     @Override
     protected void doStop() throws Exception {
         if (shutdownExecutorService && executorService != null) {
diff --git a/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceSuspendResumeTest.java b/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceSuspendResumeTest.java
new file mode 100644
index 000000000000..8a2bde1b16c8
--- /dev/null
+++ b/components/camel-resilience4j/src/test/java/org/apache/camel/component/resilience4j/ResilienceSuspendResumeTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.component.resilience4j;
+
+import org.apache.camel.Navigate;
+import org.apache.camel.Processor;
+import org.apache.camel.Route;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.CircuitBreakerConstants;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class ResilienceSuspendResumeTest extends CamelTestSupport {
+
+    @Test
+    public void testSuspendResumePreservesCircuitBreakerState() throws Exception {
+        // send a message to verify the route works
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        template.sendBody("direct:start", "Hello World");
+        MockEndpoint.assertIsSatisfied(context);
+
+        // find the ResilienceProcessor
+        ResilienceProcessor processor = findResilienceProcessor();
+        assertNotNull(processor);
+
+        // verify circuit breaker starts in CLOSED state
+        assertEquals("CLOSED", processor.getCircuitBreakerState());
+
+        // transition to OPEN state
+        processor.transitionToOpenState();
+        assertEquals("OPEN", processor.getCircuitBreakerState());
+
+        // suspend the processor
+        processor.suspend();
+
+        // resume the processor
+        processor.resume();
+
+        // circuit breaker state must still be OPEN after suspend/resume
+        assertEquals("OPEN", processor.getCircuitBreakerState());
+
+        // transition back to CLOSED for further use
+        processor.transitionToCloseState();
+        assertEquals("CLOSED", processor.getCircuitBreakerState());
+
+        // verify route still works after resume
+        MockEndpoint.resetMocks(context);
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:result").expectedPropertyReceived(CircuitBreakerConstants.RESPONSE_SUCCESSFUL_EXECUTION, true);
+        template.sendBody("direct:start", "Hello World");
+        MockEndpoint.assertIsSatisfied(context);
+    }
+
+    private ResilienceProcessor findResilienceProcessor() {
+        Route route = context.getRoute("start");
+        return findProcessor(route.getProcessor());
+    }
+
+    private ResilienceProcessor findProcessor(Processor processor) {
+        if (processor instanceof ResilienceProcessor) {
+            return (ResilienceProcessor) processor;
+        }
+        if (processor instanceof Navigate) {
+            Navigate<?> nav = (Navigate<?>) processor;
+            if (nav.hasNext()) {
+                for (Object next : nav.next()) {
+                    if (next instanceof Processor) {
+                        ResilienceProcessor found = findProcessor((Processor) next);
+                        if (found != null) {
+                            return found;
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start").routeId("start")
+                        .circuitBreaker()
+                            .to("direct:foo")
+                        .onFallback()
+                            .transform().constant("Fallback message")
+                        .end()
+                        .to("log:result").to("mock:result");
+
+                from("direct:foo").transform().constant("Bye World");
+            }
+        };
+    }
+}