allthingssecurity commented on code in PR #28260:
URL: https://github.com/apache/camel/pull/28260#discussion_r4345005702


##########
components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java:
##########
@@ -484,6 +499,26 @@ protected void doInit() throws Exception {
         super.doInit();
     }
 
+    /**
+     * Stops and removes the route a Kamelet endpoint created from its 
template while the context runs, when that
+     * endpoint is shut down (it is not shut down while the context runs 
unless it was used once and discarded).
+     */
+    private void removeCreatedRoute(String routeId) {
+        CamelContext context = getCamelContext();
+        if (context == null || !context.isStarted() || context.isStopping() || 
context.getRoute(routeId) == null) {
+            // the context removes all the routes when it stops
+            return;
+        }
+        try {
+            LOG.debug("Removing route with id={} created by a Kamelet endpoint 
that is shut down", routeId);
+            context.getRouteController().stopRoute(routeId);
+            context.removeRoute(routeId);

Review Comment:
   Done in c2f8f4c057b9. The stop and remove now run on a single thread the 
component owns (`newSingleThreadExecutor` from the `ExecutorServiceManager`, 
created in `doStart`, shut down in `doShutdown`).
   
   Handing it off alone was not enough for `threads()`: the calling route goes 
on running on the pool thread after the toD callback, so a removal on another 
thread can still `shutdownNow` that pool while the caller uses it. So when the 
endpoint is shut down while a call is completing (toD does it inside the 
producer callback), the removal waits for that exchange to be done (an 
onCompletion on it) and is then submitted to the component thread. New tests: 
`templateWithThreadsDoesNotInterruptTheCallingRoute` fails with the inline 
removal (`Expecting value to be false but was true`), and 
`kameletRouteIsKeptWhileTheCallingRouteRunsOnItsThread` fails when the removal 
does not wait for the exchange.
   
   _Claude Code on behalf of allthingssecurity_
   



##########
components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java:
##########
@@ -484,6 +499,26 @@ protected void doInit() throws Exception {
         super.doInit();
     }
 
+    /**
+     * Stops and removes the route a Kamelet endpoint created from its 
template while the context runs, when that
+     * endpoint is shut down (it is not shut down while the context runs 
unless it was used once and discarded).
+     */
+    private void removeCreatedRoute(String routeId) {
+        CamelContext context = getCamelContext();
+        if (context == null || !context.isStarted() || context.isStopping() || 
context.getRoute(routeId) == null) {

Review Comment:
   Done in c2f8f4c057b9. The endpoint keeps the `Route` instance and removes it 
only while `context.getRoute(id)` is still that instance. On top of that it 
removes only a route whose id was generated for this endpoint. With an id given 
in the uri, every endpoint replaces the previous one's route (`DefaultModel`, 
same template), so there is never more than one such route. Leaving it alone 
also closes the gap between the identity check and `stopRoute(id)`. 
`routeIdGivenInTheUriIsLeftAlone` pins that only `myRoute` is kept after 50 
calls. `sendsFromSeveralThreadsAtOnce` sends 200 requests from 8 threads and 
checks every reply.
   
   _Claude Code on behalf of allthingssecurity_
   



##########
components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java:
##########
@@ -484,6 +499,26 @@ protected void doInit() throws Exception {
         super.doInit();
     }
 
+    /**
+     * Stops and removes the route a Kamelet endpoint created from its 
template while the context runs, when that
+     * endpoint is shut down (it is not shut down while the context runs 
unless it was used once and discarded).
+     */

Review Comment:
   Yes, it is intended. The comment in `doShutdown` and the javadoc of 
`removeCreatedRoute` now say so, and 
`routeOfKameletIsRemovedWithTheRouteAddedAtRuntimeThatConsumesFromIt` covers 
it: it adds `from("kamelet:source-of?name=a")` at runtime, routes a message, 
removes the route, and the Kamelet's route goes too. Since the removal now runs 
on the component thread, it is no longer nested inside `removeRoute`; it waits 
for the context lock until that call is done. Routes created when the context 
starts are not changed: their endpoint does not record the route (it is created 
later, in `onContextInitialized`). The PR description says this now as well.
   
   _Claude Code on behalf of allthingssecurity_
   



##########
components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletToDRoutesNotKeptTest.java:
##########
@@ -0,0 +1,110 @@
+/*
+ * 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.kamelet;
+
+import org.apache.camel.Route;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * A Kamelet called with toD and cacheSize=-1 (the documented setting for a 
dynamic endpoint that is different for every
+ * message) gets a new endpoint for every call, which creates a route from the 
template. The endpoint is discarded after
+ * the call, so its route must be removed as well, otherwise every call keeps 
one more route.
+ */
+class KameletToDRoutesNotKeptTest extends CamelTestSupport {
+
+    private static final int MESSAGES = 50;
+
+    @Test
+    void routesOfDiscardedKameletEndpointsAreRemoved() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(MESSAGES);
+
+        for (int i = 0; i < MESSAGES; i++) {
+            template.sendBodyAndHeader("direct:prototype", "body", "id", "id" 
+ i);
+        }
+        mock.assertIsSatisfied();
+        for (int i = 0; i < MESSAGES; i++) {
+            assertEquals("id" + i + "-body", 
mock.getReceivedExchanges().get(i).getMessage().getBody());
+        }
+
+        assertEquals(0, kameletRoutes(), "routes created by the Kamelet and 
still kept after " + MESSAGES + " calls");
+        assertEquals(3, context.getEndpointRegistry().size(),
+                "endpoints after " + MESSAGES + " calls: " + 
context.getEndpointRegistry().values());
+    }
+
+    @Test
+    void sameValueEveryTimeDoesNotKeepRoutes() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedMessageCount(MESSAGES);
+
+        for (int i = 0; i < MESSAGES; i++) {
+            template.sendBodyAndHeader("direct:prototype", "body" + i, "id", 
"same");
+        }
+        mock.assertIsSatisfied();
+        for (int i = 0; i < MESSAGES; i++) {
+            assertEquals("same-body" + i, 
mock.getReceivedExchanges().get(i).getMessage().getBody());
+        }
+
+        assertEquals(0, kameletRoutes(), "routes created by the Kamelet and 
still kept after " + MESSAGES + " calls");
+    }
+
+    @Test
+    void cachedKameletEndpointsKeepTheirRoutes() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedBodiesReceived("a-1", "b-2", "a-3", "b-4");
+
+        String[] ids = { "a", "b", "a", "b" };
+        for (int i = 0; i < ids.length; i++) {
+            template.sendBodyAndHeader("direct:cached", String.valueOf(i + 1), 
"id", ids[i]);
+        }
+        mock.assertIsSatisfied();
+
+        // the default toD keeps the endpoints and so their routes for the 
next messages
+        assertEquals(2, kameletRoutes());
+    }
+
+    private long kameletRoutes() {
+        return 
context.getRoutes().stream().filter(Route::isCreatedByKamelet).count();
+    }
+
+    @Override
+    protected RoutesBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                routeTemplate("prefix")
+                        .templateParameter("value")
+                        .from("kamelet:source")
+                        .setBody().simple("{{value}}-${body}");
+
+                from("direct:prototype").routeId("prototype")
+                        .toD("kamelet:prefix?value=${header.id}", -1)
+                        .to("mock:result");

Review Comment:
   Both added in c2f8f4c057b9: 
`templateWithThreadsDoesNotInterruptTheCallingRoute` (50 messages through a 
template with `threads(2)`; the processor after the toD records the thread name 
and `isInterrupted()`), 
`kameletRouteIsKeptWhileTheCallingRouteRunsOnItsThread`, and 
`sendsFromSeveralThreadsAtOnce` (8 threads, 200 requests). The test class now 
uses AssertJ.
   
   _Claude Code on behalf of allthingssecurity_
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to