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 318aba6f5c06 CAMEL-26301: tokenizer EIP: wrap tokenizer call to 
prevent exchange hang on exception (#28486)
318aba6f5c06 is described below

commit 318aba6f5c06cc10a9ab73215f8323c123f812b5
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Oct 26 12:06:45 2026 +0100

    CAMEL-26301: tokenizer EIP: wrap tokenizer call to prevent exchange hang on 
exception (#28486)
    
    If tokenizer.tokenize(exchange) throws (e.g. NPE from a null body),
    the exception escapes process() without ever calling callback.done(true).
    In Camel's async model this means the AsyncCallback is never signalled
    and the caller hangs in DefaultAsyncProcessorAwaitManager.await forever.
    
    Fix:
    - TokenizerProcessor.process(): wrap the tokenizer call in a try/catch,
      set the exception on the exchange and call callback.done(true) on error.
      Follows the pattern used by TransformProcessor.
    - AbstractLangChain4JTokenizer.tokenize(): explicitly guard against a null
      body and throw a RuntimeCamelException with a clear message, instead of
      producing an NPE from the LangChain4j splitter.
    - TokenizerBodyTypesTest: add nullBodyCompletesWithException() to cover
      the hang scenario from CAMEL-26301.
---
 .../langchain4j/tokenizer/AbstractLangChain4JTokenizer.java  |  5 +++++
 .../langchain4j/tokenizer/TokenizerBodyTypesTest.java        |  9 +++++++++
 .../java/org/apache/camel/processor/TokenizerProcessor.java  | 12 ++++++++----
 3 files changed, 22 insertions(+), 4 deletions(-)

diff --git 
a/components/camel-ai/camel-langchain4j-tokenizer/src/main/java/org/apache/camel/component/langchain4j/tokenizer/AbstractLangChain4JTokenizer.java
 
b/components/camel-ai/camel-langchain4j-tokenizer/src/main/java/org/apache/camel/component/langchain4j/tokenizer/AbstractLangChain4JTokenizer.java
index 6cadffa749b4..9a87ef5ebe17 100644
--- 
a/components/camel-ai/camel-langchain4j-tokenizer/src/main/java/org/apache/camel/component/langchain4j/tokenizer/AbstractLangChain4JTokenizer.java
+++ 
b/components/camel-ai/camel-langchain4j-tokenizer/src/main/java/org/apache/camel/component/langchain4j/tokenizer/AbstractLangChain4JTokenizer.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.langchain4j.tokenizer;
 
 import dev.langchain4j.data.document.DocumentSplitter;
 import org.apache.camel.Exchange;
+import org.apache.camel.RuntimeCamelException;
 import 
org.apache.camel.component.langchain4j.tokenizer.config.LangChain4JConfiguration;
 import org.apache.camel.component.langchain4j.tokenizer.util.SplitterUtil;
 import org.apache.camel.spi.Tokenizer;
@@ -46,6 +47,10 @@ abstract class AbstractLangChain4JTokenizer<T extends 
LangChain4JConfiguration>
     public final String[] tokenize(Exchange exchange) {
         final String body = exchange.getIn().getBody(String.class);
 
+        if (body == null) {
+            throw new RuntimeCamelException("Cannot tokenize a null body");
+        }
+
         LOG.debug("Starting LangChain4j tokenizer for message: {}", body);
 
         final String[] split = SplitterUtil.split(splitter, body);
diff --git 
a/components/camel-ai/camel-langchain4j-tokenizer/src/test/java/org/apache/camel/component/langchain4j/tokenizer/TokenizerBodyTypesTest.java
 
b/components/camel-ai/camel-langchain4j-tokenizer/src/test/java/org/apache/camel/component/langchain4j/tokenizer/TokenizerBodyTypesTest.java
index 239e87699aea..b65e5e7526b0 100644
--- 
a/components/camel-ai/camel-langchain4j-tokenizer/src/test/java/org/apache/camel/component/langchain4j/tokenizer/TokenizerBodyTypesTest.java
+++ 
b/components/camel-ai/camel-langchain4j-tokenizer/src/test/java/org/apache/camel/component/langchain4j/tokenizer/TokenizerBodyTypesTest.java
@@ -77,6 +77,15 @@ public class TokenizerBodyTypesTest extends 
LangChain4JTokenizerTestSupport {
         assertArrayEquals(new String[] { order.toString() }, (String[]) 
exchange.getMessage().getBody());
     }
 
+    @Test
+    public void nullBodyCompletesWithException() {
+        // CAMEL-26301: a null body must not hang the exchange forever — the 
AsyncCallback must always be called.
+        // The tokenizer throws on null body; TokenizerProcessor must catch 
it, set it on the exchange,
+        // and signal the callback so the error handler can deal with it 
normally.
+        Exchange exchange = template.send("direct:start", e -> 
e.getMessage().setBody(null));
+        assertNotNull(exchange.getException(), "exchange must have an 
exception, not hang");
+    }
+
     @Override
     protected RoutesBuilder createRouteBuilder() {
         return new RouteBuilder() {
diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/TokenizerProcessor.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/TokenizerProcessor.java
index b794fa0bd72d..d6697d0c5d0f 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/TokenizerProcessor.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/TokenizerProcessor.java
@@ -53,11 +53,15 @@ public class TokenizerProcessor extends BaseProcessorSupport
 
     @Override
     public boolean process(Exchange exchange, AsyncCallback callback) {
-        final String[] tokenized = tokenizer.tokenize(exchange);
-
-        exchange.getMessage().setBody(tokenized);
+        try {
+            final String[] tokenized = tokenizer.tokenize(exchange);
+            exchange.getMessage().setBody(tokenized);
+        } catch (Exception e) {
+            exchange.setException(e);
+            callback.done(true);
+            return true;
+        }
         return processor.process(exchange, callback);
-
     }
 
     @Override

Reply via email to