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

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


The following commit(s) were added to refs/heads/camel-4.18.x by this push:
     new aaeaead8429f [backport camel-4.18.x] CAMEL-25930: camel-file - 
GenericFilePollingConsumer should return the polled exchange directly from 
receive (#28267)
aaeaead8429f is described below

commit aaeaead8429fb36e48682d2c03a4a7d9f66abc80
Author: Guillaume Nodet <[email protected]>
AuthorDate: Thu Oct 22 09:16:20 2026 +0200

    [backport camel-4.18.x] CAMEL-25930: camel-file - 
GenericFilePollingConsumer should return the polled exchange directly from 
receive (#28267)
    
    GenericFilePollingConsumer handed the polled exchange from process() to
    pollAndTake() through a shared BlockingQueue, so with concurrent callers of
    receive() a thread could take the exchange polled by another thread. The
    polled exchange is now returned directly from receive.
    
    Backport of #28248.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../component/file/GenericFilePollingConsumer.java | 52 ++++++++-----
 .../file/FilePollEnrichDynamicFileNameTest.java    | 87 ++++++++++++++++++++++
 2 files changed, 121 insertions(+), 18 deletions(-)

diff --git 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFilePollingConsumer.java
 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFilePollingConsumer.java
index 9289d431ede6..4cd03b0a37ca 100644
--- 
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFilePollingConsumer.java
+++ 
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFilePollingConsumer.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.file;
 import org.apache.camel.Consumer;
 import org.apache.camel.DynamicPollingConsumer;
 import org.apache.camel.Exchange;
+import org.apache.camel.PooledExchange;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.spi.PollingConsumerPollStrategy;
 import org.apache.camel.support.EventDrivenPollingConsumer;
@@ -31,6 +32,8 @@ public class GenericFilePollingConsumer extends 
EventDrivenPollingConsumer imple
 
     private static final Logger LOG = 
LoggerFactory.getLogger(GenericFilePollingConsumer.class);
     private final long delay;
+    // the exchange found by the poll of the calling thread, returned directly 
by receive
+    private final ThreadLocal<Exchange[]> polledByThisThread = new 
ThreadLocal<>();
 
     public GenericFilePollingConsumer(GenericFileEndpoint endpoint) {
         super(endpoint);
@@ -73,12 +76,7 @@ public class GenericFilePollingConsumer extends 
EventDrivenPollingConsumer imple
         if (LOG.isTraceEnabled()) {
             LOG.trace("receiveNoWait polling file: {}", 
getConsumer().getEndpoint());
         }
-        int polled = doReceive(exchange, 0);
-        if (polled > 0) {
-            return super.receive(0);
-        } else {
-            return null;
-        }
+        return pollAndTake(exchange, 0, 0);
     }
 
     @Override
@@ -86,12 +84,7 @@ public class GenericFilePollingConsumer extends 
EventDrivenPollingConsumer imple
         if (LOG.isTraceEnabled()) {
             LOG.trace("receive polling file: {}", getConsumer().getEndpoint());
         }
-        int polled = doReceive(exchange, Long.MAX_VALUE);
-        if (polled > 0) {
-            return super.receive();
-        } else {
-            return null;
-        }
+        return pollAndTake(exchange, Long.MAX_VALUE, -1);
     }
 
     @Override
@@ -99,12 +92,7 @@ public class GenericFilePollingConsumer extends 
EventDrivenPollingConsumer imple
         if (LOG.isTraceEnabled()) {
             LOG.trace("receive({}) polling file: {}", timeout, 
getConsumer().getEndpoint());
         }
-        int polled = doReceive(exchange, timeout);
-        if (polled > 0) {
-            return super.receive(timeout);
-        } else {
-            return null;
-        }
+        return pollAndTake(exchange, timeout, timeout);
     }
 
     @Override
@@ -122,6 +110,28 @@ public class GenericFilePollingConsumer extends 
EventDrivenPollingConsumer imple
         return receive(null, timeout);
     }
 
+    /**
+     * Polls and returns the exchange found by this poll, or the head of the 
queue when the exchange was processed on
+     * another thread.
+     */
+    private Exchange pollAndTake(Exchange exchange, long pollTimeout, long 
receiveTimeout) {
+        Exchange[] slot = new Exchange[1];
+        polledByThisThread.set(slot);
+        int polled;
+        try {
+            polled = doReceive(exchange, pollTimeout);
+        } finally {
+            polledByThisThread.remove();
+        }
+        if (slot[0] != null) {
+            return slot[0];
+        } else if (polled > 0) {
+            return receiveTimeout < 0 ? super.receive() : 
super.receive(receiveTimeout);
+        } else {
+            return null;
+        }
+    }
+
     protected int doReceive(Exchange exchange, long timeout) {
         int retryCounter = -1;
         boolean done = false;
@@ -217,6 +227,12 @@ public class GenericFilePollingConsumer extends 
EventDrivenPollingConsumer imple
         if (name != null) {
             LOG.debug("Received file: {}", name);
         }
+        Exchange[] slot = polledByThisThread.get();
+        if (slot != null && slot[0] == null) {
+            // a receive call on this thread is polling, so return the 
exchange to that call
+            slot[0] = isCopy() || exchange instanceof PooledExchange ? 
prepareCopy(exchange, true) : exchange;
+            return;
+        }
         super.process(exchange);
     }
 
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/file/FilePollEnrichDynamicFileNameTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/file/FilePollEnrichDynamicFileNameTest.java
new file mode 100644
index 000000000000..fd9c4317c06c
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/file/FilePollEnrichDynamicFileNameTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.file;
+
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Poll enrich with a dynamic fileName returns the content of the file named 
by each message.
+ */
+public class FilePollEnrichDynamicFileNameTest extends ContextTestSupport {
+
+    private static final int ROUNDS = 20;
+    private static final int FILES = 40;
+    private static final int THREADS = 20;
+
+    @Test
+    public void testPollEnrichDynamicFileName() throws Exception {
+        ExecutorService executor = Executors.newFixedThreadPool(THREADS);
+        List<String> mismatches = new ArrayList<>();
+        try {
+            for (int round = 0; round < ROUNDS; round++) {
+                List<String> names = new ArrayList<>();
+                for (int i = 0; i < FILES; i++) {
+                    String name = "r" + round + "-f" + i + ".txt";
+                    Files.writeString(testDirectory().resolve(name), "content 
of " + name);
+                    names.add(name);
+                }
+
+                List<Callable<String>> calls = new ArrayList<>();
+                for (String name : names) {
+                    calls.add(() -> {
+                        String body = 
template.requestBodyAndHeader("direct:start", null, "docName", name, 
String.class);
+                        return ("content of " + name).equals(body) ? null : 
name + " received: " + body;
+                    });
+                }
+                for (Future<String> future : executor.invokeAll(calls)) {
+                    String mismatch = future.get();
+                    if (mismatch != null) {
+                        mismatches.add(mismatch);
+                    }
+                }
+            }
+        } finally {
+            executor.shutdownNow();
+        }
+
+        assertEquals(List.of(), mismatches);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start")
+                        
.pollEnrich().simple(fileUri("?noop=true&fileName=${header.docName}")).timeout(5000)
+                        .convertBodyTo(String.class);
+            }
+        };
+    }
+}

Reply via email to