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


##########
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:
   Not blocking (point 2): `getRoute(routeId) != null` does not prove this is 
the route this endpoint created. With a user-given route id 
(`kamelet:prefix/myRoute`), every endpoint shares the id, and a newer endpoint 
replaces the route. Remembering the `Route` instance returned after creation, 
and removing it only while `context.getRoute(id)` is still that instance, would 
keep one endpoint from removing a route that another in-flight exchange uses.



##########
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:
   Blocking (point 1): this runs on the thread that finishes the exchange, from 
inside the toD callback, and `stopRoute` holds the CamelContext lock while it 
runs. If the Kamelet's route finishes on its own thread, that thread shuts down 
its own pool. With `threads()`, `shutdownNow` interrupts the current thread. 
With an async kafka producer, `shutdownGraceful(workerPool, 30000)` runs from a 
worker of that same pool, so it waits 30s and then interrupts itself. Please 
run the stop and remove on a separate thread that the component owns.



##########
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:
   Optional: please add a variant whose template uses `threads()` (the calling 
route must not continue on an interrupted thread), and one that sends from 
several threads at once. These cover points 1 and 2 of the review.



##########
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:
   Not blocking (point 3): this is also reached when a route that consumes from 
a Kamelet (`from("kamelet:my-source")`) and was added after the context started 
is removed, because `RouteService.doShutdown` shuts down the route's input 
endpoint. That route is then removed as well, nested inside `removeRoute`. It 
is probably what we want, but please say so here and cover it with a test.



-- 
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