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

SvenO3 pushed a commit to branch fix-adapter-producer-lifecycle
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/fix-adapter-producer-lifecycle 
by this push:
     new a12a7555f3 Clean up pipeline elements on failed start/stop
a12a7555f3 is described below

commit a12a7555f313e656d6df8e70d54ada8c1d39c1ca
Author: Sven Oehler <[email protected]>
AuthorDate: Tue Jul 14 14:54:04 2026 +0200

    Clean up pipeline elements on failed start/stop
---
 .../pe/DataProcessorPipelineElementManagement.java |   4 +-
 .../pe/DataSinkPipelineElementManagement.java      |   4 +-
 .../pe/InvocablePipelineElementManagement.java     |   4 +-
 .../http/InvokePipelineElementSubmitter.java       |   2 +-
 .../http/PipelineElementExtensionRequest.java      |   5 +-
 .../http/PipelineElementRollbackTest.java          | 136 +++++++++++++++++++++
 .../runtime/StandaloneEventProcessorRuntime.java   |  57 ++++++++-
 .../runtime/StandaloneEventSinkRuntime.java        |  45 ++++++-
 .../runtime/StandalonePipelineElementRuntime.java  |  77 +++++++++++-
 9 files changed, 313 insertions(+), 21 deletions(-)

diff --git 
a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/DataProcessorPipelineElementManagement.java
 
b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/DataProcessorPipelineElementManagement.java
index 969753f753..7e0af38a5a 100644
--- 
a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/DataProcessorPipelineElementManagement.java
+++ 
b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/DataProcessorPipelineElementManagement.java
@@ -73,7 +73,9 @@ public class DataProcessorPipelineElementManagement extends 
InvocablePipelineEle
                                    DataProcessorInvocation graph) {
     var runtime = getRuntime();
     var response = runtime.onRuntimeInvoked(runningInstanceId, 
pipelineElement, graph);
-    getRunningInstances().add(runningInstanceId, graph, runtime);
+    if (response.isSuccess()) {
+      getRunningInstances().add(runningInstanceId, graph, runtime);
+    }
     return response;
   }
 }
diff --git 
a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/DataSinkPipelineElementManagement.java
 
b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/DataSinkPipelineElementManagement.java
index 6e61831878..b7e59e2956 100644
--- 
a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/DataSinkPipelineElementManagement.java
+++ 
b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/DataSinkPipelineElementManagement.java
@@ -72,7 +72,9 @@ public class DataSinkPipelineElementManagement extends 
InvocablePipelineElementM
                                    DataSinkInvocation graph) {
     var runtime = getRuntime();
     var response = runtime.onRuntimeInvoked(runningInstanceId, 
pipelineElement, graph);
-    getRunningInstances().add(runningInstanceId, graph, runtime);
+    if (response.isSuccess()) {
+      getRunningInstances().add(runningInstanceId, graph, runtime);
+    }
     return response;
   }
 }
diff --git 
a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/InvocablePipelineElementManagement.java
 
b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/InvocablePipelineElementManagement.java
index d3125dac3d..4e67c458e9 100644
--- 
a/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/InvocablePipelineElementManagement.java
+++ 
b/streampipes-extensions-management/src/main/java/org/apache/streampipes/extensions/management/pe/InvocablePipelineElementManagement.java
@@ -122,9 +122,7 @@ public abstract class InvocablePipelineElementManagement<
     if (runningInstance != null) {
       Response response = runningInstance.onRuntimeDetached(runningInstanceId);
 
-      if (response.isSuccess()) {
-        runningInstances.remove(runningInstanceId);
-      }
+      runningInstances.remove(runningInstanceId);
 
       return response;
     }
diff --git 
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/http/InvokePipelineElementSubmitter.java
 
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/http/InvokePipelineElementSubmitter.java
index f8e57cc83f..1ea74c7d56 100644
--- 
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/http/InvokePipelineElementSubmitter.java
+++ 
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/http/InvokePipelineElementSubmitter.java
@@ -72,7 +72,7 @@ public class InvokePipelineElementSubmitter extends 
BasePipelineElementSubmitter
                                                                     
List<InvocableStreamPipesEntity> pe) {
     return pe
         .stream()
-        .filter(g -> g.getBelongsTo().equals(elementId))
+        .filter(g -> g.getElementId().equals(elementId))
         .findFirst();
   }
 }
diff --git 
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/http/PipelineElementExtensionRequest.java
 
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/http/PipelineElementExtensionRequest.java
index 081a07bb7e..1f108cb4be 100644
--- 
a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/http/PipelineElementExtensionRequest.java
+++ 
b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/execution/http/PipelineElementExtensionRequest.java
@@ -71,13 +71,12 @@ public abstract class PipelineElementExtensionRequest {
     org.apache.streampipes.model.Response streamPipesResp = JacksonSerializer
         .getObjectMapper()
         .readValue(resp, org.apache.streampipes.model.Response.class);
-    return convert(streamPipesResp, endpointUrl, pipelineElement.getName());
+    return convert(streamPipesResp, pipelineElement.getName());
   }
 
   private PipelineElementStatus convert(org.apache.streampipes.model.Response 
response,
-                                        String endpointUrl,
                                         String pipelineElementName) {
-    return new PipelineElementStatus(endpointUrl, pipelineElementName, 
response.isSuccess(),
+    return new PipelineElementStatus(response.getElementId(), 
pipelineElementName, response.isSuccess(),
         response.getOptionalMessage());
   }
 
diff --git 
a/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/execution/http/PipelineElementRollbackTest.java
 
b/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/execution/http/PipelineElementRollbackTest.java
new file mode 100644
index 0000000000..39ded2b08d
--- /dev/null
+++ 
b/streampipes-pipeline-management/src/test/java/org/apache/streampipes/manager/execution/http/PipelineElementRollbackTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.streampipes.manager.execution.http;
+
+import 
org.apache.streampipes.manager.api.extensions.ExtensionServiceOperationResult;
+import 
org.apache.streampipes.manager.api.extensions.ExtensionServiceRequestManager;
+import org.apache.streampipes.model.base.InvocableStreamPipesEntity;
+import org.apache.streampipes.model.graph.DataSinkInvocation;
+import org.apache.streampipes.model.pipeline.Pipeline;
+import org.apache.streampipes.model.pipeline.PipelineElementStatus;
+import org.apache.streampipes.resource.management.SpResourceManager;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.mockito.Mockito.mock;
+
+class PipelineElementRollbackTest {
+
+  @Test
+  void extensionResponseUsesRuntimeElementIdInStatus() {
+    var request = new TestPipelineElementExtensionRequest();
+    var element = makeElement("urn:streampipes:runtime-element", "app-id", 
"service-id", "Element");
+
+    var status = request.execute(element, "pipeline-id");
+
+    assertEquals("urn:streampipes:runtime-element", status.getElementId());
+  }
+
+  @Test
+  void rollbackDetachesSuccessfullyStartedElementByRuntimeElementId() {
+    var startedElement = makeElement("urn:streampipes:started", "app-started", 
"service-id", "Started");
+    var failedElement = makeElement("urn:streampipes:failed", "app-failed", 
"service-id", "Failed");
+    var submitter = new TestInvokePipelineElementSubmitter(
+        Map.of(
+            startedElement.getElementId(), new 
PipelineElementStatus(startedElement.getElementId(),
+                startedElement.getName(), true, ""),
+            failedElement.getElementId(), new 
PipelineElementStatus(failedElement.getElementId(),
+                failedElement.getName(), false, "startup failed")
+        )
+    );
+
+    var status = submitter.submit(List.of(startedElement, failedElement));
+
+    assertFalse(status.isSuccess());
+    assertEquals(List.of(startedElement), submitter.detachedElements);
+  }
+
+  private static DataSinkInvocation makeElement(String elementId,
+                                                String belongsTo,
+                                                String selectedServiceId,
+                                                String name) {
+    var element = new DataSinkInvocation();
+    element.setElementId(elementId);
+    element.setBelongsTo(belongsTo);
+    element.setSelectedServiceId(selectedServiceId);
+    element.setName(name);
+    return element;
+  }
+
+  private static class TestPipelineElementExtensionRequest extends 
PipelineElementExtensionRequest {
+
+    TestPipelineElementExtensionRequest() {
+      super(mock(ExtensionServiceRequestManager.class), 
mock(SpResourceManager.class));
+    }
+
+    @Override
+    protected ExtensionServiceOperationResult 
performRequest(InvocableStreamPipesEntity pipelineElement,
+                                                             String 
pipelineId) throws IOException {
+      var body = """
+          
{"elementId":"urn:streampipes:runtime-element","success":true,"optionalMessage":""}
+          """.getBytes(StandardCharsets.UTF_8);
+      return new ExtensionServiceOperationResult(200, body);
+    }
+
+    @Override
+    protected void logError(String endpointUrl,
+                            String pipelineElementName,
+                            String exceptionMessage) {
+
+    }
+  }
+
+  private static class TestInvokePipelineElementSubmitter extends 
InvokePipelineElementSubmitter {
+
+    private final Map<String, PipelineElementStatus> responses;
+    private final List<InvocableStreamPipesEntity> detachedElements;
+
+    TestInvokePipelineElementSubmitter(Map<String, PipelineElementStatus> 
responses) {
+      super(makePipeline(), mock(ExtensionServiceRequestManager.class), 
mock(SpResourceManager.class));
+      this.responses = responses;
+      this.detachedElements = new ArrayList<>();
+    }
+
+    @Override
+    protected PipelineElementStatus submitElement(InvocableStreamPipesEntity 
pipelineElement) {
+      return responses.get(pipelineElement.getElementId());
+    }
+
+    @Override
+    protected PipelineElementStatus performDetach(InvocableStreamPipesEntity 
pipelineElement) {
+      detachedElements.add(pipelineElement);
+      return new PipelineElementStatus(pipelineElement.getElementId(), 
pipelineElement.getName(), true, "");
+    }
+
+    private static Pipeline makePipeline() {
+      var pipeline = new Pipeline();
+      pipeline.setPipelineId("pipeline-id");
+      pipeline.setName("Pipeline");
+      return pipeline;
+    }
+  }
+}
diff --git 
a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventProcessorRuntime.java
 
b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventProcessorRuntime.java
index aebf71046a..f81463fb06 100644
--- 
a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventProcessorRuntime.java
+++ 
b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventProcessorRuntime.java
@@ -48,6 +48,7 @@ public class StandaloneEventProcessorRuntime extends 
StandalonePipelineElementRu
   private static final Logger LOG = 
LoggerFactory.getLogger(StandaloneEventProcessorRuntime.class);
 
   protected SpOutputCollector outputCollector;
+  private boolean pipelineStarted;
 
   public StandaloneEventProcessorRuntime() {
     super(new DataProcessorContextGenerator(), new 
DataProcessorParameterGenerator());
@@ -96,14 +97,64 @@ public class StandaloneEventProcessorRuntime extends 
StandalonePipelineElementRu
   protected void beforeStart() {
     this.outputCollector = getOutputCollector();
     pipelineElement.onPipelineStarted(runtimeParameters, outputCollector, 
runtimeContext);
+    pipelineStarted = true;
     prepareRuntime();
   }
 
   @Override
   protected void afterStop() {
-    disconnectInputCollectors();
-    pipelineElement.onPipelineStopped();
-    outputCollector.disconnect();
+    RuntimeException stopException = null;
+    try {
+      disconnectInputCollectors();
+    } catch (RuntimeException e) {
+      stopException = collectCleanupException(stopException, e);
+    }
+    try {
+      pipelineElement.onPipelineStopped();
+    } catch (RuntimeException e) {
+      stopException = collectCleanupException(stopException, e);
+    } finally {
+      pipelineStarted = false;
+    }
+    try {
+      outputCollector.disconnect();
+    } catch (RuntimeException e) {
+      stopException = collectCleanupException(stopException, e);
+    }
+
+    if (stopException != null) {
+      throw stopException;
+    }
+  }
+
+  @Override
+  protected void afterStartFailed() {
+    RuntimeException cleanupException = null;
+    try {
+      super.afterStartFailed();
+    } catch (RuntimeException e) {
+      cleanupException = collectCleanupException(cleanupException, e);
+    }
+    try {
+      if (pipelineStarted) {
+        pipelineElement.onPipelineStopped();
+      }
+    } catch (RuntimeException e) {
+      cleanupException = collectCleanupException(cleanupException, e);
+    } finally {
+      pipelineStarted = false;
+    }
+    try {
+      if (outputCollector != null) {
+        outputCollector.disconnect();
+      }
+    } catch (RuntimeException e) {
+      cleanupException = collectCleanupException(cleanupException, e);
+    }
+
+    if (cleanupException != null) {
+      throw cleanupException;
+    }
   }
 
 }
diff --git 
a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventSinkRuntime.java
 
b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventSinkRuntime.java
index 859468a761..c8046673a5 100644
--- 
a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventSinkRuntime.java
+++ 
b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandaloneEventSinkRuntime.java
@@ -45,6 +45,7 @@ public class StandaloneEventSinkRuntime extends 
StandalonePipelineElementRuntime
     IDataSinkParameters> implements IDataSinkRuntime, RawDataProcessor {
 
   private static final Logger LOG = 
LoggerFactory.getLogger(StandaloneEventSinkRuntime.class);
+  private boolean pipelineStarted;
 
   public StandaloneEventSinkRuntime() {
     super(new DataSinkContextGenerator(), new DataSinkParameterGenerator());
@@ -83,14 +84,52 @@ public class StandaloneEventSinkRuntime extends 
StandalonePipelineElementRuntime
   @Override
   protected void beforeStart() {
     pipelineElement.onPipelineStarted(runtimeParameters, runtimeContext);
+    pipelineStarted = true;
     inputCollectors.forEach(is -> is.registerConsumer(instanceId, this));
     prepareRuntime();
   }
 
   @Override
   protected void afterStop() {
-    inputCollectors.forEach(is -> is.unregisterConsumer(instanceId));
-    pipelineElement.onPipelineStopped();
-    postDiscard();
+    RuntimeException stopException = null;
+    try {
+      pipelineElement.onPipelineStopped();
+    } catch (RuntimeException e) {
+      stopException = collectCleanupException(stopException, e);
+    } finally {
+      pipelineStarted = false;
+    }
+    try {
+      postDiscard();
+    } catch (RuntimeException e) {
+      stopException = collectCleanupException(stopException, e);
+    }
+
+    if (stopException != null) {
+      throw stopException;
+    }
+  }
+
+  @Override
+  protected void afterStartFailed() {
+    RuntimeException cleanupException = null;
+    try {
+      super.afterStartFailed();
+    } catch (RuntimeException e) {
+      cleanupException = collectCleanupException(cleanupException, e);
+    }
+    try {
+      if (pipelineStarted) {
+        pipelineElement.onPipelineStopped();
+      }
+    } catch (RuntimeException e) {
+      cleanupException = collectCleanupException(cleanupException, e);
+    } finally {
+      pipelineStarted = false;
+    }
+
+    if (cleanupException != null) {
+      throw cleanupException;
+    }
   }
 }
diff --git 
a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandalonePipelineElementRuntime.java
 
b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandalonePipelineElementRuntime.java
index e906161004..547a4e7de9 100644
--- 
a/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandalonePipelineElementRuntime.java
+++ 
b/streampipes-wrapper-standalone/src/main/java/org/apache/streampipes/wrapper/standalone/runtime/StandalonePipelineElementRuntime.java
@@ -75,18 +75,44 @@ public abstract class StandalonePipelineElementRuntime<
     this.runtimeContext = runtimeContext;
     this.instanceId = pipelineElementInvocation.getElementId();
     this.inputCollectors = 
getInputCollectors(pipelineElementInvocation.getInputStreams());
-    this.beforeStart();
+    try {
+      this.beforeStart();
+    } catch (RuntimeException e) {
+      try {
+        this.afterStartFailed();
+      } catch (RuntimeException cleanupException) {
+        e.addSuppressed(cleanupException);
+      }
+      throw e;
+    }
   }
 
   @Override
   public void stopRuntime() {
-    this.inputCollectors.forEach(is -> is.unregisterConsumer(instanceId));
-    resetCounter(instanceId);
-    afterStop();
+    RuntimeException stopException = null;
+    try {
+      unregisterInputCollectors();
+    } catch (RuntimeException e) {
+      stopException = collectCleanupException(stopException, e);
+    }
+    try {
+      afterStop();
+    } catch (RuntimeException e) {
+      stopException = collectCleanupException(stopException, e);
+    }
+    try {
+      removeMonitoring(instanceId);
+    } catch (RuntimeException e) {
+      stopException = collectCleanupException(stopException, e);
+    }
+
+    if (stopException != null) {
+      throw stopException;
+    }
   }
 
-  protected void resetCounter(String resourceId) throws SpRuntimeException {
-    monitoringManager.resetCounter(resourceId);
+  protected void removeMonitoring(String resourceId) throws SpRuntimeException 
{
+    monitoringManager.remove(resourceId);
   }
 
   protected List<SpInputCollector> getInputCollectors(List<SpDataStream> 
inputStreams) throws SpRuntimeException {
@@ -115,6 +141,45 @@ public abstract class StandalonePipelineElementRuntime<
     this.inputCollectors.forEach(is -> is.registerConsumer(instanceId, this));
   }
 
+  protected void unregisterInputCollectors() {
+    if (this.inputCollectors != null) {
+      this.inputCollectors.forEach(is -> is.unregisterConsumer(instanceId));
+    }
+  }
+
+  protected void afterStartFailed() {
+    RuntimeException cleanupException = null;
+    try {
+      unregisterInputCollectors();
+    } catch (RuntimeException e) {
+      cleanupException = collectCleanupException(cleanupException, e);
+    }
+    try {
+      disconnectInputCollectors();
+    } catch (RuntimeException e) {
+      cleanupException = collectCleanupException(cleanupException, e);
+    }
+    try {
+      removeMonitoring(instanceId);
+    } catch (RuntimeException e) {
+      cleanupException = collectCleanupException(cleanupException, e);
+    }
+
+    if (cleanupException != null) {
+      throw cleanupException;
+    }
+  }
+
+  protected RuntimeException collectCleanupException(RuntimeException 
cleanupException,
+                                                    RuntimeException 
nextException) {
+    if (cleanupException == null) {
+      return nextException;
+    } else {
+      cleanupException.addSuppressed(nextException);
+      return cleanupException;
+    }
+  }
+
   protected abstract void beforeStart();
 
   protected abstract void afterStop();

Reply via email to