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 f3c12e7d79fb CAMEL-24090: camel-file - Drain eagerly-added idempotent
keys on poll exception
f3c12e7d79fb is described below
commit f3c12e7d79fba9e4eb9c9873478b7d7b7a68a9cc
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Jul 16 07:19:00 2026 +0200
CAMEL-24090: camel-file - Drain eagerly-added idempotent keys on poll
exception
With idempotentEager=true (the default), keys are added to the
idempotent repository during directory scanning. If pollDirectory()
throws mid-scan, the List overload of removeExcessiveInProgressFiles
only removes in-progress keys — eagerly-added idempotent keys leak,
causing files to be permanently skipped on subsequent polls.
CAMEL-21830 fixed the Deque overload used for batch draining but missed
the List overload on the poll-exception path. Mirror the same cleanup:
when idempotentEager is enabled, also call removeExcessiveIdempotentFile
for each file during the List drain.
Closes #24745
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../camel/component/file/GenericFileConsumer.java | 3 +
.../file/FileIdempotentEagerPollExceptionTest.java | 85 ++++++++++++++++++++++
2 files changed, 88 insertions(+)
diff --git
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileConsumer.java
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileConsumer.java
index 89b676b33a82..e02c4a5091d8 100644
---
a/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileConsumer.java
+++
b/components/camel-file/src/main/java/org/apache/camel/component/file/GenericFileConsumer.java
@@ -311,6 +311,9 @@ public abstract class GenericFileConsumer<T> extends
ScheduledBatchPollingConsum
for (GenericFile file : files) {
String key = file.getAbsoluteFilePath();
endpoint.getInProgressRepository().remove(key);
+ if (endpoint.isIdempotentEager() &&
endpoint.getIdempotentRepository() != null) {
+ removeExcessiveIdempotentFile(file, null);
+ }
}
}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/component/file/FileIdempotentEagerPollExceptionTest.java
b/core/camel-core/src/test/java/org/apache/camel/component/file/FileIdempotentEagerPollExceptionTest.java
new file mode 100644
index 000000000000..b0bddb2a99ee
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/component/file/FileIdempotentEagerPollExceptionTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.Registry;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests that eagerly-added idempotent keys are drained when pollDirectory
throws mid-scan (CAMEL-24090).
+ *
+ * Scenario: two files exist (a.txt, b.txt); preSort=name guarantees a.txt is
scanned first. A filter throws on b.txt
+ * during the first poll only. Without the fix, a.txt's eagerly-added
idempotent key leaks and the file is never
+ * consumed on subsequent polls.
+ */
+public class FileIdempotentEagerPollExceptionTest extends ContextTestSupport {
+
+ private final AtomicBoolean shouldThrow = new AtomicBoolean(true);
+
+ @Override
+ protected Registry createCamelRegistry() throws Exception {
+ Registry jndi = super.createCamelRegistry();
+ jndi.bind("throwOnceFilter", new ThrowOnceFilter());
+ return jndi;
+ }
+
+ @Test
+ public void testIdempotentKeysDrainedOnPollException() throws Exception {
+ template.sendBodyAndHeader(fileUri(), "aaa", Exchange.FILE_NAME,
"a.txt");
+ template.sendBodyAndHeader(fileUri(), "bbb", Exchange.FILE_NAME,
"b.txt");
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMinimumMessageCount(2);
+
+ context.getRouteController().startRoute("testRoute");
+
+ MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS);
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
from(fileUri("?noop=true&initialDelay=0&delay=100&preSort=name&filter=#throwOnceFilter"))
+ .routeId("testRoute").noAutoStartup()
+ .convertBodyTo(String.class)
+ .to("mock:result");
+ }
+ };
+ }
+
+ private class ThrowOnceFilter implements GenericFileFilter<Object> {
+ @Override
+ public boolean accept(GenericFile<Object> file) {
+ if (file.isDirectory()) {
+ return true;
+ }
+ if ("b.txt".equals(file.getFileName()) &&
shouldThrow.compareAndSet(true, false)) {
+ throw new RuntimeException("Simulated mid-scan failure");
+ }
+ return true;
+ }
+ }
+}