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 d0381bafb261 CAMEL-26881: camel-core - Aggregate EIP: an Error of the 
aggregation strategy or repository no longer stops the completion timeout, 
interval or recover task (#29785)
d0381bafb261 is described below

commit d0381bafb2615b1e25ebb98d6bbfa16a3432fff7
Author: allthingssecurity <[email protected]>
AuthorDate: Fri Nov 13 20:23:02 2026 +0530

    CAMEL-26881: camel-core - Aggregate EIP: an Error of the aggregation 
strategy or repository no longer stops the completion timeout, interval or 
recover task (#29785)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../processor/aggregate/AggregateProcessor.java    | 42 +++++++++---
 .../AggregateIntervalTaskExceptionTest.java        | 28 +++++++-
 .../AggregateOnCompletionThrowsTest.java           | 40 +++++++++--
 .../AggregateTimeoutRepositoryExceptionTest.java   | 20 ++++++
 .../AggregateTimeoutStrategyThrowsErrorTest.java   | 78 ++++++++++++++++++++++
 .../DefaultTimeoutMapListenerThrowsErrorTest.java  | 73 ++++++++++++++++++++
 .../apache/camel/support/DefaultTimeoutMap.java    | 17 ++++-
 7 files changed, 276 insertions(+), 22 deletions(-)

diff --git 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/AggregateProcessor.java
 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/AggregateProcessor.java
index f257f354427b..11b89f86f5a5 100644
--- 
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/AggregateProcessor.java
+++ 
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/aggregate/AggregateProcessor.java
@@ -1105,22 +1105,28 @@ public class AggregateProcessor extends 
BaseProcessorSupport
     /**
      * Sends the aggregated exchanges (more than one when the batch consumer 
completes all its groups). None of them is
      * in the repository any more, so when one of them fails to be submitted 
(the thread pool rejects it, or the
-     * aggregation strategy throws an exception on completion), the others are 
still sent (or failed too), and then the
-     * first failure is thrown.
+     * aggregation strategy throws an exception or an Error on completion), 
the others are still sent (or failed too),
+     * and then the first failure is thrown.
      */
     private void submitCompletions(String key, List<Exchange> aggregated) {
-        RuntimeException failure = null;
+        Throwable failure = null;
         for (Exchange agg : aggregated) {
             try {
                 onSubmitCompletion(key, agg);
-            } catch (RuntimeException e) {
+            } catch (VirtualMachineError e) {
+                // left as before: a JVM-level failure is not handled here
+                throw e;
+            } catch (RuntimeException | Error e) {
+                // also an Error of the aggregation strategy, as for an 
exception
                 if (failure == null) {
                     failure = e;
                 }
             }
         }
-        if (failure != null) {
-            throw failure;
+        if (failure instanceof Error err) {
+            throw err;
+        } else if (failure != null) {
+            throw (RuntimeException) failure;
         }
     }
 
@@ -1669,7 +1675,11 @@ public class AggregateProcessor extends 
BaseProcessorSupport
             try {
                 doOnEviction(key, exchangeId);
                 forgetRetry(key);
-            } catch (Exception e) {
+            } catch (VirtualMachineError e) {
+                // left as before: a JVM-level failure is not handled here
+                throw e;
+            } catch (Throwable e) {
+                // also an Error of the aggregation strategy (its timeout or 
onCompletion), as for an exception
                 // the entry is no longer in the timeout map, and the timeout 
map ignores errors of its listeners, so
                 // without a new entry a group the repository could not read 
or remove would never time out (an entry
                 // that a new message for the key has put in the meantime is 
kept)
@@ -1788,7 +1798,11 @@ public class AggregateProcessor extends 
BaseProcessorSupport
                         lock.unlock();
                     }
                 }
-            } catch (Exception e) {
+            } catch (VirtualMachineError e) {
+                // left as before: a JVM-level failure is not handled, and 
ends the interval task
+                throw e;
+            } catch (Throwable e) {
+                // also an Error (such as of the aggregation strategy), which 
would otherwise end the interval task
                 LOG.warn("Error during completion interval task. This 
exception is ignored.", e);
             }
 
@@ -1912,7 +1926,10 @@ public class AggregateProcessor extends 
BaseProcessorSupport
                                     // not exhaust so resubmit the recovered 
exchange
                                     try {
                                         onSubmitCompletion(key, exchange);
-                                    } catch (RuntimeException e) {
+                                    } catch (VirtualMachineError e) {
+                                        // left as before: a JVM-level failure 
is not handled, and ends the recover task
+                                        throw e;
+                                    } catch (RuntimeException | Error e) {
                                         // it has been failed and passed to 
the exception handler, and is recovered
                                         // again on a later run, so go on with 
the other exchanges
                                         LOG.debug("Error resubmitting 
recovered exchange with id: {}", exchangeId, e);
@@ -1924,8 +1941,11 @@ public class AggregateProcessor extends 
BaseProcessorSupport
                         recoverLock.unlock();
                     }
                 }
-            } catch (Exception e) {
-                // must catch and log exception otherwise the executor will 
not schedule next recover task
+            } catch (VirtualMachineError e) {
+                // left as before: a JVM-level failure is not handled, and 
ends the recover task
+                throw e;
+            } catch (Throwable e) {
+                // must catch and log exception (also an Error) otherwise the 
executor will not schedule next recover task
                 LOG.warn("Error during recover task. This exception is 
ignored.", e);
             } finally {
                 recoveryInProgress.set(false);
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateIntervalTaskExceptionTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateIntervalTaskExceptionTest.java
index 67d3abac8b43..fdf309b8a90d 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateIntervalTaskExceptionTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateIntervalTaskExceptionTest.java
@@ -51,6 +51,18 @@ public class AggregateIntervalTaskExceptionTest extends 
ContextTestSupport {
         MockEndpoint.assertIsSatisfied(context, 15, TimeUnit.SECONDS);
     }
 
+    @Test
+    public void testIntervalTaskSurvivesError() throws Exception {
+        getMockEndpoint("mock:error").expectedMinimumMessageCount(1);
+
+        template.sendBodyAndHeader("direct:error", "A", "id", "1");
+        template.sendBodyAndHeader("direct:error", "B", "id", "1");
+
+        // the first interval tick with data gets an Error (not a 
VirtualMachineError) from getKeys(), and the
+        // scheduler survives it too
+        MockEndpoint.assertIsSatisfied(context, 15, TimeUnit.SECONDS);
+    }
+
     @Override
     protected RouteBuilder createRouteBuilder() {
         return new RouteBuilder() {
@@ -58,9 +70,15 @@ public class AggregateIntervalTaskExceptionTest extends 
ContextTestSupport {
             public void configure() {
                 from("direct:start")
                         .aggregate(header("id"), new 
UseLatestAggregationStrategy())
-                        .aggregationRepository(new FailOnceRepository())
+                        .aggregationRepository(new FailOnceRepository(false))
                         .completionInterval(500)
                         .to("mock:result");
+
+                from("direct:error")
+                        .aggregate(header("id"), new 
UseLatestAggregationStrategy())
+                        .aggregationRepository(new FailOnceRepository(true))
+                        .completionInterval(500)
+                        .to("mock:error");
             }
         };
     }
@@ -69,6 +87,11 @@ public class AggregateIntervalTaskExceptionTest extends 
ContextTestSupport {
 
         private final MemoryAggregationRepository delegate = new 
MemoryAggregationRepository();
         private final AtomicInteger getKeysCount = new AtomicInteger();
+        private final boolean error;
+
+        FailOnceRepository(boolean error) {
+            this.error = error;
+        }
 
         @Override
         public Exchange add(CamelContext camelContext, String key, Exchange 
exchange) {
@@ -94,6 +117,9 @@ public class AggregateIntervalTaskExceptionTest extends 
ContextTestSupport {
         public Set<String> getKeys() {
             Set<String> keys = delegate.getKeys();
             if (keys != null && !keys.isEmpty() && 
getKeysCount.incrementAndGet() == 1) {
+                if (error) {
+                    throw new NoClassDefFoundError("Simulated missing class");
+                }
                 throw new RuntimeException("Simulated repository failure");
             }
             return keys;
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateOnCompletionThrowsTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateOnCompletionThrowsTest.java
index 9b5943d132f0..df94ff2665d7 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateOnCompletionThrowsTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateOnCompletionThrowsTest.java
@@ -38,24 +38,34 @@ import 
org.apache.camel.support.KeyValueAggregationRepository;
 import org.apache.camel.support.MemoryKeyValueRepository;
 import org.apache.camel.support.SynchronizationAdapter;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
 
 import static org.awaitility.Awaitility.await;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * An aggregation strategy whose onCompletion throws (its javadoc says it must 
not): the aggregated exchange is booked
  * as in progress already, and its group has been removed from the repository. 
The aggregated exchange must then be
  * failed, as when the thread pool rejects it, so that the aggregator no 
longer counts it as in progress and a
- * recoverable repository recovers it, instead of skipping it forever as being 
in progress.
+ * recoverable repository recovers it, instead of skipping it forever as being 
in progress. An Error that is not a
+ * VirtualMachineError (such as a NoClassDefFoundError of a class used by the 
strategy) is handled as an exception.
  */
 class AggregateOnCompletionThrowsTest extends ContextTestSupport {
 
+    enum Failure {
+        EXCEPTION,
+        ERROR
+    }
+
     private final AtomicBoolean throwOnce = new AtomicBoolean(true);
     private final Set<String> alwaysThrow = ConcurrentHashMap.newKeySet();
     private final List<String> failures = new CopyOnWriteArrayList<>();
     private final List<Exchange> handled = new CopyOnWriteArrayList<>();
+    private Failure failure = Failure.EXCEPTION;
 
     @Override
     public boolean isUseRouteBuilder() {
@@ -105,8 +115,10 @@ class AggregateOnCompletionThrowsTest extends 
ContextTestSupport {
         assertMockEndpointsSatisfied();
     }
 
-    @Test
-    void testFailedBatchCompletionSendsOtherGroups() throws Exception {
+    @ParameterizedTest
+    @EnumSource(Failure.class)
+    void testFailedBatchCompletionSendsOtherGroups(Failure failure) throws 
Exception {
+        this.failure = failure;
         AggregateProcessor aggregator = addRoute(recoverableRepository(), 
true);
 
         // the batch consumer completes the groups A and B at once: whichever 
group's onCompletion throws is
@@ -118,15 +130,17 @@ class AggregateOnCompletionThrowsTest extends 
ContextTestSupport {
         sendBatch("A1", "A");
         sendBatch("B1", "B");
         Exchange last = sendBatch("A2", "A");
-        assertInstanceOf(IllegalStateException.class, last.getException());
+        assertFailedWith(failure, last);
 
         assertMockEndpointsSatisfied();
         await().atMost(5, TimeUnit.SECONDS)
                 .untilAsserted(() -> assertEquals(0, 
aggregator.getInProgressCompleteExchanges()));
     }
 
-    @Test
-    void testRecoverGoesOnAfterAnExchangeThatFailsAgain() throws Exception {
+    @ParameterizedTest
+    @EnumSource(Failure.class)
+    void testRecoverGoesOnAfterAnExchangeThatFailsAgain(Failure failure) 
throws Exception {
+        this.failure = failure;
         addRoute(new OrderedRecoverableRepository(), false);
         // onCompletion of A1+A2 throws each time, so it fails again each time 
the recover task resubmits it, and it is
         // the first one the recover task finds: the recover task must still 
go on and recover B1+B2
@@ -141,13 +155,22 @@ class AggregateOnCompletionThrowsTest extends 
ContextTestSupport {
         // onCompletion of B1+B2 throws once (throwOnce), so it is in the 
completed store too
         send("B1", "B");
         Exchange last = send("B2", "B");
-        assertInstanceOf(IllegalStateException.class, last.getException());
+        assertFailedWith(failure, last);
 
         assertMockEndpointsSatisfied();
         // and A1+A2 was recovered and failed again
         assertTrue(failures.stream().filter("A1+A2"::equals).count() >= 2);
     }
 
+    private static void assertFailedWith(Failure failure, Exchange exchange) {
+        if (failure == Failure.ERROR) {
+            // the message that completed the group fails with the Error, as 
before
+            assertNotNull(exchange.getException(NoClassDefFoundError.class));
+        } else {
+            assertInstanceOf(IllegalStateException.class, 
exchange.getException());
+        }
+    }
+
     private static KeyValueAggregationRepository recoverableRepository() {
         KeyValueAggregationRepository repository = new 
KeyValueAggregationRepository(new MemoryKeyValueRepository());
         repository.setUseRecovery(true);
@@ -184,6 +207,9 @@ class AggregateOnCompletionThrowsTest extends 
ContextTestSupport {
                     }
                 });
                 if (alwaysThrow.contains(body) || 
throwOnce.compareAndSet(true, false)) {
+                    if (failure == Failure.ERROR) {
+                        throw new NoClassDefFoundError("onCompletion failed 
for " + body);
+                    }
                     throw new IllegalStateException("onCompletion failed for " 
+ body);
                 }
             }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateTimeoutRepositoryExceptionTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateTimeoutRepositoryExceptionTest.java
index 7bdc3ca2dfaf..90a61be096ed 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateTimeoutRepositoryExceptionTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateTimeoutRepositoryExceptionTest.java
@@ -104,6 +104,21 @@ public class AggregateTimeoutRepositoryExceptionTest 
extends ContextTestSupport
         assertEquals(1, repository.failures.get(), "the repository should have 
failed once");
     }
 
+    @Test
+    public void testTimeoutCompletesGroupAfterFailedGetWithError() throws 
Exception {
+        MockEndpoint result = getMockEndpoint("mock:result");
+        result.expectedBodiesReceived("B");
+
+        template.sendBodyAndHeader("direct:start", "A", "id", "1");
+        template.sendBodyAndHeader("direct:start", "B", "id", "1");
+        // the next read of the group is the one of the timeout checker, and 
it throws an Error (such as a missing class
+        // of a stored body): it is tried again, as for an exception
+        repository.failNextGetWithError.set(true);
+
+        MockEndpoint.assertIsSatisfied(context, 10, TimeUnit.SECONDS);
+        assertEquals(1, repository.failures.get(), "the repository should have 
failed once");
+    }
+
     @Test
     public void testTimeoutCompletesGroupAfterFailedRemove() throws Exception {
         MockEndpoint result = getMockEndpoint("mock:result");
@@ -136,6 +151,7 @@ public class AggregateTimeoutRepositoryExceptionTest 
extends ContextTestSupport
 
         private final MemoryAggregationRepository delegate = new 
MemoryAggregationRepository();
         private final AtomicBoolean failNextGet = new AtomicBoolean();
+        private final AtomicBoolean failNextGetWithError = new AtomicBoolean();
         private final AtomicBoolean failNextRemove = new AtomicBoolean();
         private final AtomicInteger failingGets = new AtomicInteger();
         private final AtomicInteger failures = new AtomicInteger();
@@ -147,6 +163,10 @@ public class AggregateTimeoutRepositoryExceptionTest 
extends ContextTestSupport
 
         @Override
         public Exchange get(CamelContext camelContext, String key) {
+            if (failNextGetWithError.compareAndSet(true, false)) {
+                failures.incrementAndGet();
+                throw new NoClassDefFoundError("Simulated missing class");
+            }
             if (failNextGet.compareAndSet(true, false) || 
failingGets.getAndUpdate(n -> Math.max(0, n - 1)) > 0) {
                 failures.incrementAndGet();
                 throw new IllegalStateException("Simulated repository failure 
in get");
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateTimeoutStrategyThrowsErrorTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateTimeoutStrategyThrowsErrorTest.java
new file mode 100644
index 000000000000..97c4a6b71b30
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/aggregator/AggregateTimeoutStrategyThrowsErrorTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.processor.aggregator;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.AggregationStrategy;
+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.junit.jupiter.api.Test;
+
+import static org.awaitility.Awaitility.await;
+
+/**
+ * The timeout of the aggregation strategy throws an Error (not a 
VirtualMachineError) when a group completes by its
+ * completion timeout: the other groups still complete by their timeout.
+ */
+class AggregateTimeoutStrategyThrowsErrorTest extends ContextTestSupport {
+
+    private final AtomicInteger timeouts = new AtomicInteger();
+
+    @Test
+    void testOtherGroupsStillCompleteByTimeout() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        // A is not delivered, as for an exception of timeout (the group was 
removed from the repository before)
+        mock.expectedBodiesReceived("B");
+
+        template.sendBodyAndHeader("direct:start", "A", "id", "A");
+        // the timeout of A has thrown the Error
+        await().atMost(5, TimeUnit.SECONDS).until(() -> timeouts.get() >= 1);
+        template.sendBodyAndHeader("direct:start", "B", "id", "B");
+
+        // the timeout checker still runs: B completes by its timeout
+        MockEndpoint.assertIsSatisfied(10, TimeUnit.SECONDS, mock);
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start")
+                        .aggregate(header("id"), new AggregationStrategy() {
+                            @Override
+                            public Exchange aggregate(Exchange oldExchange, 
Exchange newExchange) {
+                                return oldExchange == null ? newExchange : 
oldExchange;
+                            }
+
+                            @Override
+                            public void timeout(Exchange exchange, int index, 
int total, long timeout) {
+                                if (timeouts.incrementAndGet() == 1) {
+                                    throw new NoClassDefFoundError("Simulated 
missing class");
+                                }
+                            }
+                        })
+                        
.completionTimeout(50).completionTimeoutCheckerInterval(10)
+                        .to("mock:result");
+            }
+        };
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/support/DefaultTimeoutMapListenerThrowsErrorTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/support/DefaultTimeoutMapListenerThrowsErrorTest.java
new file mode 100644
index 000000000000..eb76f4776eb0
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/support/DefaultTimeoutMapListenerThrowsErrorTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.support;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+import org.apache.camel.TimeoutMap;
+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.assertTrue;
+
+/**
+ * A listener of a DefaultTimeoutMap throws an Error (not a 
VirtualMachineError) for an evicted entry: the other entries
+ * are still evicted, and the map keeps purging.
+ */
+class DefaultTimeoutMapListenerThrowsErrorTest {
+
+    private final ScheduledExecutorService executor = new 
ScheduledThreadPoolExecutor(1);
+
+    @AfterEach
+    void shutdown() {
+        executor.shutdownNow();
+    }
+
+    @Test
+    void testPurgeGoesOnAfterAListenerThrowsAnError() {
+        DefaultTimeoutMap<String, Integer> map = new 
DefaultTimeoutMap<>(executor, 10);
+        List<String> evicted = new CopyOnWriteArrayList<>();
+        map.addListener((type, key, value) -> {
+            if (type == TimeoutMap.Listener.Type.Evict) {
+                evicted.add(key);
+                if ("A".equals(key)) {
+                    throw new NoClassDefFoundError("Simulated missing class");
+                }
+            }
+        });
+        map.start();
+        try {
+            // A and B expire in the same purge: B is still evicted after the 
Error of A
+            map.put("A", 1, 20);
+            map.put("B", 2, 20);
+            await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> 
assertTrue(evicted.containsAll(List.of("A", "B"))));
+
+            // and the map keeps purging: an entry added later is evicted too
+            map.put("C", 3, 20);
+            await().atMost(Duration.ofSeconds(5)).untilAsserted(() -> 
assertTrue(evicted.contains("C")));
+            assertEquals(0, map.size());
+        } finally {
+            map.stop();
+        }
+    }
+}
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/DefaultTimeoutMap.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/DefaultTimeoutMap.java
index 3e3ac2fe785b..23310c9bbb6a 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/DefaultTimeoutMap.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/DefaultTimeoutMap.java
@@ -171,9 +171,13 @@ public class DefaultTimeoutMap<K, V> extends 
ServiceSupport implements TimeoutMa
         log.trace("Running purge task to see if any entries have been timed 
out");
         try {
             purge();
-        } catch (Exception t) {
-            // must catch and log exception otherwise the executor will now 
schedule next purgeTask
-            log.warn("Exception occurred during purge task. This exception 
will be ignored.", t);
+        } catch (VirtualMachineError t) {
+            // left as before: a JVM-level failure still ends the purge task
+            throw t;
+        } catch (Throwable t) {
+            // must catch and log exception (also an Error) otherwise the 
executor will not schedule the next purgeTask,
+            // and no entry would ever time out again
+            log.warn("Exception or error occurred during purge task. This 
exception or error will be ignored.", t);
         }
     }
 
@@ -243,6 +247,13 @@ public class DefaultTimeoutMap<K, V> extends 
ServiceSupport implements TimeoutMa
         for (Listener<K, V> listener : listeners) {
             try {
                 listener.timeoutMapEvent(type, key, value);
+            } catch (VirtualMachineError t) {
+                // left as before: a JVM-level failure is thrown on (and ends 
the purge task when it purges)
+                throw t;
+            } catch (Error t) {
+                // the other entries that expired with this one are still 
evicted
+                log.warn("Error in a listener of the timeout map for key: {} 
({} event). This error is ignored.", key, type,
+                        t);
             } catch (Exception t) {
                 // Ignore
             }

Reply via email to