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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 8e4d1c2a0e84 CAMEL-25314: camel-cxf - a suspended consumer must reject 
new requests (#27390)
8e4d1c2a0e84 is described below

commit 8e4d1c2a0e84e654007dccace7156e81f20902ff
Author: allthingssecurity <[email protected]>
AuthorDate: Tue Oct 6 14:50:41 2026 +0530

    CAMEL-25314: camel-cxf - a suspended consumer must reject new requests 
(#27390)
    
    * CAMEL-25314: camel-cxf - a suspended consumer must reject new requests
    
    CxfConsumer and CxfRsConsumer are Suspendable (CAMEL-12870), so that a
    graceful shutdown suspends them and keeps the CXF server running while the
    requests in flight complete. But they have no suspend logic, and their
    invokers never check the status: a suspended consumer kept accepting and
    routing new requests. A graceful shutdown with a steady flow of requests
    therefore waited for the shutdown timeout (and then forced the requests in
    flight), and suspending the route (route controller, JMX, route policies 
such
    as ThrottlingInflightRoutePolicy) did not stop it.
    
    The invokers now reject a new invocation (synchronous, or a new 
continuation)
    with HTTP status 503 while the consumer is suspending or suspended, like the
    HTTP consumers do. A resumed continuation belongs to a request in flight and
    still completes.
    
    The rejection is returned as a 503 response instead of a 
WebApplicationException, which CXF's
    WebApplicationExceptionMapper logged at WARN with its stack trace for every 
rejected request. The
    continuation is not looked up for the invocation of a sub resource locator 
(as before): the method of the
    sub resource is invoked next and is checked then, and getting the 
continuation twice starts the async
    context of the servlet request again. New test: a graceful stop rejects a 
new request with 503 while the
    asynchronous request in flight (continuation resumed after the suspend) 
still completes.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    
    * CAMEL-25314: camel-cxf - a rejected request of a suspended consumer is 
not logged as a CXF warning, and the test shuts down its bus
    
    CXF logs a fault it does not expect at WARN level with its stack trace, so 
a graceful shutdown under load logged one
    stack trace per rejected request. The rejection now sets a FaultListener on 
the request message that skips CXF's
    default logging of that fault (the consumer logs the rejection at DEBUG); 
the 503 response is unchanged.
    
    Review nit: CxfConsumerSuspendTest reuses one bus for its clients and shuts 
it down after each test.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 5.5 <[email protected]>
---
 .../camel/component/cxf/jaxrs/CxfRsInvoker.java    |  19 +-
 .../cxf/jaxrs/CxfRsConsumerSuspendTest.java        | 113 ++++++++++++
 .../camel/component/cxf/jaxws/CxfConsumer.java     |  26 +++
 .../cxf/jaxws/CxfConsumerSuspendTest.java          | 195 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  10 ++
 5 files changed, 360 insertions(+), 3 deletions(-)

diff --git 
a/components/camel-cxf/camel-cxf-rest/src/main/java/org/apache/camel/component/cxf/jaxrs/CxfRsInvoker.java
 
b/components/camel-cxf/camel-cxf-rest/src/main/java/org/apache/camel/component/cxf/jaxrs/CxfRsInvoker.java
index 768d01be35c1..190285304cce 100644
--- 
a/components/camel-cxf/camel-cxf-rest/src/main/java/org/apache/camel/component/cxf/jaxrs/CxfRsInvoker.java
+++ 
b/components/camel-cxf/camel-cxf-rest/src/main/java/org/apache/camel/component/cxf/jaxrs/CxfRsInvoker.java
@@ -22,6 +22,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.HttpHeaders;
 import jakarta.ws.rs.core.Request;
+import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.core.SecurityContext;
 import jakarta.ws.rs.core.UriInfo;
 
@@ -58,18 +59,30 @@ public class CxfRsInvoker extends JAXRSInvoker {
             Exchange cxfExchange, final Object serviceObject, Method method,
             Object[] paramArray)
             throws Exception {
+        OperationResourceInfo ori = 
cxfExchange.get(OperationResourceInfo.class);
+        // a sub resource locator only returns the sub resource, whose method 
is invoked next (and checked then)
+        Continuation continuation = null;
+        if (!ori.isSubResourceLocator()) {
+            continuation = endpoint.isSynchronous() ? null : 
getContinuation(cxfExchange);
+            // a resumed continuation belongs to a request that is in flight 
and must complete
+            if ((continuation == null || continuation.isNew()) && 
cxfRsConsumer.isSuspendingOrSuspended()) {
+                // a suspended consumer (suspended route, graceful shutdown) 
does not accept new requests, like the
+                // HTTP consumers, which answer 503 (a response rather than an 
exception, which CXF logs with its
+                // stack trace)
+                LOG.debug("Consumer suspended, cannot service request");
+                return 
Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
+            }
+        }
         Object response = null;
         if (endpoint.isPerformInvocation()) {
             response = super.performInvocation(cxfExchange, serviceObject, 
method, paramArray);
         }
         paramArray = insertExchange(method, paramArray, cxfExchange);
-        OperationResourceInfo ori = 
cxfExchange.get(OperationResourceInfo.class);
         if (ori.isSubResourceLocator()) {
             // don't delegate the sub resource locator call to camel processor
             return method.invoke(serviceObject, paramArray);
         }
-        Continuation continuation;
-        if (!endpoint.isSynchronous() && (continuation = 
getContinuation(cxfExchange)) != null) {
+        if (continuation != null) {
             LOG.trace("Calling the Camel async processors.");
             return asyncInvoke(cxfExchange, method, paramArray, continuation, 
response);
         } else {
diff --git 
a/components/camel-cxf/camel-cxf-rest/src/test/java/org/apache/camel/component/cxf/jaxrs/CxfRsConsumerSuspendTest.java
 
b/components/camel-cxf/camel-cxf-rest/src/test/java/org/apache/camel/component/cxf/jaxrs/CxfRsConsumerSuspendTest.java
new file mode 100644
index 000000000000..4780414c2550
--- /dev/null
+++ 
b/components/camel-cxf/camel-cxf-rest/src/test/java/org/apache/camel/component/cxf/jaxrs/CxfRsConsumerSuspendTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.cxf.jaxrs;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.cxf.common.CXFTestSupport;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A suspended CXF-RS consumer (suspended route, graceful shutdown, route 
policies) must not process new requests.
+ */
+class CxfRsConsumerSuspendTest extends CamelTestSupport {
+
+    private static final String CONTEXT = "/CxfRsConsumerSuspendTest";
+    private static final String URL = "http://localhost:"; + 
CXFTestSupport.getPort1() + CONTEXT
+                                      + "/rest/customerservice/customers/123";
+
+    private final AtomicInteger processed = new AtomicInteger();
+    private final CountDownLatch entered = new CountDownLatch(1);
+    private final CountDownLatch release = new CountDownLatch(1);
+    private volatile boolean block;
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                from("cxfrs://http://localhost:"; + CXFTestSupport.getPort1() + 
CONTEXT + "/rest"
+                     + 
"?resourceClasses=org.apache.camel.component.cxf.jaxrs.testbean.CustomerService")
+                        .routeId("rs")
+                        .process(exchange -> {
+                            if (processed.incrementAndGet() == 1 && block) {
+                                entered.countDown();
+                                release.await(20, TimeUnit.SECONDS);
+                            }
+                        })
+                        .setBody(constant("ok"));
+            }
+        };
+    }
+
+    @Test
+    void testSuspendedRouteRejectsRequests() throws Exception {
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = 
HttpRequest.newBuilder(URI.create(URL)).GET().build();
+        assertEquals(200, client.send(request, 
HttpResponse.BodyHandlers.ofString()).statusCode());
+
+        context.getRouteController().suspendRoute("rs");
+        assertEquals(503, client.send(request, 
HttpResponse.BodyHandlers.ofString()).statusCode(),
+                "A suspended route must answer 503");
+        assertEquals(1, processed.get(), "A suspended route must not process a 
request");
+
+        context.getRouteController().resumeRoute("rs");
+        assertEquals(200, client.send(request, 
HttpResponse.BodyHandlers.ofString()).statusCode());
+        assertEquals(2, processed.get());
+    }
+
+    @Test
+    void testGracefulStopCompletesInflightAndRejectsNewRequests() throws 
Exception {
+        block = true;
+        HttpClient client = HttpClient.newHttpClient();
+        HttpRequest request = 
HttpRequest.newBuilder(URI.create(URL)).GET().build();
+        // the consumer is asynchronous by default: the continuation of this 
request resumes after the suspend
+        CompletableFuture<HttpResponse<String>> inflight = 
client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
+        assertTrue(entered.await(20, TimeUnit.SECONDS));
+
+        CompletableFuture<Void> stop = CompletableFuture.runAsync(() -> {
+            try {
+                context.getRouteController().stopRoute("rs", 20, 
TimeUnit.SECONDS);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
+        ServiceSupport consumer = (ServiceSupport) 
context.getRoute("rs").getConsumer();
+        await().atMost(20, 
TimeUnit.SECONDS).until(consumer::isSuspendingOrSuspended);
+
+        assertEquals(503, client.send(request, 
HttpResponse.BodyHandlers.ofString()).statusCode(),
+                "A request received while stopping must be rejected");
+        assertEquals(1, processed.get(), "A request received while stopping 
must not be processed");
+
+        release.countDown();
+        assertEquals(200, inflight.get(20, TimeUnit.SECONDS).statusCode());
+        stop.get(30, TimeUnit.SECONDS);
+    }
+}
diff --git 
a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfConsumer.java
 
b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfConsumer.java
index b636b78b3726..00b1da356367 100644
--- 
a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfConsumer.java
+++ 
b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfConsumer.java
@@ -48,6 +48,7 @@ import org.apache.cxf.continuations.ContinuationProvider;
 import org.apache.cxf.endpoint.Server;
 import org.apache.cxf.frontend.ServerFactoryBean;
 import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.logging.FaultListener;
 import org.apache.cxf.message.Exchange;
 import org.apache.cxf.message.FaultMode;
 import org.apache.cxf.message.Message;
@@ -67,6 +68,8 @@ import org.slf4j.LoggerFactory;
 public class CxfConsumer extends DefaultConsumer implements Suspendable {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(CxfConsumer.class);
+    // skips the default logging of a fault by CXF
+    private static final FaultListener NO_FAULT_LOGGING = (exception, 
description, message) -> false;
 
     private Server server;
     private CxfEndpoint cxfEndpoint;
@@ -168,14 +171,37 @@ public class CxfConsumer extends DefaultConsumer 
implements Suspendable {
             Continuation continuation;
             if (!endpoint.isSynchronous() && 
isAsyncInvocationSupported(cxfExchange)
                     && (continuation = getContinuation(cxfExchange)) != null) {
+                // a resumed continuation belongs to a request that is in 
flight and must complete
+                if (continuation.isNew()) {
+                    rejectIfSuspended(cxfExchange);
+                }
                 LOG.trace("Calling the Camel async processors.");
                 return asyncInvoke(cxfExchange, continuation);
             } else {
+                rejectIfSuspended(cxfExchange);
                 LOG.trace("Calling the Camel sync processors.");
                 return syncInvoke(cxfExchange);
             }
         }
 
+        // a suspended consumer (suspended route, graceful shutdown) does not 
accept new requests, like the HTTP
+        // consumers, which answer 503
+        private void rejectIfSuspended(Exchange cxfExchange) {
+            if (isSuspendingOrSuspended()) {
+                LOG.debug("Consumer suspended, cannot service request");
+                // CXF logs a fault it does not expect at WARN level with its 
stack trace (PhaseInterceptorChain),
+                // unless a FaultListener of the message says otherwise: a 
rejected request is expected while
+                // suspended (e.g. every request during a graceful shutdown 
under load), so it is only logged above
+                Message in = cxfExchange.getInMessage();
+                if (in != null) {
+                    in.put(FaultListener.class.getName(), NO_FAULT_LOGGING);
+                }
+                Fault fault = new Fault(new IllegalStateException("Service 
unavailable: the consumer is suspended"));
+                fault.setStatusCode(503);
+                throw fault;
+            }
+        }
+
         // NOTE this code cannot work with CXF 2.2.x and JMSContinuation
         // as it doesn't break out the interceptor chain when we call it
         private Object asyncInvoke(Exchange cxfExchange, final Continuation 
continuation) {
diff --git 
a/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfConsumerSuspendTest.java
 
b/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfConsumerSuspendTest.java
new file mode 100644
index 000000000000..5f22a2e74342
--- /dev/null
+++ 
b/components/camel-cxf/camel-cxf-soap/src/test/java/org/apache/camel/component/cxf/jaxws/CxfConsumerSuspendTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.cxf.jaxws;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.cxf.common.CXFTestSupport;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.frontend.ClientFactoryBean;
+import org.apache.cxf.frontend.ClientProxyFactoryBean;
+import org.apache.cxf.transport.http.HTTPException;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.config.Property;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A suspended CXF consumer (suspended route, graceful shutdown, route 
policies) must not process new requests, while
+ * the requests in flight complete.
+ */
+class CxfConsumerSuspendTest extends CamelTestSupport {
+
+    private static final String ADDRESS = "http://localhost:"; + 
CXFTestSupport.getPort1() + "/CxfConsumerSuspendTest/test";
+
+    private final AtomicInteger processed = new AtomicInteger();
+    private final CountDownLatch entered = new CountDownLatch(1);
+    private final CountDownLatch release = new CountDownLatch(1);
+    private volatile boolean block;
+    // the bus of the test clients
+    private Bus clientBus;
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                from("cxf://" + ADDRESS + 
"?serviceClass=org.apache.camel.component.cxf.jaxws.HelloService")
+                        .routeId("cxf")
+                        .process(exchange -> {
+                            if (processed.incrementAndGet() == 1 && block) {
+                                entered.countDown();
+                                release.await(20, TimeUnit.SECONDS);
+                            }
+                            String text = (String) 
exchange.getIn().getBody(List.class).get(0);
+                            exchange.getMessage().setBody("echo " + text);
+                        });
+            }
+        };
+    }
+
+    @Test
+    void testSuspendedRouteRejectsRequests() throws Exception {
+        HelloService client = createClient();
+        assertEquals("echo a", client.echo("a"));
+
+        context.getRouteController().suspendRoute("cxf");
+        Exception e = assertThrows(Exception.class, () -> client.echo("b"), "A 
suspended route must not accept a request");
+        assertServiceUnavailable(e);
+        assertEquals(1, processed.get(), "A suspended route must not process a 
request");
+
+        context.getRouteController().resumeRoute("cxf");
+        assertEquals("echo c", client.echo("c"));
+        assertEquals(2, processed.get());
+    }
+
+    @Test
+    void testRejectedRequestIsNotLoggedAsWarning() throws Exception {
+        HelloService client = createClient();
+        assertEquals("echo a", client.echo("a"));
+        context.getRouteController().suspendRoute("cxf");
+
+        // CXF logs an unexpected fault at WARN level with its stack trace: a 
rejected request is expected while the
+        // consumer is suspended (e.g. every request during a graceful 
shutdown under load)
+        // (the client, on this thread, logs the 503 fault it receives)
+        String clientThread = Thread.currentThread().getName();
+        List<LogEvent> warnings = new CopyOnWriteArrayList<>();
+        AbstractAppender appender = new 
AbstractAppender("CxfConsumerSuspendTest", null, null, true, 
Property.EMPTY_ARRAY) {
+            @Override
+            public void append(LogEvent event) {
+                if (event.getLevel().isMoreSpecificThan(Level.WARN) && 
event.getLoggerName().startsWith("org.apache.cxf")
+                        && !clientThread.equals(event.getThreadName())) {
+                    warnings.add(event.toImmutable());
+                }
+            }
+        };
+        appender.start();
+        Logger cxfLogger = (Logger) LogManager.getLogger("org.apache.cxf");
+        cxfLogger.addAppender(appender);
+        try {
+            Exception e = assertThrows(Exception.class, () -> 
client.echo("b"));
+            assertServiceUnavailable(e);
+        } finally {
+            cxfLogger.removeAppender(appender);
+            appender.stop();
+        }
+        assertTrue(warnings.isEmpty(), () -> "A rejected request must not be 
logged as a warning by the CXF server: "
+                                             + warnings.stream()
+                                                     .map(w -> 
w.getThreadName() + " " + w.getLoggerName() + ": "
+                                                               + 
w.getMessage().getFormattedMessage())
+                                                     .toList());
+    }
+
+    @Test
+    void testGracefulStopCompletesInflightAndRejectsNewRequests() throws 
Exception {
+        block = true;
+        HelloService client = createClient();
+        CompletableFuture<String> inflight = CompletableFuture.supplyAsync(() 
-> {
+            try {
+                return client.echo("a");
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
+        assertTrue(entered.await(20, TimeUnit.SECONDS));
+
+        CompletableFuture<Void> stop = CompletableFuture.runAsync(() -> {
+            try {
+                context.getRouteController().stopRoute("cxf", 20, 
TimeUnit.SECONDS);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
+        ServiceSupport consumer = (ServiceSupport) 
context.getRoute("cxf").getConsumer();
+        await().atMost(20, 
TimeUnit.SECONDS).until(consumer::isSuspendingOrSuspended);
+
+        HelloService other = createClient();
+        Exception e = assertThrows(Exception.class, () -> other.echo("b"),
+                "A request received while stopping must be rejected");
+        assertServiceUnavailable(e);
+        assertEquals(1, processed.get(), "A request received while stopping 
must not be processed");
+
+        release.countDown();
+        assertEquals("echo a", inflight.get(20, TimeUnit.SECONDS));
+        stop.get(30, TimeUnit.SECONDS);
+    }
+
+    private static void assertServiceUnavailable(Exception e) {
+        HTTPException http = ObjectHelper.getException(HTTPException.class, e);
+        assertNotNull(http, "Expected an HTTP 503 response, got: " + e);
+        assertEquals(503, http.getResponseCode());
+    }
+
+    private HelloService createClient() {
+        if (clientBus == null) {
+            clientBus = BusFactory.newInstance().createBus();
+        }
+        ClientProxyFactoryBean proxyFactory = new ClientProxyFactoryBean();
+        ClientFactoryBean clientBean = proxyFactory.getClientFactoryBean();
+        clientBean.setAddress(ADDRESS);
+        clientBean.setServiceClass(HelloService.class);
+        clientBean.setBus(clientBus);
+        return (HelloService) proxyFactory.create();
+    }
+
+    @AfterEach
+    void shutdownClientBus() {
+        if (clientBus != null) {
+            clientBus.shutdown(true);
+            clientBus = null;
+        }
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 12fc32e9a85e..d254cf412111 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -2593,6 +2593,16 @@ A route that relies on an undeclared exception's message 
reaching the caller mus
 
cxf://http://localhost:8080/service?serviceClass=com.example.MyService&muteException=false
 ----
 
+==== A suspended consumer rejects new requests
+
+The CXF consumers (`cxf` and `cxfrs`) are suspendable, so that a graceful 
shutdown keeps the CXF server running
+while the requests in flight complete, but a suspended consumer kept accepting 
and routing new requests: during a
+graceful shutdown (until the shutdown timeout), and while the route was 
suspended (route controller, JMX, route
+policies such as `ThrottlingInflightRoutePolicy`). A suspended consumer now 
rejects a new request with HTTP status
+503 (Service Unavailable), like the HTTP consumers do; the requests in flight, 
including asynchronous ones whose
+continuation resumes later, still complete. The `cxf` consumer rejects the 
request with a SOAP fault, which is
+logged at DEBUG level only; over a transport other than HTTP the caller gets 
the fault only.
+
 === camel-thrift
 
 The `thrift` data format used to deserialize into its own `defaultInstance` 
and return that object to

Reply via email to